#Passing state from one page to a another

21 messages · Page 1 of 1 (latest)

opaque plover
#

It's me again!
I'm trying to make my life difficult.
I have two pages that both need data from an API call. So, instead of of using the dame function twice, I've moved the API request in a separate component. You may notice the weird res=>res.text ;it's not related to our current question.

export function GetListings(url) {
    const [listings, setListings] = React.useState([])
    **const [isLoading, setIsLoading] = React.useState(true)**
    React.useEffect(() => {
        fetch(url)
            .then(res => res.text())
            .then(text => {
                setTimeout(() => {
                    setListings(JSON.parse(text))
                    setIsLoading(false)
                }, 1500)
            })
            
    }, [])

    return listings
}

This call does two things: fetches data AND sets isLoading state to false. THis is for a loader component, and is at the heart of my problem.

When I call the function on another page to get the data, I don't know how to receive the state isLoading change as well.

This is the component for the page that needs the data and the state

export default function FicheLogement() {
    **const [isLoading, setIsLoading] = React.useState(true)**
    
    const data = GetListings("../../data/logements.json")
    **console.log(isLoading)**
    
(some code)

    if (isLoading) {
        return <Loader />
    } else if (listing){
        return ( 
            <JSX...>

There, I'm declaring the same state, which is probably a mistake const [isLoading, setIsLoading] = React.useState(true) . Indeed, the console.log shows that the state does not update, and the loader loads forever. But I don't know how to receive the state change set in the API component.

I'm familiar with the concept of lifting state up, but have no idea how to implement it.
Thanks!

glacial oar
#

If your application is not huge, you are correct you can lift the state up. Lifting the state only means that you grab the state and the related parts from the current place and move it a level or more higher. This will allow you an easy way to share state between components.

Also GetListings should be a hook so it should start with use keyword ---> useGetListings.

Then you can reuse this custom hook everywhere. You should also want to return the isLoading state from that hook alongside the listing data so you have access to it.

Now, reusing the same hook will create a separate scope each time, meaning, the state is not shared, so you will do double data fetching if you use it at the places which is not ideal since the data can be reused.

To avoid doing that, you can do the state lifting and pass down the props or you can create a context provider and use that as the core as the data manager.

opaque plover
# glacial oar If your application is not huge, you are correct you can lift the state up. Lift...

Hey Bence, thanks for your answer.

What do you mean by this
``Also GetListings should be a hook so it should start with use keyword ---> useGetListings.

Then you can reuse this custom hook everywhere. You should also want to return the isLoading state from that hook alongside the listing data so you have access to it.
``
I did have warning that it should be using a capital G, but it's not really a component

devout anvil
#

But as Bence said, in his last 2 paragraphs it would be better to pass down the props as if you use the custom hook in two places, you are eventually making two API calls anyway which defeats the purpose of your problem of not repeating the same code/same fetch call in two separate components.

opaque plover
#

Hey Vimo, thanks. I just found that article after Bence's comment. Reading it.
I just don't understand the logic of passing down the props. Who is the parent here? GetListings () is not a component, doesn't appear on the <App />. Should <App /> hold the isLoading state?

devout anvil
# opaque plover Hey Vimo, thanks. I just found that article after Bence's comment. Reading it. I...

The idea being you make a fetch call in the parent component of the child component which wants the data in.
So if you need data in a child component <DucksPage .../> which is loaded from the parent component <App /> you make the fetch call in App and pass down the props instead.
Or if its a grandchild, you lift the state up to App and pass to child and then grandchild <DucksPage .../>
Or you use useContext to create a context which provides that data to the whole app or just the component you want.

glacial oar
# opaque plover Hey Vimo, thanks. I just found that article after Bence's comment. Reading it. I...

In the "old days" when we had class based components there was a highly used pattern called the container pattern (it doesn't mean this cannot be used with functional components and hooks).

The main idea is to have a centralised place, a container which is essentially the data layer. You store the data in that container and all the relevant operations. eg.:
MoviesContainer

  • It will does the following: Fetch the data
  • It will provide the following: The data itself in a pure or computed shape + additional methods eg.: Mark a movie as watched, favourite, delete it from favourites, add a new movie, delete a movie, refetch etc....

Now the container is your core part, it isolates the business logic from the other part os your application. Now, you can create "dumb" components. A dumb component is just purely a component which takes in some data, and renders it to the user. It doesn't do any formatting, doesn't contain any business logic etc...

You use these dumb components inside your container and pass the relevant props to those components.

#

In your case this is exactly what need to be done.
You need a "container" where you have your data stored. This can be the App component itself since your application isn't robust just yet.

opaque plover
#

OK I think I understand the logic.

How can I return the isLoading state from that hook alongside the listing data

glacial oar
#

or you can return a tuple (array) with 2 elements, the data and isLoading

#

an object would be more intuitive in your case tho

#

You can also memoise the returned object but that is not very important for now

opaque plover
#

OK thanks,
I managed to get it to work with returning an object containing the two pieces os info

opaque plover
#

Thanks both ;
It's now the second time I've had to deal with lifting state up, and I've failed both times.

glacial oar
#

so just keep up the good work 🙂

jovial ember
#

Only need to make custom hook who retrieve data when needed or useContext hook which will share your data across the application.

opaque plover
#

Hey guys,
So I did a bit of refactoring to use async fetch calls. It's all working fine, but I wondering if you could give me your opinion on these snippets.

I have a separate Services.js files that contains my 2 API functions/custom hooks (I'll just show you one). They manage fetching the data and managing state, then return data and state.

//custom hook used in Home.js
export function UseGetListings(url) {
        const [listings, setListings] = useState([])
        const [isLoading, setIsLoading] = useState(true)
        const [error, setError] = useState(false)

        useEffect(() => {
            async function getListings() {
                try {
                    const response = await fetch(url)
                    const data = await response.json()
                    setListings(data)
                }
                catch(error) {
                    console.log(error)
                    setError(true)
                }
                finally {
                    setTimeout(() => {
                        setIsLoading(false)
                    }, 1500)
                }
            }
            getListings()
        }, [url])

        return {listings, isLoading, error}
}

(TBC)