简体   繁体   中英

How to get data from server and use a hook to retrieve it in multiple components?

I have a rather basic use-case: I want to get the user info from the server when the app loads and then using a hook to get the info in different components.

For some reason, I run into an infinite loop and get Error: Maximum update depth exceeded. getMe gets called recursively until the app crashes. Is that a correct hook behavior?

This is the relevant part of the hook:

export default function useUser () {
  const [user, setUser] = useState(null)
  const [authenticating, setAuthenticating] = useState(true)

  // ...

  const getMe = (jwt) => {
    console.log('set user')
    axios.get(baseURL + endpoints.currentUser, { headers: {
      'X-Access-Token': jwt,
      'Content-Type': 'application/json'
    }}).then(response => {
      setUser({
        name: response.data.name,
        img: response.data.avatar_url
      })
    })
  }

  useEffect(() => {
    getMe(jwt)
  }, [])

  return { user, authenticating }
}

This is the first call

function App () {
  const { user, authenticating } = useUser()
  const c = useStyles()

  return (
    authenticating ? (
      <div className={c.wrapper}>
        <Loader size={60}/>
      </div>
    ) : (
      <div className={c.wrapper}>
        <div className={c.sidebar}>
          <img className={c.lamp} src={ user ? user.img : lamp } />

And I also call need the user in the Router component

const Routes = () => {
  const { user } = useUser()

  return (
    <Router history={history}>
      <Switch>
        // ...
        <Route
          path={pages.login}
          render={routeProps => (
            user ?
              <Redirect to={pages.main}/> :
              <Login {...routeProps}/>
          )}
        />

You shouldn't be requesting the server each time you call the hook since it pretty much unnecessary. You could use Redux or context for this (for this paradigm redux would be better). However, if you insist on this method, it seems you have to wrap your getMe function in a useCallback hook since it must be re-rendering each time the function runs.

Read more on the useCallback hook:

https://reactjs.org/docs/hooks-reference.html#usecallback

You're now making a request via the useEffect in your custom hook - why not let the component do that programatically?

Change getMe to a useCallback function and export it:

export default function useUser () {
  const [user, setUser] = useState(null)
  const [authenticating, setAuthenticating] = useState(true)

  // ...

  const getMe = useCallback((jwt) => {
    console.log('set user')
    axios.get(baseURL + endpoints.currentUser, { headers: {
      'X-Access-Token': jwt,
      'Content-Type': 'application/json'
    }}).then(response => {
      setUser({
        name: response.data.name,
        img: response.data.avatar_url
      })
    })
  }, [])

  return { user, authenticating, doFetch: getMe }
}

..and use that function in your components (import doFetch and call it on mount), eg:

function App () {
  const { user, authenticating, doFetch } = useUser()
  const c = useStyles()

  useEffect(() => doFetch(), [doFetch])

  return (
    authenticating ? (
      <div className={c.wrapper}>
        <Loader size={60}/>
      </div>
    ) : (
      <div className={c.wrapper}>
        <div className={c.sidebar}>
          <img className={c.lamp} src={ user ? user.img : lamp } />

You now avoid the infinite loop and your component takes control of the request logic instead of the reusable hook.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM