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
You can do one of two things:
Move fetchProfile out of the component entirely, and use its result instead of having it call setUser directly.
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).
Related
I have setup axios to use interceptor which does follows,
Make API call to an endpoint
If it fails with status 401, make call to refresh-token
If this refresh call fails with status 401 redirect user to login page.
Happening issues,
It does not stops on failing first api call, it makes all apis calls: /students, /fees/total and /courses.
Result of above, it makes multiple calls to refresh-token image attached below
If refresh token api fails it also not redirecting to /login; url changes but redirection does not happen.
When I refresh the page, only then redirection happens.
I will list files in order,
Dashboard.jsx
It uses custom axios hook useAxios to make three api calls to fetch widget data.
Each api call must be authenticated.
import { Box, Stack } from '#mui/material';
import { useAxios } from '../../api/use-axios';
import { NewWidget } from '../../components/widget/NewWidget';
import ApiConfig from '../../api/api-config';
const Dashboard = () => {
const { response: studentResponse } = useAxios(ApiConfig.STUDENT.GET_STUDENTS);
const { response: courseResponse } = useAxios(ApiConfig.COURSE.GET_COURSES);
const { response: feesResponse } = useAxios(ApiConfig.FEES.GET_TOTAL);
return (
<Box padding={2} width="100%">
<Stack direction={'row'} justifyContent="space-between" gap={2} mb={10}>
<NewWidget type={'student'} counter={studentResponse?.data?.length} />
<NewWidget type={'course'} counter={courseResponse?.data?.length} />
<NewWidget type={'earning'} counter={feesResponse?.data} />
</Stack>
</Box>
);
};
export default Dashboard;
use-axios.js
It is a custom axios hook, which attaches interceptor on the response as well as request.
import { useState, useEffect } from 'react';
import axios from 'axios';
import history from '../utils/history';
import refreshToken from './refresh-token';
const Client = axios.create();
Client.defaults.baseURL = 'http://localhost:3000/api/v1';
const getUser = () => {
const user = localStorage.getItem('user');
return user ? JSON.parse(user) : null;
};
const updateLocalStorageAccessToken = (accessToken) => {
const user = getUser();
user.accessToken = accessToken;
localStorage.setItem('user', JSON.stringify(user));
};
Client.interceptors.request.use(
(config) => {
const user = getUser();
config.headers.Authorization = user?.accessToken;
return config;
},
(error) =>
// Do something with request error
Promise.reject(error)
);
Client.interceptors.response.use(
(response) => response,
async (error) => {
// Reject promise if usual error
if (error.response.status !== 401) {
return Promise.reject(error);
}
const user = getUser();
const status = error.response ? error.response.status : null;
const originalRequest = error.config;
if (status === 401) {
refreshToken(user.refreshToken)
.then((res) => {
console.log('response', res);
const { accessToken } = res.data.data;
Client.defaults.headers.common.Authorization = accessToken;
// update local storage
updateLocalStorageAccessToken(accessToken);
return Client(originalRequest);
})
.catch((err) => {
console.log(err);
if (err.response.status === 401) {
localStorage.setItem('user', null);
history.push('/login');
}
return Promise.reject(err);
});
}
return Promise.reject(error);
}
);
export const useAxios = (axiosParams, isAuto = true) => {
const [response, setResponse] = useState(undefined);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
const fetchData = async (params) => {
try {
const result = await Client.request({
...params,
method: params.method || 'GET',
headers: {
accept: 'application/json',
},
});
setResponse(result.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (isAuto) fetchData(axiosParams);
}, [axiosParams, isAuto]); // execute once only
return { fetch: () => fetchData(axiosParams), response, error, loading };
};
refresh-token.js
import Client from './client';
const refreshToken = (refreshToken) =>
Client({
url: '/auth/refresh-token',
method: 'POST',
data: {
refreshToken,
},
});
export default refreshToken;
client.js
import axios from 'axios';
const Client = axios.create();
Client.defaults.baseURL = 'http://localhost:3000/api/v1';
export default Client;
Errors,
It is supposed to stop on first api call fail, and it should try to refresh token. If refreshing also fails with status 401, it should go back to login page.
So only two API calls: one actual and one to refresh.
Update:
{
...,
"react-router-dom": "^6.2.2",
}
history.js
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
App.js
import { useContext } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import Home from './pages/home/Home';
import Login from './pages/login/Login';
import Single from './pages/single/Single';
import './style/dark.scss';
import { userInputs } from './formSource';
import { AuthContext } from './context/AuthContext';
import Student from './pages/student/Student';
import Course from './pages/course/Course';
import AddNewCourse from './pages/course/AddNewCourse';
import AddNewStudent from './pages/student/AddNewStudent';
import Fees from './pages/fees/Fees';
import SingleStudent from './pages/student/SingleStudent';
import ThemeProvider from './theme';
import history from './utils/history';
function App() {
const { currentUser } = useContext(AuthContext);
const RequireAuth = ({ children }) => (currentUser ? children : <Navigate to="/login" />);
return (
<ThemeProvider>
<BrowserRouter history={history}>
<Routes>
<Route path="/">
<Route
index
element={
<RequireAuth>
<Home />
</RequireAuth>
}
/>
<Route path="login" element={<Login />} />
<Route path="students">
<Route
index
element={
<RequireAuth>
<Student />
</RequireAuth>
}
/>
<Route path=":studentId" element={<SingleStudent />} />
<Route
path="new"
element={
<RequireAuth>
<AddNewStudent inputs={userInputs} />
</RequireAuth>
}
/>
</Route>
<Route path="courses">
<Route
index
element={
<RequireAuth>
<Course />
</RequireAuth>
}
/>
<Route
path=":courseId"
element={
<RequireAuth>
<Single />
</RequireAuth>
}
/>
<Route
path="new"
element={
<RequireAuth>
<AddNewCourse inputs={userInputs} title="Add New Course" />
</RequireAuth>
}
/>
</Route>
<Route
path="fees"
element={
<RequireAuth>
<Fees />
</RequireAuth>
}
/>
</Route>
</Routes>
</BrowserRouter>
</ThemeProvider>
);
}
export default App;
I am trying to make it so that when a user clicks on a certain Link (Upload Component), it will redirect them to to the login component and am not sure how to accomplish this task in React. I was directed to another answered question, but it hasn't helped me as I am still confused on what I need to do with my own set up. I understand I need to make my own protected route (maybe), but I saw others accessing useContext and I do not have any file with context. I am using Version 6 in react dom. I am also using React router and redux in my project so I know I need to access the state somehow, just not sure how to wrap my mind around it so if anyone could help, I would appreciate it. The user is being stored in local storage with JWT authentication.
App.js:
import React, {useState, useEffect, useCallback} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom'; //Switch was replaced by Routes in react-router-dom v6
import './App.css';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import Home from './components/pages/Home';
import Uploads from './components/pages/Uploads';
import Account from './components/pages/Account';
import Login from './components/pages/Login';
import LogOut from './components/Logout';
import Register from './components/pages/Register';
import Profile from './components/pages/Profile';
import EditProfile from './components/pages/EditProfile';
import BoardAdmin from './components/pages/BoardAdmin';
import BoardModerator from './components/pages/BoardModerator';
import BoardUser from './components/pages/BoardUser';
import {logout} from './slices/auth';
import EventBus from './common/EventBus';
const App = () => {
const [showModeratorBoard, setShowModeratorBoard] = useState(false);
const [showAdminBoard, setShowAdminBoard] = useState(false);
const {user: currentUser} = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
if (currentUser) {
setShowModeratorBoard(currentUser.roles.includes('ROLE_MODERATOR'));
setShowAdminBoard(currentUser.roles.includes('ROLE_ADMIN'));
} else {
setShowModeratorBoard(false);
setShowAdminBoard(false);
}
}, [currentUser]);
return (
<>
<Router>
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
<Route path='/create/upload' element={<Uploads />} />
<Route path='/my-account' element={<Account />} />
<Route path='/login' element={<Login />} />
<Route path='/logout' element={<LogOut />} />
<Route path='/register' element={<Register />} />
<Route path='/profile' element={<Profile />} />
<Route path='/profile/edit' element={<EditProfile />} />
<Route path='/user' element={<BoardUser />} />
<Route path='/mod' element={<BoardModerator />} />
<Route path='/admin' element={<BoardAdmin />} />
</Routes>
<Footer />
</Router>
</>
);
};
export default App;
Auth.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {setMessage} from './messages';
import AuthService from '../services/auth.service';
const user = JSON.parse(localStorage.getItem('user'));
export const register = createAsyncThunk(
'auth/register',
async ({username, email, password}, thunkAPI) => {
try {
const response = await AuthService.register(username, email, password);
thunkAPI.dispatch(setMessage(response.data.message));
return response.data;
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const login = createAsyncThunk(
'auth/login',
async ({username, password}, thunkAPI) => {
try {
const data = await AuthService.login(username, password);
return {user: data};
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const logout = createAsyncThunk('auth/logout', async () => {
await AuthService.logout();
});
const initialState = user
? {isLoggedIn: true, user}
: {isLoggedIn: false, user: null};
const authSlice = createSlice({
name: 'auth',
initialState,
extraReducers: {
[register.fulfilled]: (state, action) => {
state.isLoggedIn = false;
},
[register.rejected]: (state, action) => {
state.isLoggedIn = false;
},
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
},
});
const {reducer} = authSlice;
export default reducer;
It looks like adding the if statement in my upload file is making it so that a logged in user can access the h1 element that is in the file, but not an unauthenticated user. So its working to an extent, just not redirecting back to login.
Upload.jsx:
import React from 'react';
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux';
function Uploads() {
const {user: currentUser} = useSelector((state) => state.auth);
if (!currentUser) {
return <Link to='/login' />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Rendering the Link doesn't imperatively navigate, use the Navigation component.
Example:
import React, { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
function Uploads() {
const { user: currentUser } = useSelector((state) => state.auth);
if (!currentUser) {
return <Navigate to='/login' replace />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Typically you would protect the route instead. If you wanted to do that:
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PrivateRoutes = () => {
const { user: currentUser } = useSelector((state) => state.auth);
return currentUser
? <Outlet />
: <Navigate to="/login" replace />;
}
...
<Routes>
<Route path='/' element={<Home />} />
<Route element={<PrivateRoutes />}>
<Route path='/create/upload' element={<Uploads />} />
... any other protected routes ...
</Route>
... other unprotected routes ...
</Routes>
I encountered such an error match undefind. I'm taking an old course on React did everything as shown in the lesson but I get an error why. i don't understand why match undefinde. Maybe you need to pick up the match in another way or somehow pass it ??
import React from "react";
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { NavBar } from "./components/NavBar";
import { Home } from './pages/Home'
import { About } from './pages/About'
import { Profile } from './pages/Profile'
import { Alert } from "./components/Alert";
import { AlertState } from "./context/alert/AlertState";
import { GithubState } from "./context/github/GithunState";
function App() {
return (
<GithubState>
<AlertState>
<BrowserRouter>
<NavBar />
<div className="container pt-4">
<Alert alert={{text: 'Test Alert'}} />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profile/:name" element={<Profile />} />
</Routes>
</div>
</BrowserRouter>
</AlertState>
</GithubState>
);
}
export default App;
import React from 'react'
import { useContext, useEffect } from 'react';
import { GithubContext } from '../context/github/githubContex';
export const Profile = ({match}) => {
// const github = useContext(GithubContext)
// const name = match.params.name
// useEffect(() => {
// github.getUser()
// github.getRepos(name)
// }, [])
console.log('asd',match);
return(
<div>
<h1>Profile page</h1>
</div>
)
}
import React, {useReducer} from "react"
import axios from 'axios'
import { CLEAR_USERS, GET_REPOS, GET_USER, SEARCH_USERS, SET_LOADING } from "../types"
import { GithubContext } from "./githubContex"
import { githubReducer } from "./githubReducer"
const CLIENT_ID = process.env.REACT_APP_CLIENT_ID
const CLIENT_SECRET = process.env.REACT_APP_CLIENT_SECRET
const withCreads = url => {
return `${url}client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}`
}
export const GithubState = ({children}) => {
const initialState = {
user: {},
users: [],
loading: false,
repos: []
}
const [state, dispatch] = useReducer(githubReducer, initialState)
const search = async value => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/search/users?q=${value}&`)
)
dispatch({
type: SEARCH_USERS,
payload: response.data.items
})
}
const getUser = async name => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/users/users/${name}?`)
)
dispatch({
type: GET_USER,
payload: response.data
})
}
const getRepos = async name => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/users/users/${name}/repos?per_page=5&`)
)
dispatch({
type: GET_REPOS,
payload: response.data
})
}
const clearUsers = () => dispatch({type: CLEAR_USERS})
const setLoading = () => dispatch({type: SET_LOADING})
const {user, users, repos, loading} = state
return (
<GithubContext.Provider value={{
setLoading, search, getUser, getRepos, clearUsers,
user, users, repos, loading
}}>
{children}
</GithubContext.Provider>
)
}
link to Github https://github.com/Eater228/React-Hooks
Check your package.json file and if you are using an older version of react-router-dom please use the latest version.
match prop should be passed down from the Route component and it will reflect the correct data as you are using react-router-dom.
Update
You are using element prop for rendering component and that's not the correct one. You should replace that element with component and it will work.
Update
Please consider using useParams hook instead of that match prop.
https://reactrouter.com/docs/en/v6/getting-started/overview#reading-url-parameters
Whenever I sent the uploadsData variable from App.js into the <Gallery data={uploadsData}/>} /> component via a prop, it gets called more than one time. The first array contains nothing but the next two arrays contain the correct data.
Furthermore, whenever I hover over the blue !, I get that warning illustrated in the screenshot. This means I can never properly access this object properly.
How can I make so that it's executed once and only once so I can access the object in the proper way?
I thought having a [] in useEffect() (2nd param) as the dependency would cause pave the way for this to only be executed once?
Thanks in advance for taking the time to read. I'm open to any sort of feedback including code improvements/changes that are considered better practice
Here's App.js:
import { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route, useHistory} from 'react-router-dom';
import ReactDOM from 'react-dom';
import '../../sass/HomePage/homePage.scss';
import LoginRegister from "./LoginRegister/LoginRegister";
import Gallery from "./Gallery/Gallery";
import Cookies from 'js-cookie';
const App = () => {
const [uploadsData, setUploadsData] = useState([]);
let { push } = useHistory();
let authToken = Cookies.get('token');
useEffect(() => {
getUploads();
},[]);
function getUploads() {
const headers = {
"Accept": 'application/json',
"Authorization": `Bearer ${authToken}`
}
axios.get('http://localhost:8005/api/get-uploads', {headers})
.then(resp => {
let uData = [...uploadsData,resp];
setUploadsData(uData);
if (authToken !== null) {
push('/gallery');
} else {
console.log("User's NOT authenticated, returning to login view");
push('/');
}
}).catch(error => {
console.log(error);
})
}
return (
<>
<Switch>
<Route exact path="/" component={LoginRegister} />
<Route component={() => <Gallery data={uploadsData}/>} />
</Switch>
</>
);
}
export default App;
if (document.getElementById('example')) {
ReactDOM.render(<Router><App/></Router>, document.getElementById('example'));
}
Here's Gallery.js:
import React from 'react';
const Gallery = (data) => {
return (
<>
{console.log(data)}
</>
);
}
export default Gallery;
Show the component when data is prepared:
import { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route, useHistory} from 'react-router-dom';
import ReactDOM from 'react-dom';
import '../../sass/HomePage/homePage.scss';
import LoginRegister from "./LoginRegister/LoginRegister";
import Gallery from "./Gallery/Gallery";
import Cookies from 'js-cookie';
const App = () => {
const [uploadsData, setUploadsData] = useState([]);
let { push } = useHistory();
let authToken = Cookies.get('token');
useEffect(() => {
getUploads();
},[]);
function getUploads() {
const headers = {
"Accept": 'application/json',
"Authorization": `Bearer ${authToken}`
}
axios.get('http://localhost:8005/api/get-uploads', {headers})
.then(resp => {
let uData = [...uploadsData,resp];
setUploadsData(uData);
if (authToken !== null) {
push('/gallery');
} else {
console.log("User's NOT authenticated, returning to login view");
push('/');
}
}).catch(error => {
console.log(error);
})
}
return (
<>
<Switch>
<Route exact path="/" component={LoginRegister} />
<Route component={() => <> {uploadsData?.data?.length && <Gallery data={uploadsData}/> }</>} />
</Switch>
</>
);
}
export default App;
if (document.getElementById('example')) {
ReactDOM.render(<Router><App/></Router>, document.getElementById('example'));
}
And use memo to avoid of rerender component when props don't have changes:
import React, {memo} from 'react';
const Gallery = (data) => {
return (
<>
{console.log(data)}
</>
);
}
export default memo(Gallery);
Why does the useCallback hook execute twice? I got a warning advising me to use useCallback so I'm trying to do so. From my understanding useCallback will only execute whenever the object we pass to the array is updated. So my goal is for the websocket to connect once a token is loaded. It 'mostly' works; the socket is connected twice, the callback is running twice.
const setupSocket = () => {
if (token && !socket && authenticated) {
console.log(token, authenticated, socket === null);
const newSocket = io(ENDPOINT, {
query: {
token,
},
});
newSocket.on("disconnect", () => {
setSocket(null);
setTimeout(setupSocket, 3000);
});
newSocket.on("connect", () => {
console.log("success, connected to socket");
});
setSocket(newSocket);
}
};
useCallback(setupSocket(), [token]);
App.js
import React, { useEffect, useState, useCallback } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import "./App.css";
//Pages
import Home from "./pages/home.jsx";
import LoginContainer from "./pages/login/login.container";
import Signup from "./pages/signup";
import PostDetailContainer from "./pages/post-detail/post-detail.container.js";
import jwtDecode from "jwt-decode";
import ProfileContainer from "./pages/profile/profile.container";
import AboutContainer from "./pages/about/about.container";
//Components
import Navbar from "./components/Navbar";
import AuthRoute from "./utils/AuthRoute";
//Redux
import { connect } from "react-redux";
//SocketIO
import io from "socket.io-client";
//Actions
import {
clearUserData,
getUserFromToken,
setAuthentication,
} from "./redux/actions/userActions";
function App({ user: { authenticated }, clearUserData, getUserFromToken }) {
const [token, setToken] = useState(localStorage.IdToken);
const [socket, setSocket] = useState(null);
const ENDPOINT = "http://localhost:3001";
const setupSocket = () => {
if (token && !socket && authenticated) {
const newSocket = io(ENDPOINT, {
query: {
token,
},
});
newSocket.on("disconnect", () => {
setSocket(null);
setTimeout(setupSocket, 3000);
});
newSocket.on("connect", () => {
console.log("success, connected to socket");
});
setSocket(newSocket);
}
};
useCallback(setupSocket(), [token]);
useEffect(() => {
if (token) {
//decode token
const decodedToken = jwtDecode(token);
//token is expired
if (decodedToken.exp * 1000 < Date.now()) {
//remove token from local storage
localStorage.removeItem("IdToken");
setToken(null);
clearUserData();
} else {
if (!authenticated) {
setAuthentication();
getUserFromToken(token);
}
if (authenticated) return;
//get user
}
}
}, [token, authenticated, clearUserData, getUserFromToken]);
return (
<div className="App">
<Router>
<Navbar />
<div className="container">
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={LoginContainer} />
<Route exact path="/signup" component={Signup} />
<Route exact path="/profile" component={ProfileContainer} />
<Route exact path="/about" component={AboutContainer} />
<AuthRoute
exact
path="/message/:username"
component={Message}
authenticated={authenticated}
/>
<AuthRoute
exact
path="/posts/:postId"
component={PostDetailContainer}
authenticated={authenticated}
/>
</Switch>
</div>
</Router>
</div>
);
}
const mapStateToProps = (state) => ({
user: state.user,
});
const mapDispatchToProps = {
clearUserData,
setAuthentication,
getUserFromToken,
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Using React.Strict will make your code run twice. Check out here for more information about this.
Your code
useCallback(setupSocket(), [token]);
Try this
useCallback(setupSocket, [token]);
If that was a typo, then I hope you already got the issue. If not and you need explanation, then here it is.
Assume you have a methods like this
function foo () {
console.log('I am foo');
return 'I am foo'; // Incase 'foo' is not void and returns something
};
Way 1: You are executing 'foo' and storing return value in 'refFoo'.
var refFoo = foo();
Way 2: You are creating reference of 'foo' as "refFoo".
var refFoo = foo;