I am currently working on a music application and am running into a memory leak error message when navigating away from the details page.
When I use the back button to navigate back to my home page I get this error message in the console.
index.js:1 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.
How do I fix this? I havn't had to deal with any code cleanup in the past while working with React.
ConcertDetails.js
import React, { useState, useEffect } from 'react';
import axios from '../utils/API';
const ConcertDetails = (Props) => {
const [details, setDetails] = useState({});
const [songs, setSongs] = useState([]);
useEffect(() => {
const url = '/concerts/' Props.match.params.id.toString() '/';
const getDetails = async () => {
const response = await axios.get(url, {}, {'Content-Type': 'application/json'});
setDetails(response.data);
setSongs(response.data.song);
};
getDetails();
});
const getSongs = () => {
return songs.map((song) => (
<li key={song.title}>{song.title}</li>
));
}
return (
<>
<h1>Concert Details</h1>
<p>Venue: {details.venue}</p>
<p>Date: {details.date}</p>
<ul>
{getSongs()}
</ul>
</>
)
}
export default ConcertDetails;
App.js
import React from 'react';
import './App.css';
import NavBar from "./components/Navbar";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Home from './pages';
import About from './pages/about';
import Contact from './pages/contact';
import SignUp from './pages/signup';
import SignIn from './pages/signin';
import ConcertDetails from './pages/concertDetails';
function App() {
return (
<Router>
<NavBar />
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
<Route path="/signin" component={SignIn} />
<Route path="/signup" component={SignUp} />
<Route path="/concert/:id" component={ConcertDetails} />
</Switch>
</Router>
);
}
export default App;
CodePudding user response:
You want to run useEffect only on first component render.
Add []
as the second param of useEffect.
useEffect(() => {
const url = '/concerts/' Props.match.params.id.toString() '/';
const getDetails = async () => {
const response = await axios.get(url, {}, {'Content-Type': 'application/json'});
setDetails(response.data);
setSongs(response.data.song);
};
getDetails();
}, []);
without the second param. the effect will run each time state / props is changed. Since the effect changes the state, this can cause an infinite loop.
CodePudding user response:
Check the plnkr: CodeSanbox: https://codesandbox.io/s/suspicious-dew-rsz5s?file=/src/App.js
The explanation:
There are multiple issues, let's solve step by step
- Your
useEffect
is running multiple times, we have to define a condition with the array of dependencies, I'll define it only runs on the mount with empty dependencies. - But also, your useEffect is trying to
setState
after an async call. So, maybe at that moment, the component is already unmounted. For that reason, you have to run asafeSetState
:). Basically, you have to validate before setState.
First, we will define our hook validation of mount/unmounted:
useSafeWrite.js:
import React from 'react';
function useSafeWrite(dispatch) {
const mounted = React.useRef(false)
React.useLayoutEffect(() => {
mounted.current = true
return () => (mounted.current = false)
}, [])
return React.useCallback(
(...args) => (mounted.current ? dispatch(...args) : void 0),
[dispatch],
)
}
export default useSafeWrite;
And after that, we will redefine the setDetails
and setSongs
with our hook and use them inside our useEffect
.
ConcertDetails
const [details, setDetails] = useState({});
const [songs, setSongs] = useState([]);
const safeSetDetails = useSafeWrite(setDetails);
const safeSetSongs = useSafeWrite(setSongs);
useEffect(() => {
const getDetails = async () => {
const url = '/concerts/' Props.match.params.id.toString() '/';
const response = await axios.get(url, {}, {'Content-Type': 'application/json'});
safeSetDetails(response.data);
safeSetSongs(response.data.song );
};
getDetails();
}, [safeSetDetails, safeSetSongs]);
CodeSanbox: https://codesandbox.io/s/suspicious-dew-rsz5s?file=/src/App.js