Custom React Hook Causing Memory Leak Error - javascript

I had created a custom hook to fetch data for a single item but for some reason its causing this error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
at PostPage (http://localhost:3000/static/js/bundle.js:2530:81)
at Routes (http://localhost:3000/static/js/bundle.js:48697:5)
at div
at Router (http://localhost:3000/static/js/bundle.js:48630:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:48110:5)
Here is the code
const useFetchMultiSingle = (mainUrl, secondaryKey, mainUrlPath, secondaryUrlPath, path) => {
const [mainUrlDataSingle, setMainUrlDataSingle] = useState(null);
const [secondaryUrlDataSingle, setSecondaryUrlDataSingle] = useState(null);
const [loadingMultiUrlSingle, setLoadingMultiUrlSingle] = useState(false);
const [errorMultiUrlSingle, setErrorMultiUrlSingle] = useState(null);
useEffect(() => {
const apiOnePromise = axios.get(`${mainUrl}/${mainUrlPath}?secondary_id=${path}`);
const apiTwoPromise = axios.get(`http://localhost:5555/${secondaryUrlPath}?id=${path}`);
setLoadingMultiUrlSingle(true);
Promise.all([apiOnePromise, apiTwoPromise])
.then(values => {
const response01 = values[0].data;
const response02 = values[1].data;
setMainUrlDataSingle(response01);
setSecondaryUrlDataSingle(response02);
})
.catch((err) => {
setErrorMultiUrlSingle(err);
})
.finally(() => {
setLoadingMultiUrlSingle(false);
})
}, [mainUrl, secondaryKey, mainUrlPath, secondaryUrlPath, path]);
const refetch = () => {
const apiOnePromise = axios.get(`${mainUrl}/${mainUrlPath}?secondary_id=${path}`);
const apiTwoPromise = axios.get(`http://localhost:5555/${secondaryUrlPath}?id=${path}`);
setLoadingMultiUrlSingle(true);
Promise.all([apiOnePromise, apiTwoPromise])
.then(values => {
const response01 = values[0].data;
const response02 = values[1].data;
setMainUrlDataSingle(response01);
setSecondaryUrlDataSingle(response02);
})
.catch((err) => {
setErrorMultiUrlSingle(err);
})
.finally(() => {
setLoadingMultiUrlSingle(false);
})
};
return { mainUrlDataSingle, secondaryUrlDataSingle, loadingMultiUrlSingle, errorMultiUrlSingle, refetch };
};
This hook fetches the data from the main url and then fetches the data from the second url based on the first api response data
Here is how i am currently calling the hook. I am just using console.log to output the api results
import React from 'react'
import { useLocation } from 'react-router-dom';
import { useFetchMultiSingle } from '../utilities/useFetchMultiUrl';
const PostPage = () => {
const location = useLocation();
const path = location.pathname.split("/")[2];
const { mainUrlDataSingle, secondaryUrlDataSingle, loadingMultiUrlSingle, errorMultiUrlSingle } = useFetchMultiSingle(process.env.REACT_APP_S_API_URL, process.env.REACT_APP_API_KEY, "posts", "post", path);
if (loadingMultiUrlSingle) return <h1>Loading</h1>
if (mainUrlDataSingle && secondaryUrlDataSingle) console.log(mainUrlDataSingle && secondaryUrlDataSingle);
if (errorMultiUrlSingle) console.log(errorMultiUrlSingle);
return (
<div>
Single Post
</div>
)
}
export default PostPage;

PROBLEM SOLVED
I don't know why but my problem had to do with the skeleton loading components that were causing the problem i had initially used a single skeleton component to load in a parent if statement which then i made it conditional with each of its respective actual component it had nothing to do with the hook itself.
Here is what i initially did
if (isLoading) return <Skeleton/>
return (
<Router>
<Routes>
<Route exact path="/" element={<Component/>}/>
<Routes>
<Router/>
)
And here was my fix
return (
<Router>
<Routes>
<Route exact path="/" element={ isLoading ? <Skeleton/> : <Component/>}/>
<Routes>
<Router/>
)

Related

React state not updating inside custom route

I am having an issue where my react state is not updating.
I am trying to make a role-based protected route, following this tutorial style https://dev.to/iamandrewluca/private-route-in-react-router-v6-lg5, using the following component:
const MasterRoute = ({ children }) => {
const [role, setRole] = useState('');
const [isLoading, setIsLoading] = useState(false);
const checkAuth = async () => {
setIsLoading(true);
let response = await getRole();
setRole(response.role);
setIsLoading(false);
}
useEffect(() => {
checkAuth();
}, [])
useEffect(() => {
console.log(role);
}, [role])
return role === 'ADMIN' ? children : <Navigate to="/" />;
}
Logging the role in the useEffect function displays an empty result in the console.
Logging the variable response directly after the await function displays the correct response retrieved from the server.
I've tried to console log the role directly after the checkAuth() function in useEffect(), but also obtained an empty line in the console.
What could be the problem?
This component is used as the following in App.js file:
<Route
element={
<MasterRoute>
<Dashboard child={<Admin />}></Dashboard>
</MasterRoute>
}
path={'/roles'}
></Route>
Issue
It seems the general problem is that the initial role state is '', and since '' === 'ADMIN' evaluates false the <Navigate to="/" /> is rendered and the route changes. In other words, the route changed and MasterRoute likely isn't being rendered when the checkAuth call completes.
Solution
You could use that isLoading state to conditionally render null or some loading indicator while the auth/role status us checked. You'll want MasterRoute to mount with isLoading initially true so no routing/navigation action is taken on the initial render cycle.
Example:
const MasterRoute = ({ children }) => {
const [role, setRole] = useState('');
const [isLoading, setIsLoading] = useState(true); // <-- initially true
const checkAuth = async () => {
setIsLoading(true);
let response = await getRole();
setRole(response.role);
setIsLoading(false);
}
useEffect(() => {
checkAuth();
}, []);
useEffect(() => {
console.log(role);
}, [role]);
if (isLoading) {
return null; // or loading indicator/spinner/etc
}
return role === 'ADMIN' ? children : <Navigate to="/" replace />;
}

Why I am getting 'Uncaught Error: Too many re-renders.' while a try to assign a const that a got from custom hook into another const using useState?

I have made a custom hook that takes url and fetches the data in json format. But when I am trying to assign the data into const users using use state, I getting the error :
'Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop'
Here is the component from where I am trying to assign:
import React, { useState } from "react";
import useFetch from "./fetchData";
import Users from "./Users";
const ASSIGN5 = () => {
const [users, setUsers] = useState();
const { data, isLoading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
setUsers(data);
return (
<div className="container">
<h1 className="">Users Management App</h1>
{isLoading && <p>Loading users...</p>}
{error && <p>{error}</p>}
<Search onHandleSearch={handleSearch} />
{users && <Users users={users} />}
</div>
);
};
export default ASSIGN5;
And here is the useFetch hook:
import React, { useEffect, useState } from "react";
const useFetch = (url) => {
const [data, setData] = useState([]);
const [isloading, setIsloading] = useState(true);
const [error, setError] = useState();
useEffect(() => {
fetch(url)
.then((res) => {
if (!res.ok) {
throw Error("Fetching unsucessful");
} else {
return res.json();
}
})
.then((data) => {
setData(data);
setIsloading(false);
setError(null);
})
.catch((error) => {
setError(error.message);
setIsloading(false);
});
}, [url]);
return { data, isloading, error };
};
export default useFetch;
But it runs fine when I use data directly without assigning but I need to because have to filter the data using functions
I am expecting that the data will assigned to the const users
Don't call state setters unconditionally in the component body or that'll trigger infinite renders.
It appears you don't need the users state at all because it's just an alias of the data array returned by your useFetch hook.
const ASSIGN5 = () => {
const { data, isLoading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
return (
<div className="container">
<h1 className="">Users Management App</h1>
{isLoading && <p>Loading users...</p>}
{error && <p>{error}</p>}
<Search onHandleSearch={handleSearch} />
{data?.length && <Users users={data} />}
</div>
);
};
If you want to rename it you can use
const { data: users, isLoading, error } = useFetch(...);
// now you can replace `data` with `users`
Search and handleSearch weren't defined but I assume those are in your actual code somewhere.
Components are typically PascalCase, so ASSIGN5 should be Assign5.

React router dom dynamic routing

I am doing a React.js project. I am retrieving dat from the Star Wars API rendering a list of films on the screen and now I am trying to route every film to its own page through react-router-dom. Unfortunately, I am not able to make it work. it crash when I try to routing dynamically.
UPDATE AFTER ANSWER OF REZA
This is the App.js:
import './App.css';
import { Route, Routes } from "react-router-dom";
import Home from './components/Home';
import ItemContainer from './components/ItemContainer';
import Navbar from './components/Navbar';
function App() {
return (
<>
<Navbar />
<Routes>
<Route exact path='/' element={<Home />} />
<Route exact path="/:movieId" element={<ItemContainer />} />
</Routes>
</>
);
}
export default App;
This is the ItemContainer:
import { useEffect, useState } from "react";
import MovieDetail from "../MovieDetail";
import { useParams } from "react-router-dom";
const ShowMovie = (movieId) => {
const [result, setResult] = useState([]);
const fetchData = async () => {
const res = await fetch("https://www.swapi.tech/api/films/");
const json = await res.json();
setResult(json.result);
}
useEffect(() => {
fetchData();
}, []);
return new Promise((res) =>
res(result.find((value) => value.properties.title === movieId)))
}
const ItemContainer = () => {
const [films, setFilms] = useState([]);
const { movieId } = useParams([]);
console.log('params movieId container', movieId)
useEffect(() => {
ShowMovie(movieId).then((value) => {
setFilms(value.properties.title)
})
}, [movieId])
return (
<MovieDetail key={films.properties.title} films={films} />
);
}
export default ItemContainer;
The console.log doesn't give anything.
UPDATE
Also, this is the whole code in sandbox.
ShowMovie is declared like a React component, but used like a utility function. You shouldn't directly invoke React function components. React functions are also to be synchronous, pure functions. ShowMovie is returning a Promise with makes it an asynchronous function.
Convert ShowMovie into a utility function, which will basically call fetch and process the JSON response.
import { useEffect, useState } from "react";
import MovieDetail from "../MovieDetail";
import { useParams } from "react-router-dom";
const showMovie = async (movieId) => {
const res = await fetch("https://www.swapi.tech/api/films/");
const json = await res.json();
const movie = json.result.find((value) => value.properties.episode_id === Number(movieId)));
if (!movie) {
throw new Error("No match found.");
}
return movie;
}
const ItemContainer = () => {
const [films, setFilms] = useState({});
const { movieId } = useParams();
useEffect(() => {
console.log('params movieId container', movieId);
showMovie(movieId)
.then((movie) => {
setFilms(movie);
})
.catch(error => {
// handle error/log it/show message/ignore/etc...
setFilms({}); // maintain state invariant of object
});
}, [movieId]);
return (
<MovieDetail key={films.properties?.title} films={films} />
);
};
export default ItemContainer;
Update
The route path should include movieId, the param you are accessing in ItemContainer. The sub-path "film" should match what you link from in Home. In Home ensure you link to the /"film/...." path.
<Routes>
<Route path="/films" element={<Home />} />
<Route path="/film/:movieId" element={<ItemContainer />} />
<Route path="/" element={<Navigate replace to="/films" />} />
</Routes>
In ItemContainer you should be matching a movie object's episode_id property to the movieId value. Store the entire movie object into state, not just the title.
const showMovie = async (movieId) => {
const res = await fetch("https://www.swapi.tech/api/films/");
const json = await res.json();
const movie = json.result.find((value) => value.properties.episode_id === Number(movieId)));
if (!movie) {
throw new Error("No match found.");
}
return movie;
}
...
useEffect(() => {
console.log("params movieId container", movieId);
showMovie(movieId)
.then((movie) => {
setFilms(movie);
})
.catch((error) => {
// handle error/log it/show message/ignore/etc...
setFilms({}); // maintain state invariant of object
});
}, [movieId]);
You should also use Optional Chaining on the more deeply nested films prop object properties in MovieDetail, or conditionally render MovieDetail only if the films state has something to display.
const MovieDetail = ({ films }) => {
return (
<div>
<h1>{films.properties?.title}</h1>
<h3>{films.description}</h3>
</div>
);
};
Demo:
Modify App.js like this:
function App() {
return (
<>
<Navbar />
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/MovieDetail/:movieId" element={<ItemContainer />} />
</Routes>
</>
);
}

Error: Too many re-renders. React limits the number of renders to prevent an infinite loop

How do I prevent the following error:
Too many re-renders. React limits the number of renders to prevent an infinite loop.'
I just changed a class based component to functional component and its not working
My source code
import React, {Fragment, useState} from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Navbar from './Components/Layout/Navbar';
import Users from './Components/users/Users';
import User from './Components/users/User';
import Search from './Components/users/Search';
import Alert from './Components/Layout/Alert';
import About from './Components/pages/About';
import './App.css';
import Axios from 'axios';
const App = () => {
const [users, setUsers] = useState( [] );
const [user, setUser] = useState( {} );
const [repos, setRepos] = useState( [] );
const [loading, setLoading] = useState( false );
const [alert, setAlert] = useState( null );
// Search Github Users
const searchUsers = async text => {
setLoading(true);
const res = await Axios.get(`https://api.github.com/search/users?q=${text}&client_id=${process.env.REACT_APP_GITHUB_CLIENT_ID}&client_secret=${process.env.REACT_APP_GITHUB_CLIENT_SECRET}`);
setUsers(res.data.items);
setLoading(false);
};
// GEt single github user
const getUser = async username => {
setLoading(true);
const res = await Axios.get(`https://api.github.com/users/${username}?&client_id=${process.env.REACT_APP_GITHUB_CLIENT_ID}&client_secret=${process.env.REACT_APP_GITHUB_CLIENT_SECRET}`);
setUser(res.data);
setLoading(false);
};
// Get users repos
const getUserRepos = async username => {
setLoading(true);
const res = await Axios.get(`https://api.github.com/users/${username}/repos?per_page=5&sort=created:asc&client_id=${process.env.REACT_APP_GITHUB_CLIENT_ID}&client_secret=${process.env.REACT_APP_GITHUB_CLIENT_SECRET}`);
setRepos(res.data);
setLoading(false);
};
// Clear users from state
const clearUsers = () =>
setUsers([]);
setLoading(false);
// Set ALert
const showAlert = (msg, type) => {
setAlert({msg, type});
setTimeout(()=> setAlert(null),3000);
};
return (
<Router>
<div className="App">
<Navbar />
<div className="container">
<Alert alert={alert} />
<Switch>
<Route exact path='/' render={props => (
<Fragment>
<Search
searchUsers={searchUsers}
clearUsers={clearUsers}
showClear={ users.length>0? true : false }
setAlert={showAlert}
/>
<Users loading={loading} users={users} />
</Fragment>
)} />
<Route exact path = '/about' component={About} />
<Route exact path= '/user/:login' render={props => (
<User
{...props}
getUser={getUser}
getUserRepos={getUserRepos}
user={user}
repos={repos}
loading={loading} />
)} />
</Switch>
</div>
</div>
</Router>
);
}
export default App;
I just change a class based component to functional component and i get these error.
0
How do I prevent the following error:
Too many re-renders. React limits the number of renders to prevent an infinite loop.'
As from the comment,
There was error in the declaration of function clearUsers
const clearUsers = () => setUsers([]);
setLoading(false);
which should be.
const clearUsers = () => {
setUsers([]);
setLoading(false);
}
because of this small typo. The setLoading function was being called on the first render which would then call setLoading triggering react to call render again and in return call setLoading and caused the infinite renders.
I experienced the same error. I found my problem was having brackets at the end of a function(). Yet the function should have been called without brackets. Rookie error.
Before:
onClick={myFunction()}
After:
onClick={myFunction}
If you are using a button also check the caps of your 'onclick'.

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), e. g.:
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.

Categories