4

I'm newbie in React but I'm developing an app which loads some data from the server when user open the app. App.js render this AllEvents.js component:

const AllEvents = function ({ id, go, fetchedUser }) {
    const [popout, setPopout] = useState(<ScreenSpinner className="preloader" size="large" />)
    const [events, setEvents] = useState([])
    const [searchQuery, setSearchQuery] = useState('')
    const [pageNumber, setPageNumber] = useState(1)

    useEvents(setEvents, setPopout) // get events on the main page

    useSearchedEvents(setEvents, setPopout, searchQuery, pageNumber)

    // for ajax pagination
    const handleSearch = (searchQuery) => {
        setSearchQuery(searchQuery)
        setPageNumber(1)
    }

    return(
        <Panel id={id}>
            <PanelHeader>Events around you</PanelHeader>
            <FixedLayout vertical="top">
                <Search onChange={handleSearch} />
            </FixedLayout>
            {popout}
            {
                <List id="event-list">
                    {
                        events.length > 0
                    ?
                        events.map((event, i) => <EventListItem key={event.id} id={event.id} title={event.title} />)
                    :
                        <InfoMessages type="no-events" />
                    }
                </List>
            }
        </Panel>
    )
}

export default AllEvents

useEvents() is a custom hook in EventServerHooks.js file. EventServerHooks is designed for incapsulating different ajax requests. (Like a helper file to make AllEvents.js cleaner) Here it is:

function useEvents(setEvents, setPopout) {
    useEffect(() => {
        axios.get("https://server.ru/events")
            .then(
                (response) => {
                    console.log(response)
                    console.log(new Date())
                    setEvents(response.data.data)
                    setPopout(null)
                },
                (error) => {
                    console.log('Error while getting events: ' + error)
                }
            )
    }, [])

    return null
}

function useSearchedEvents(setEvents, setPopout, searchQuery, pageNumber) {
    useEffect(() => {
        setPopout(<ScreenSpinner className="preloader" size="large" />)
        let cancel
        axios({
            method: 'GET',
            url: "https://server.ru/events",
            params: {q: searchQuery, page: pageNumber},
            cancelToken: new axios.CancelToken(c => cancel = c)
        }).then(
            (response) => {
                setEvents(response.data)
                setPopout(null)
            },
            (error) => {
                console.log('Error while getting events: ' + error)
            }
        ).catch(
            e => {
                if (axios.isCancel(e)) return
            }
        )

        return () => cancel()
    }, [searchQuery, pageNumber])

    return null
}

export { useEvents, useSearchedEvents }

And here is the small component InfoMessages from the first code listing, which display message "No results" if events array is empty:

const InfoMessages = props => {
    switch (props.type) {
        case 'no-events':
            {console.log(new Date())}
            return <Div className="no-events">No results :(</Div>
        default:
            return ''
    }
}

export default InfoMessages

So my problem is that events periodically loads and periodically don't after app opened. As you can see in the code I put console log in useEvents() and in InfoMessages so when it's displayed it looks like this: logs if events are displayed, and the app itself

And if it's not displayed it looks like this: logs if events are not displayed, and the app itself

I must note that data from the server is loaded perfectly in both cases, so I have totally no idea why it behaves differently with the same code. What am I missing?

taseenb
  • 1,378
  • 1
  • 16
  • 31
ivnku
  • 83
  • 8
  • 1
    This is very interesting piece of code. The first suggestion is to see if you can bring `useEffect` inline, or use `useAsync` or `useApi` (any third party). My guts feeling is that `useEffect` wasn't coded properly. https://usehooks.com/useAsync/ – windmaomao Mar 07 '20 at 15:09
  • 1
    @windmaomao firstly I removed useEvents call from AllEvents.js and replaced it with inline useEffect, it didn't help, then I decided to comment out the call of useSearchedEvents and tried to open the app 15 times and all 15 times all the events were displayed. So it something wrong with my custom hooks in **EventServerHooks** you're right. But I still don't know what is really wrong) – ivnku Mar 07 '20 at 15:24

2 Answers2

2

Do not pass a hook to a custom hook: custom hooks are supposed to be decoupled from a specific component and possibly reused. In addition, your custom hooks return always null and that's wrong. But your code is pretty easy to fix.

In your main component you can fetch data with a custom hook and also get the loading state like this, for example:

function Events () {
  const [events, loadingEvents] = useEvents([])

  return loadingEvents ? <EventsSpinner /> : <div>{events.map(e => <Event key={e.id} title={e.title} />}</div>
}

In your custom hook you should return the internal state. For example:

function useEvents(initialState) {
  const [events, setEvents] = useState(initialState)
  const [loading, setLoading] = useState(true)

  useEffect(function() {
    axios.get("https://server.ru/events")
            .then(
                (res) => {
                    setEvents(res.data)
                    setLoading(false)
                }
            )
  }, [])

  return [events, loading]
}

In this example, the custom hook returns an array because we need two values, but you could also return an object with two key/value pairs. Or a simple variable (for example only the events array, if you didn't want the loading state), then use it like this:

const events = useEvents([])
taseenb
  • 1,378
  • 1
  • 16
  • 31
  • thanks for the reply, now I see the right solution) – ivnku Mar 21 '20 at 15:43
  • And one more question, since ajax call is async, how we can be sure that return [events, loading] will be executed after ajax finish its fetching and custom hook will return fetched events and not the initial value? – ivnku Mar 21 '20 at 16:10
  • That line `return [events, loading]` will be executed every time the hook updates. In this case that will be only 2 times. Once at the initialisation and once when `setEvents` and `setLoading` get called. Therefore the value of `[events, loading]` will be updated accordingly. – taseenb Mar 22 '20 at 02:54
0

This is another example that you can use, creating a custom hook that performs the task of fetching the information

export const useFetch = (_url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(true);

  useEffect(function() {
    setLoading('procesando...');
    setData(null);
    setError(null);
    const source = axios.CancelToken.source();
    
    setTimeout( () => {
      axios.get( _url,{cancelToken: source.token})
        .then(
          (res) => {
            setLoading(false);
            console.log(res.data);
            //setData(res);
            res.data && setData(res.data);
            // res.content && setData(res.content); 
          })
          .catch(err =>{
            setLoading(false);
            setError('si un error ocurre...');
          })
    },1000)
    
    return ()=>{
      source.cancel();
    }    
  }, [_url])
Emma
  • 658
  • 10
  • 20