Rob Terrell
Rob Terrell

Reputation: 2552

useEffect has missing dependency

For the life of me I can't seem to remove the ESlinting warning about my useEffect having a missing dependency fetchProfile(). When I add fetchProfile to the dependency array, I get an endless loop. I would really appreciate any suggestions that could help me stifle this warning. The code is as follows:

import React, { useEffect, useContext, useReducer } from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import './App.css'
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles/'
import UserContext from './contexts/UserContext'
import jwtDecode from 'jwt-decode'
import axios from 'axios'

// utils
import reducer from './utils/reducer'
import themeFile from './utils/theme'
import AuthRoute from './utils/AuthRoute'
import UnAuthRoute from './utils/UnAuthRoute'

// Components
import NavBar from './components/NavBar'

// Pages
import Home from './pages/Home'
import Login from './pages/Login'
import Profile from './pages/Profile'
import SignUp from './pages/SignUp'
import Admin from './pages/Admin'
import Dashboard from './pages/Dashboard'
import Alumni from './pages/Alumni'

// context
import { ProfileContext } from './contexts/ProfileContext'

const theme = createMuiTheme(themeFile)

// axios.defaults.baseURL = `https://us-central1-jobtracker-4f14f.cloudfunctions.net/api`

const App = () => {
  const initialState = useContext(UserContext)
  const [user, setUser] = useContext(ProfileContext)
  const [state, dispatch] = useReducer(reducer, initialState)

  const fetchProfile = async token => {
    await axios
      .get(`/user`, {
        headers: {
          Authorization: `${token}`
        }
      })
      .then(res => {
        setUser(res.data)
      })
      .catch(err => console.log({ err }))
  }
  // keeps userContext authorized if signed in
  useEffect(
    _ => {
      const token = localStorage.FBIdToken
      if (token && token !== 'Bearer undefined') {
        const decodedToken = jwtDecode(token)
        if (decodedToken.exp * 1000 < Date.now()) {
          localStorage.removeItem('FBIdToken')
          dispatch({ type: 'LOGOUT' })
        } else {
          dispatch({ type: 'LOGIN' })
          state.isAuth && fetchProfile(token)
        }
      } else {
        dispatch({ type: 'LOGOUT' })
        localStorage.removeItem('FBIdToken')
      }
    },
    [state.isAuth]
  )

  return (
    <MuiThemeProvider theme={theme}>
      <UserContext.Provider value={{ state, dispatch }}>
        <div className="App">
          <Router>
            <NavBar isAuth={state.isAuth} />
            <div className="container">
              <Switch>
                <Route exact path="/" component={Home} />
                <UnAuthRoute
                  path="/signup"
                  component={SignUp}
                  isAuth={state.isAuth}
                />
                <UnAuthRoute
                  path="/login"
                  component={Login}
                  isAuth={state.isAuth}
                />
                <AuthRoute
                  path="/profile"
                  component={Profile}
                  isAuth={state.isAuth}
                />
                <AuthRoute
                  path="/dashboard"
                  component={Dashboard}
                  isAuth={state.isAuth}
                />
                <Route path="/admin" component={Admin} isAuth={state.isAuth} />
                <AuthRoute
                  path="/users/:id"
                  component={Alumni}
                  isAuth={state.isAuth}
                />
              </Switch>
            </div>
          </Router>
        </div>
      </UserContext.Provider>
    </MuiThemeProvider>
  )
}

export default App

Upvotes: 0

Views: 215

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1073968

You can do one of two things:

  1. Move fetchProfile out of the component entirely, and use its result instead of having it call setUser directly.

  2. Memoize fetchProfile so you only create a new one when something it depends on changes (which is...never, because fetchProfile only depends on setUser, which is stable). (You'd do this with useMemo or its close cousin useCallback, probably, though in theory useMemo [and thuse useCallback] is for performance enhancement, not "semantic guarantee.")

For me, #1 is your best bet. Outside your component:

const fetchProfile = token => {
  return axios
    .get(`/user`, {
      headers: {
        Authorization: `${token}`
      }
    })
}

then

useEffect(
  _ => {
    const token = localStorage.FBIdToken
    if (token && token !== 'Bearer undefined') {
      const decodedToken = jwtDecode(token)
      if (decodedToken.exp * 1000 < Date.now()) {
        localStorage.removeItem('FBIdToken')
        dispatch({ type: 'LOGOUT' })
      } else {
        dispatch({ type: 'LOGIN' })
        if (state.isAuth) {                         // ***
            fetchProfile(token)                     // ***
            .then(res => setUser(res.data))         // ***
            .catch(error => console.error(error))   // ***
        }                                           // ***
      }
    } else {
      dispatch({ type: 'LOGOUT' })
      localStorage.removeItem('FBIdToken')
    }
  },
  [state.isAuth]
)

Since the action is asynchronous, you might want to cancel/disregard it if the component re-renders in the meantime (it depends on your use case).

Upvotes: 2

Related Questions