Home > Software design >  why the changing state of my component do not transmit when i export this component?
why the changing state of my component do not transmit when i export this component?

Time:09-17

I have a hook that rules my requests. It has state "loading" that becoming true while loading

export const useHttp = () => {

const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const request = useCallback(async (url, method = 'GET', body = null, headers = {'Content-Type': 'application/json' }) => {

    setLoading(true);

    try {
        const res = await fetch(url, {method, body, headers});
        if (!res.ok) {
            throw new Error(`Could not fetch ${url}, status: ${res.status}`); 
        }
        const data = await res.json();
        setLoading(false);
        return data;
    } catch(e) {
        setLoading(false);
        setError(e.message);
        throw e;
    }

}, [])

const clearError = useCallback(() => {
    setError(null);
}, [])

return {
    loading,
    error,
    request,
    clearError
}

}

Also i have a service that makes requests:

import { useHttp } from "../hooks/http.hook";

    const useNasaService = () => {

    const { loading, error, request, clearError } = useHttp();
 
    const _apiBase = 'https://api.nasa.gov/';
    const _apiKey = 'api_key=DEMO_KEY';

    const getMissionManifest = async (rover) => {
        const res = await request(`${_apiBase}mars-photos/api/v1/manifests/${rover}/?${_apiKey}`);
        return _transformManifestData(res.photo_manifest);
    }

    const getImagesData = async (rover, sol, page = 1) => {
        const res = await request(`${_apiBase}mars-photos/api/v1/rovers/${rover}/photos?sol=${sol}&page=${page}&${_apiKey}`);
        return res.photos.map(_transformImagesData);
    }

    const _transformImagesData = (data) => {
        return {
            id: data.id,
            sol: data.sol,
            earthDate: data.earth_date,
            path: data.img_src,
            camera: data.camera.full_name,
            rover: data.rover.name
        }
    }

    const _transformManifestData = (data) => {
        return {
            landingDate: data.landing_date,
            launchDate: data.launch_date,
            maxDate: data.max_date,
            maxSol: data.max_sol,
            name: data.name,
            photos: data.photos,
            status: data.status,
            totalPhotos: data.total_photos
        }
    }

    return {
        loading,
        error,
        clearError,
        getImagesData,
        getMissionManifest
    }

   }

   export default useNasaService;

Finally i have a component that needs state "loading" for disabling the inputs. The question is why "loading" is never getting true in this component:

import useNasaService from '../../services/useNasaService';

const RoverFilter = (props) => {

    const { loading } = useNasaService();

    console.log(loading); /* always false */

    const onRadioChange = (e) => {
        props.onRoverSelected(e.target.value);
        props.onRoverClicked(e.target.value);
    }

    return (
        <div className="roverFilter" >
            <h2 className="roverFilter__title">Select rover</h2>
            <div className="roverFilter__inputs">
                <label htmlFor="curiosity">Curiosity</label>
                <input disabled={loading} type="radio" name="rover-choise" id="curiosity" value="curiosity" onChange={onRadioChange}/>
                <label htmlFor="opportunity">Opportunity</label>
                <input disabled={loading} type="radio" name="rover-choise" id="opportunity" value="opportunity" onChange={onRadioChange}/>
                <label htmlFor="spirit">Spirit</label>
                <input disabled={loading} type="radio" name="rover-choise" id="spirit" value="spirit" onChange={onRadioChange}/>
                <label htmlFor="perseverance">Perseverance</label>
                <input disabled={loading} type="radio" name="rover-choise" id="perseverance" value="perseverance" onChange={onRadioChange}/>
            </div>
        </div>
    )
}

export default RoverFilter;

By the way, in my app there are another components, where "loading" becoming true without any problems. I cant see the difference.

for example, here loading works good:


import { useEffect, useState } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';

import useNasaService from '../../services/useNasaService';
import ImageGallerySkeleton from '../imageGallerySkeleton/ImageGallerySkeleton';
import Spinner from '../spinner/Spinner';
import ErrorMessage from '../errorMessage/ErrorMessage';
import SliderModal from '../sliderModal/SliderModal';

const ImageGallery = (props) => {

    const {loading, getImagesData, clearError, error} = useNasaService();

    const [imagesData, setImagesData] = useState([]);
    const [nextPage, setNextPage] = useState(1);
    const [firstLoading, setFirstLoading] = useState(true);
    const [imagesDataLoaded, setImagesDataLoaded] = useState(false);
    const [itemIndex, setItemIndex] = useState(0);
    const [sliderOpen, setSliderOpen] = useState(false);

    const transitionDuration = 1000;

    const onImagesDataLoaded = (newData) => {
        setImagesData(data => [...data, ...newData]);
        setNextPage(page => page   1);
        setFirstLoading(false);
        setImagesDataLoaded(true);
    }

    const onRequestImages = (rover, sol, page) => {
        clearError();
        if (!rover || !sol) return;
        getImagesData(rover, sol, page)
            .then(onImagesDataLoaded);
    }

    const onSliderClosed = () => {
        setSliderOpen(false);
    }

    useEffect(() => {
        onRequestImages(props.selectedRover, props.selectedSol, nextPage);
        // eslint-disable-next-line
    }, [props.selectedRover, props.selectedSol])

    if (sliderOpen) {
        document.body.style.overflow = "hidden";
    } else {
        document.body.style.overflow = "visible";
    }


    function renderItemList(arr) {
        const itemList = arr.map((item, i) => {
            return (
                <CSSTransition
                    key={item.id} 
                    in={imagesDataLoaded}
                    timeout={transitionDuration}
                    classNames='imageGallery__card'>
                    <li className="imageGallery__card"
                        onClick={() => {
                            setSliderOpen(true);
                            setItemIndex(i);
                        }}>
                        <img src={item.path} alt="img from mars"/>
                        <div className="imageGallery__descr">
                            <ul>
                                <li>Rover: {item.rover}</li>
                                <li>Earth_date: {item.earthDate}</li>
                                <li>Sol: {item.sol}</li>
                                <li>{item.camera}</li>
                            </ul>
                        </div>
                    </li>    
                </CSSTransition> 
            )
        })
        return (
            <ul className="imageGallery__list">
                <TransitionGroup component={null}>
                    {itemList}
                </TransitionGroup>
            </ul>
        )
    }

    const spinner = loading && firstLoading ? <Spinner/> : null;
    const skeleton = imagesData.length === 0 && firstLoading && !loading && !error ? <ImageGallerySkeleton/> : null;
    const items = renderItemList(imagesData);
    const errorMessage = error ? <ErrorMessage/> : null;
    const counter = imagesData.length === 0 || error ? null : 
    <h2 className="imageGallery__title">
        Showed {loading ? "..." : imagesData.length} photos of {props.totalPhotosInSol}
    </h2>
    const button = props.totalPhotosInSol === imagesData.length ? null : 
    <button 
        onClick={() => onRequestImages(props.selectedRover, props.selectedSol, nextPage)} 
        disabled={loading}
        className="imageGallery__btn">{loading  ? "Loading..." : "Load next page" }
    </button>    

    const slider = <SliderModal 
                    open={sliderOpen} 
                    items={imagesData} 
                    slideIndex={itemIndex} 
                    onSliderClosed={onSliderClosed} />

    const wrapStyles = firstLoading && loading ? {"padding": "50px"} : null;

    return (
        <section className="imageGallery" style={wrapStyles}>
            {counter}
            {spinner}
            {skeleton}
            {imagesData.length === 0 && !firstLoading ? 
                <h2 className="imageGallery__title">There is no photo for this sol</h2> : 
                items
            }
            {button}
            {errorMessage}
            {slider}
        </section>
    )
}

export default ImageGallery;

CodePudding user response:

When you call useState in two different components, those states are independant from eachother. This is still true if you move the useState calls inside a custom hook. If two components call useNasaService (which calls useHttp, which calls useState), then the two components are creating their own states and own functions. If component A starts loading data, that will have no effect on component B.

So ImageGallery is working because it makes a call to getImagesData. This sets the loading state of ImageGallery to true. No other components are affected by this though. When the loading finishes, ImageGallery will set state to have the new data, but again, no other components can use this. RoverFilter on the other hand never calls getImagesData, so its loading state stays false, and it never gets any data.

In react, the typical way to share data is to lift state up. You have a component higher up in the tree, which is responsible for loading the data and setting state. That component then passes the data and functions down to any children that need it. You can either pass the data down using props, or if you need to pass the data a long distance you can consider using context instead.

There's also a wide variety of 3rd party libraries which can be used to manage global state. For example, Redux, Jotai, Zustand, MobX. These can make it simpler to share data between components in far-flung parts of the component tree.

  • Related