ComponentWillReceiveProps giving multiples calls. React - javascript

I have a big problem with the ComponentWillReceiveProps.
What happens?
I have the three components, that i pass the props from component 1 to component 2 and of the component 2 i pass to component 3, where is really used..
Basically is: I pass the props of the component 1 to use in the component 3
In the component 3, i make an ajax call with the news props using ComponentWillReceiveProps. But when i pass the props it makes multiples ajax instead just one..
I dont know why.. Can someone help me?
Component 1:
render() {
return (
<Router>
<Route exact path = "/"
render = {
(props) =>
<Overview
{ ...props}
domainStatusFiltered = {
this.state.domainStatusFiltered
}
subdomainStatusFiltered = {
this.state.subdomainStatusFiltered
}
managerStatusFiltered = {
this.state.managerStatusFiltered
}
countryStatusFiltered = {
this.state.countryStatusFiltered
}
cityStatusFiltered = {
this.state.cityStatusFiltered
}
squadNameStatusFiltered = {
this.state.squadNameStatusFiltered
}
/>} / >
)
}
Component 2:
render(){
return (
<TwoColumnGrid>
<Module className="mt-2" title="Team Status">
<TeamStatus
domainStatusFiltered={this.props.domainStatusFiltered}
subdomainStatusFiltered={this.props.subdomainStatusFiltered}
managerStatusFiltered={this.props.managerStatusFiltered}
countryStatusFiltered={this.props.countryStatusFiltered}
cityStatusFiltered={this.props.cityStatusFiltered}
squadNameStatusFiltered={this.props.squadNameStatusFiltered} />
</Module>
</TwoColumnGrid>
)
}
In the Component 3, i do a verify of the user logged and make the ajax according to your level:
componentWillReceiveProps(props) {
const firstName = localStorage.getItem('nameLoggedUser');
const lastName = localStorage.getItem('lastNameLoggedUser');
const fullName = `${firstName} ${lastName}`.toLowerCase();
const loggedUserIs = localStorage.getItem("user-role");
if (loggedUserIs === 'full') {
axios.get(`/api/squadsPeopleAll/${props.managerStatusFiltered}/${props.cityStatusFiltered}/${props.countryStatusFiltered}/${props.squadNameStatusFiltered}/${props.domainStatusFiltered}/${props.subdomainStatusFiltered}`)
.then(res => {
const getIds = res.data.map(i => i.id);
const people = Object.keys(getIds).length;
const getSquad = res.data.map(i => i.squad_name);
const unicSquads = Array.from(new Set(getSquad));
const squads = Object.keys(unicSquads).length;
this.setState({
people,
squads
})
})
.catch(err => console.log(err))
axios.get(`/api/wfmskills/${props.managerStatusFiltered}/${props.cityStatusFiltered}/${props.countryStatusFiltered}/${props.squadStatusFiltered}/${props.domainStatusFiltered}/${props.subdomainStatusFiltered}`)
.then(res => {
this.setState({
totalSkills: res.data.count,
loading: false
})
})
axios.get(`/api/notupdated/${props.managerStatusFiltered}/${props.cityStatusFiltered}/${props.countryStatusFiltered}/${props.squadStatusFiltered}/${props.domainStatusFiltered}/${props.subdomainStatusFiltered}`)
.then(res => {
this.setState({
totalNotUpdated: res.data.count
})
})
axios.get(`/api/updated2017/${props.managerStatusFiltered}/${props.cityStatusFiltered}/${props.countryStatusFiltered}/${props.squadStatusFiltered}/${props.domainStatusFiltered}/${props.subdomainStatusFiltered}`)
.then(res => {
this.setState({
updated2017: res.data.count
})
})
axios.get(`/api/updated2016/${props.managerStatusFiltered}/${props.cityStatusFiltered}/${props.countryStatusFiltered}/${props.squadStatusFiltered}/${props.domainStatusFiltered}/${props.subdomainStatusFiltered}`)
.then(res => {
this.setState({
updated2016: res.data.count
})
})
axios.get(`/api/updated2018/${props.managerStatusFiltered}/${props.cityStatusFiltered}/${props.countryStatusFiltered}/${props.squadStatusFiltered}/${props.domainStatusFiltered}/${props.subdomainStatusFiltered}`)
.then(res => {
this.setState({
updated2018: res.data.count
});
})
}
if (loggedUserIs === 'L4') {
axios.get(`/api/squadsPeopleManager/${fullName}/${this.state.cityStatusFiltered}/${this.state.countryStatusFiltered}/${this.state.squadNameStatusFiltered}/${this.state.domainStatusFiltered}/${this.state.subdomainStatusFiltered}`)
.then(res => {
console.log(res.data)
const getIds = res.data.map(i => i.id);
const people = Object.keys(getIds).length;
const getSquad = res.data.map(i => i.squad_name);
const unicSquads = Array.from(new Set(getSquad));
const squads = Object.keys(unicSquads).length;
this.setState({
people,
squads
})
})
.catch(err => console.log(err))
axios.get(`/api/wfmskills/manager/${fullName}`)
.then(res => {
this.setState({
totalSkills: res.data.count
})
})
axios.get(`/api/notupdated/manager/${fullName}/${props.cityStatusFiltered}/${props.countryStatusFiltered}/${props.squadStatusFiltered}/${props.domainStatusFiltered}/${props.subdomainStatusFiltered}`)
.then(res => {
this.setState({
totalNotUpdated: res.data.count
})
})
axios.get(`/api/updated2017/manager/${fullName}/${props.cityStatusFiltered}/${props.countryStatusFiltered}/${props.squadStatusFiltered}/${props.domainStatusFiltered}/${props.subdomainStatusFiltered}`)
.then(res => {
this.setState({
updated2017: res.data.count
})
})
axios.get(`/api/updated2016/manager/${fullName}/${props.cityStatusFiltered}/${props.countryStatusFiltered}/${props.squadStatusFiltered}/${props.domainStatusFiltered}/${props.subdomainStatusFiltered}`)
.then(res => {
this.setState({
updated2016: res.data.count
})
})
axios.get(`/api/updated2018/manager/${fullName}/${props.cityStatusFiltered}/${props.countryStatusFiltered}/${props.squadStatusFiltered}/${props.domainStatusFiltered}/${props.subdomainStatusFiltered}`)
.then(res => {
this.setState({
updated2018: res.data.count
}, () => this.setState({
loading: false
}));
})
}
}
The problem is, for example: If I give the setState only in subdomainStatusFiltered it from multiple ajax calls and returns the result several times, I did a test and put a console.log ('test') it is rendered several times, I do not know why, or I'm using the wrong method and I do not know, look that it rerender several times... I just want to call a single time when I change some state, can someone help me, please?

You can use componentDidMount()
componentDidMount() {
// here you can use your API calls with this.props
}
If still you want use componentWillReceiveProps then you can do as below:
componentWillReceiveProps(nextProps) {
if(!this.props.managerStatusFiltered && nextProps.managerStatusFiltered){
// now you can use nextProps.managerStatusFiltered
}
}

First, have you compare props with nextProps to make sure props was different? componentWillReceiveProps does not guarantee the props was different. Instead, it'll be invoked everytime your component needs to re-render.
And secondly, I think the best way to do it is moving the async calls to the top level component and passing the response data to children that need it. This way you can avoid complex state check.

If you want events to occur once and only once over the lifetime of a React component, you should use componentDidMount.
Please note that componentWillReceiveProps is a legacy lifecycle method and should not be used anymore in new projects. This method will soon only be available with the UNSAFE_ prefix when React 17 will be released. From the react documentation:
UNSAFE_componentWillReceiveProps(nextProps)
Note:
Using this lifecycle method often leads to bugs and inconsistencies,
and for that reason it is going to be deprecated in the future.
If you need to perform a side effect (for example, data fetching or an
animation) in response to a change in props, use componentDidUpdate
lifecycle instead.

Related

How to solve "can't perform a react state update on an unmounted component"

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.
in PokemonListItem (at PokemonList.jsx:148)
Okay so I know this is a common issue and the solution should be quite simple. I just don't know how to implement it to my code.
I'm making a kind of Pokédex for mobile using React-Native and PokéAPI. I'm not sure where the leak lies, so more experienced developers, please help.
PokemonListItem
export default function PokemonListItem({ url, Favorite }) {
const [pokemondata, setData] = React.useState({});
const [dataReady, setReady] = React.useState(false);
const [isFavorite, setFavorite] = React.useState(false);
const favoriteStatus = (bool) => {
setFavorite(bool);
};
const getData = async () => {
await fetch(url)
.then((res) => res.json())
.then((data) => setData(data));
setReady(true);
};
React.useEffect(() => {
getData();
}, []);
more code...
PokemonList
const renderItem = ({ item }) => (
<TouchableHighlight
style={{ borderRadius: 10 }}
underlayColor="#ffc3c2"
onPress={() => {
navigation.navigate("Pokémon Details", {
url: item.url,
});
}}
>
<PokemonListItem url={item.url} Favorite={FavoriteButton} />
</TouchableHighlight>
);
if you need to see the full code, you can visit the repository.
Try this
React.useEffect(() => {
(async function onMount() {
await fetch(url)
.then((res) => res.json())
.then((data) => setData(data));
setReady(true);
})();
}, []);
An approach seems to be to maintain a variable to see whether or not the component is still mounted or not, which feels smelly to me (React-hooks. Can't perform a React state update on an unmounted component)- but anyway this is how I would see it in your code...
let isMounted;
const getData = async () => {
await fetch(url)
.then((res) => res.json())
.then((data) => { if(isMounted) setData(data)});
setReady(true);
};
React.useEffect(() => {
isMounted = true;
getData();
return () => {
isMounted = false;
}
}, []);
Similar to what was mentioned earlier, the key point being wrapping your state update setReady() in an if (mounted){} block .
Create local variable to represent your initial mounted state let mounted = true; in your effect that has the async call
Use the cleanup effect https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect to set mounted to false return () => { mounted = false }
Wrap the setState call with if (mounted) { setState(...)}
useEffect(() => {
let mounted = true;
const apiRequest = async (setReady) => {
let response;
try {
response = await APICall();
if (mounted) {
setReady(response.data);
}
} catch (error) {}
}
apiRequest();
return () => { mounted = false;}
})
https://codesandbox.io/s/upbeat-easley-kl6fv?file=/src/App.tsx
If you remove the || true call and refresh you'll see that the error for mem leak is gone.

Rendering objects from array in React after Async function

I have an array data in my state, that contains a collection of objects, however when I try to access and render some of the information stored in the objects, none of the expected text appears on the page.
My current code for this is in my render function as follows:
<ul>
{this.state.data.map(match =>
<Link to={'teams/'+ team.homeId} key={match.id}><li>{match.homeTeam}</li></Link>
<ul>
When I check the elements in my console, I see undefined where I'm expecting my data.
The information is retrieved inside an await axios.get() request inside my async componentDidMount() function, which leads me to believe that the data may not be displaying correctly as it has not yet been retrieved when the render occurs, however I'm not sure.
I've previously tried to define let match = this.state.data || {} at the beginning of my render, so that my render would reload once the setState had taken place in my componentDidMount, however this did not work. Any directions would be appreciated.
EDIT as per MonteCristo request;
class Welcome extends Component {
constructor(props) {
super(props)
this.state = {
data: []
}
}
async componentDidMount() {
let res = await axios.get(`https://soccer.sportmonks.com/api/v2.0/livescores?api_token=${API}`)
let matchInfo = new Array(res.data.data.length).fill().map(_ => ({}))
res.data.data.forEach((id, i) => Object.assign(matchInfo[i], {id: id.id, homeScore: id.scores.localteam_score}, {awayScore: id.scores.visitorteam_score}))
res.data.data.forEach((id, i) => {
axios.get(`https://soccer.sportmonks.com/api/v2.0/teams/${id.localteam_id}?api_token=${API}`)
.then(res1 => {
Object.assign(matchInfo[i], {homeId: res1.data.data.id, homeTeam: res1.data.data.name})
})
})
res.data.data.forEach((id, i) => {
axios.get(`https://soccer.sportmonks.com/api/v2.0/teams/${id.visitorteam_id}?api_token=${API}`)
.then(res2 => {
Object.assign(matchInfo[i], {awayId: res2.data.data.id, awayTeam: res2.data.data.name})
})
})
res.data.data.forEach((id, i) => {axios.get(`https://soccer.sportmonks.com/api/v2.0/leagues/${id.league_id}?api_token=${API}`)
.then(res3 => {
Object.assign(matchInfo[i], {leagueName: res3.data.data.name})
})
})
this.setState({
data: [...this.state.data, ...matchInfo]
})
}
render() {
return (
<div>
<p className="text-xl font-semibold">Live Matches</p>
<div>
<ul>
{this.state.data.map(match =>
<Link to={'teams/'+ match.homeId} key={match.id}><li>{match.homeTeam}</li></Link>)}
</ul>
</div>
</div>
);
}
}
export default Welcome;
You need to actually wait for the requests to complete.
You're doing a this.setState({ data: [...this.state.data, ...matchInfo] }) at the bottom, but you are mutating the matchInfo objects asynchronously, so setState is occurring before all those axios requests complete. Mutating state will not cause re-renders, only setting it via setState.
If you await all your axios calls instead and build your object before calling setState, you'll get what you are looking for:
async componentDidMount() {
const soccerApi = axios.create({
baseURL: 'https://soccer.sportmonks.com/api/v2.0',
params: { api_token: API }
});
const res = await soccerApi.get(`/livescores`);
const matchInfo = await Promise.all(res.data.data.map(async row => {
const [homeTeamInfo, awayTeamInfo, leagueInfo] = await Promise.all([
soccerApi.get(`/teams/${row.localteam_id}`),
soccerApi.get(`/teams/${row.visitorteam_id}`),
soccerApi.get(`/leagues/${row.league_id}`)
]);
return {
id: row.id,
homeScore: row.scores.localteam_score,
awayScore: row.scores.visitorteam_score,
homeId: homeTeamInfo.data.data.id,
homeTeam: homeTeamInfo.data.data.name,
awayId: awayTeamInfo.data.data.id,
awayTeam: awayTeamInfo.data.data.name,
leagueName: leagueInfo.data.data.name
};
}));
this.setState({
data: [...this.state.data, ...matchInfo]
});
}
You need to check that data was retrieved successfully like this:
<ul>
{this.state.data && this.state.data.map(match => {
return (<Link to={'teams/'+ team.homeId} key={match.id}>{match.homeTeam}</Link>)
}
<ul>
Main issue is your axios calls in forEach. you're not waiting for them. you are calling them within the forEach instead of waiting.
so you'd need to do something like this
const allPromises = res.data.data.map((id, i) => { return axios.get() }
// then
const allResponseValues = await axios.all(allPromises).catch(console.error)
// do something with allResponses and update matchInfo
// finally
this.setState({...})
also update your render function to below for readability and clarity
{this.state.data.map(match => (
<Link to={"teams/" + match.homeId} key={match.id}>
<li>{match.homeTeam}</li>
</Link>
))}

how to deal with [.map is not a function]

I'm learning React Framework. When i try to fetch from my api using setState .map function dont work. It render "data.map is not a function"
This is the only Method i know:
constructor(props) {
super(props);
this.state = {
name: "secret",
email: "secret",
datas: []
};
}
componentDidMount() {
const { name, password} = this.state;
fetch(
`http://localhost:3000/api/user/account?username=${username}&email=${email}`
)
.then(res => res.json())
.then(getdata => {
this.setState({ datas: getdata });
})
.catch(err => console.log(err));
}
render() {
const { datas } = this.state;
const repoItems = datas.map(data => (
<div key={data.id}>
<p>{data.name}</p>
<p>{data.email}</p>
</div>
));
return (
<div>
{datas}
</div>
);
}
Did I wrote my code wrong or is there any other method beside from this
From the top view everything seems fine with your code. map will only run on arrays and I can see that you are setting default state to array as well. However the problem may lies when you attempt to set the state after getting response from fetch call
fetch(
`http://localhost:3000/api/user/account?username=${username}&email=${email}`
)
.then(res => res.json())
.then(getdata => {
this.setState({ datas: getdata });
})
.catch(err => console.log(err));
Here you need to make sure that whatever you are setting data, it should be an array. So in case you are not getting the array in the response set the value which is actually an array and in case the response is empty set an empty array in state instead, something like below
// may be array is a property of response
this.setState({ datas: getdata.array });
// may be getdata is empty
this.setState({ datas: getdata || [] });

React useEffect causing: Can't perform a React state update on an unmounted component

When fetching data I'm getting: Can't perform a React state update on an unmounted component. The app still works, but react is suggesting I might be causing a memory leak.
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."
Why do I keep getting this warning?
I tried researching these solutions:
https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
but this still was giving me the warning.
const ArtistProfile = props => {
const [artistData, setArtistData] = useState(null)
const token = props.spotifyAPI.user_token
const fetchData = () => {
const id = window.location.pathname.split("/").pop()
console.log(id)
props.spotifyAPI.getArtistProfile(id, ["album"], "US", 10)
.then(data => {setArtistData(data)})
}
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [])
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image={album.images[0].url}
name={album.name}
artists={album.artists}
key={album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
}
Edit:
In my api file I added an AbortController() and used a signal so I can cancel a request.
export function spotifyAPI() {
const controller = new AbortController()
const signal = controller.signal
// code ...
this.getArtist = (id) => {
return (
fetch(
`https://api.spotify.com/v1/artists/${id}`, {
headers: {"Authorization": "Bearer " + this.user_token}
}, {signal})
.then(response => {
return checkServerStat(response.status, response.json())
})
)
}
// code ...
// this is my cancel method
this.cancelRequest = () => controller.abort()
}
My spotify.getArtistProfile() looks like this
this.getArtistProfile = (id,includeGroups,market,limit,offset) => {
return Promise.all([
this.getArtist(id),
this.getArtistAlbums(id,includeGroups,market,limit,offset),
this.getArtistTopTracks(id,market)
])
.then(response => {
return ({
artist: response[0],
artistAlbums: response[1],
artistTopTracks: response[2]
})
})
}
but because my signal is used for individual api calls that are resolved in a Promise.all I can't abort() that promise so I will always be setting the state.
For me, clean the state in the unmount of the component helped.
const [state, setState] = useState({});
useEffect(() => {
myFunction();
return () => {
setState({}); // This worked for me
};
}, []);
const myFunction = () => {
setState({
name: 'Jhon',
surname: 'Doe',
})
}
Sharing the AbortController between the fetch() requests is the right approach.
When any of the Promises are aborted, Promise.all() will reject with AbortError:
function Component(props) {
const [fetched, setFetched] = React.useState(false);
React.useEffect(() => {
const ac = new AbortController();
Promise.all([
fetch('http://placekitten.com/1000/1000', {signal: ac.signal}),
fetch('http://placekitten.com/2000/2000', {signal: ac.signal})
]).then(() => setFetched(true))
.catch(ex => console.error(ex));
return () => ac.abort(); // Abort both fetches on unmount
}, []);
return fetched;
}
const main = document.querySelector('main');
ReactDOM.render(React.createElement(Component), main);
setTimeout(() => ReactDOM.unmountComponentAtNode(main), 1); // Unmount after 1ms
<script src="//cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.development.js"></script>
<main></main>
For example, you have some component that does some asynchronous actions, then writes the result to state and displays the state content on a page:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
// ...
useEffect( () => {
(async () => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it takes some time
// When request is finished:
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
})();
}, []);
return (
<div className={loading ? "loading" : ""}>
{someData}
<Link to="SOME_LOCAL_LINK">Go away from here!</Link>
</div>
);
}
Let's say that user clicks some link when doVeryLongRequest() still executes. MyComponent is unmounted but the request is still alive and when it gets a response it tries to set state in lines (1) and (2) and tries to change the appropriate nodes in HTML. We'll get an error from subject.
We can fix it by checking whether compponent is still mounted or not. Let's create a componentMounted ref (line (3) below) and set it true. When component is unmounted we'll set it to false (line (4) below). And let's check the componentMounted variable every time we try to set state (line (5) below).
The code with fixes:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
const componentMounted = useRef(true); // (3) component is mounted
// ...
useEffect( () => {
(async () => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it takes some time
// When request is finished:
if (componentMounted.current){ // (5) is component still mounted?
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
}
return () => { // This code runs when component is unmounted
componentMounted.current = false; // (4) set it to false when we leave the page
}
})();
}, []);
return (
<div className={loading ? "loading" : ""}>
{someData}
<Link to="SOME_LOCAL_LINK">Go away from here!</Link>
</div>
);
}
Why do I keep getting this warning?
The intention of this warning is to help you prevent memory leaks in your application. If the component updates it's state after it has been unmounted from the DOM, this is an indication that there could be a memory leak, but it is an indication with a lot of false positives.
How do I know if I have a memory leak?
You have a memory leak if an object that lives longer than your component holds a reference to it, either directly or indirectly. This usually happens when you subscribe to events or changes of some kind without unsubscribing when your component unmounts from the DOM.
It typically looks like this:
useEffect(() => {
function handleChange() {
setState(store.getState())
}
// "store" lives longer than the component,
// and will hold a reference to the handleChange function.
// Preventing the component to be garbage collected after
// unmount.
store.subscribe(handleChange)
// Uncomment the line below to avoid memory leak in your component
// return () => store.unsubscribe(handleChange)
}, [])
Where store is an object that lives further up the React tree (possibly in a context provider), or in global/module scope. Another example is subscribing to events:
useEffect(() => {
function handleScroll() {
setState(window.scrollY)
}
// document is an object in global scope, and will hold a reference
// to the handleScroll function, preventing garbage collection
document.addEventListener('scroll', handleScroll)
// Uncomment the line below to avoid memory leak in your component
// return () => document.removeEventListener(handleScroll)
}, [])
Another example worth remembering is the web API setInterval, which can also cause memory leak if you forget to call clearInterval when unmounting.
But that is not what I am doing, why should I care about this warning?
React's strategy to warn whenever state updates happen after your component has unmounted creates a lot of false positives. The most common I've seen is by setting state after an asynchronous network request:
async function handleSubmit() {
setPending(true)
await post('/someapi') // component might unmount while we're waiting
setPending(false)
}
You could technically argue that this also is a memory leak, since the component isn't released immediately after it is no longer needed. If your "post" takes a long time to complete, then it will take a long time to for the memory to be released. However, this is not something you should worry about, because it will be garbage collected eventually. In these cases, you could simply ignore the warning.
But it is so annoying to see the warning, how do I remove it?
There are a lot of blogs and answers on stackoverflow suggesting to keep track of the mounted state of your component and wrap your state updates in an if-statement:
let isMountedRef = useRef(false)
useEffect(() => {
isMountedRef.current = true
return () => {
isMountedRef.current = false
}
}, [])
async function handleSubmit() {
setPending(true)
await post('/someapi')
if (!isMountedRef.current) {
setPending(false)
}
}
This is not an recommended approach! Not only does it make the code less readable and adds runtime overhead, but it might also might not work well with future features of React. It also does nothing at all about the "memory leak", the component will still live just as long as without that extra code.
The recommended way to deal with this is to either cancel the asynchronous function (with for instance the AbortController API), or to ignore it.
In fact, React dev team recognises the fact that avoiding false positives is too difficult, and has removed the warning in v18 of React.
You can try this set a state like this and check if your component mounted or not. This way you are sure that if your component is unmounted you are not trying to fetch something.
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
return () => setDidMount(false);
}, [])
if(!didMount) {
return null;
}
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image={album.images[0].url}
name={album.name}
artists={album.artists}
key={album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
Hope this will help you.
I had a similar issue with a scroll to top and #CalosVallejo answer solved it :) Thank you so much!!
const ScrollToTop = () => {
const [showScroll, setShowScroll] = useState();
//------------------ solution
useEffect(() => {
checkScrollTop();
return () => {
setShowScroll({}); // This worked for me
};
}, []);
//----------------- solution
const checkScrollTop = () => {
setShowScroll(true);
};
const scrollTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
window.addEventListener("scroll", checkScrollTop);
return (
<React.Fragment>
<div className="back-to-top">
<h1
className="scrollTop"
onClick={scrollTop}
style={{ display: showScroll }}
>
{" "}
Back to top <span>⟶ </span>
</h1>
</div>
</React.Fragment>
);
};
I have getting same warning, This solution Worked for me ->
useEffect(() => {
const unsubscribe = fetchData(); //subscribe
return unsubscribe; //unsubscribe
}, []);
if you have more then one fetch function then
const getData = () => {
fetch1();
fetch2();
fetch3();
}
useEffect(() => {
const unsubscribe = getData(); //subscribe
return unsubscribe; //unsubscribe
}, []);
This error occurs when u perform state update on current component after navigating to other component:
for example
axios
.post(API.BASE_URI + API.LOGIN, { email: username, password: password })
.then((res) => {
if (res.status === 200) {
dispatch(login(res.data.data)); // line#5 logging user in
setSigningIn(false); // line#6 updating some state
} else {
setSigningIn(false);
ToastAndroid.show(
"Email or Password is not correct!",
ToastAndroid.LONG
);
}
})
In above case on line#5 I'm dispatching login action which in return navigates user to the dashboard and hence login screen now gets unmounted.
Now when React Native reaches as line#6 and see there is state being updated, it yells out loud that how do I do this, the login component is there no more.
Solution:
axios
.post(API.BASE_URI + API.LOGIN, { email: username, password: password })
.then((res) => {
if (res.status === 200) {
setSigningIn(false); // line#6 updating some state -- moved this line up
dispatch(login(res.data.data)); // line#5 logging user in
} else {
setSigningIn(false);
ToastAndroid.show(
"Email or Password is not correct!",
ToastAndroid.LONG
);
}
})
Just move react state update above, move line 6 up the line 5.
Now state is being updated before navigating the user away. WIN WIN
there are many answers but I thought I could demonstrate more simply how the abort works (at least how it fixed the issue for me):
useEffect(() => {
// get abortion variables
let abortController = new AbortController();
let aborted = abortController.signal.aborted; // true || false
async function fetchResults() {
let response = await fetch(`[WEBSITE LINK]`);
let data = await response.json();
aborted = abortController.signal.aborted; // before 'if' statement check again if aborted
if (aborted === false) {
// All your 'set states' inside this kind of 'if' statement
setState(data);
}
}
fetchResults();
return () => {
abortController.abort();
};
}, [])
Other Methods:
https://medium.com/wesionary-team/how-to-fix-memory-leak-issue-in-react-js-using-hook-a5ecbf9becf8
If the user navigates away, or something else causes the component to get destroyed before the async call comes back and tries to setState on it, it will cause the error. It's generally harmless if it is, indeed, a late-finish async call. There's a couple of ways to silence the error.
If you're implementing a hook like useAsync you can declare your useStates with let instead of const, and, in the destructor returned by useEffect, set the setState function(s) to a no-op function.
export function useAsync<T, F extends IUseAsyncGettor<T>>(gettor: F, ...rest: Parameters<F>): IUseAsync<T> {
let [parameters, setParameters] = useState(rest);
if (parameters !== rest && parameters.some((_, i) => parameters[i] !== rest[i]))
setParameters(rest);
const refresh: () => void = useCallback(() => {
const promise: Promise<T | void> = gettor
.apply(null, parameters)
.then(value => setTuple([value, { isLoading: false, promise, refresh, error: undefined }]))
.catch(error => setTuple([undefined, { isLoading: false, promise, refresh, error }]));
setTuple([undefined, { isLoading: true, promise, refresh, error: undefined }]);
return promise;
}, [gettor, parameters]);
useEffect(() => {
refresh();
// and for when async finishes after user navs away //////////
return () => { setTuple = setParameters = (() => undefined) }
}, [refresh]);
let [tuple, setTuple] = useState<IUseAsync<T>>([undefined, { isLoading: true, refresh, promise: Promise.resolve() }]);
return tuple;
}
That won't work well in a component, though. There, you can wrap useState in a function which tracks mounted/unmounted, and wraps the returned setState function with the if-check.
export const MyComponent = () => {
const [numPendingPromises, setNumPendingPromises] = useUnlessUnmounted(useState(0));
// ..etc.
// imported from elsewhere ////
export function useUnlessUnmounted<T>(useStateTuple: [val: T, setVal: Dispatch<SetStateAction<T>>]): [T, Dispatch<SetStateAction<T>>] {
const [val, setVal] = useStateTuple;
const [isMounted, setIsMounted] = useState(true);
useEffect(() => () => setIsMounted(false), []);
return [val, newVal => (isMounted ? setVal(newVal) : () => void 0)];
}
You could then create a useStateAsync hook to streamline a bit.
export function useStateAsync<T>(initialState: T | (() => T)): [T, Dispatch<SetStateAction<T>>] {
return useUnlessUnmounted(useState(initialState));
}
Try to add the dependencies in useEffect:
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [fetchData, props.spotifyAPI])
Usually this problem occurs when you showing the component conditionally, for example:
showModal && <Modal onClose={toggleModal}/>
You can try to do some little tricks in the Modal onClose function, like
setTimeout(onClose, 0)
This works for me :')
const [state, setState] = useState({});
useEffect( async ()=>{
let data= await props.data; // data from API too
setState(users);
},[props.data]);
I had this problem in React Native iOS and fixed it by moving my setState call into a catch. See below:
Bad code (caused the error):
const signupHandler = async (email, password) => {
setLoading(true)
try {
const token = await createUser(email, password)
authContext.authenticate(token)
} catch (error) {
Alert.alert('Error', 'Could not create user.')
}
setLoading(false) // this line was OUTSIDE the catch call and triggered an error!
}
Good code (no error):
const signupHandler = async (email, password) => {
setLoading(true)
try {
const token = await createUser(email, password)
authContext.authenticate(token)
} catch (error) {
Alert.alert('Error', 'Could not create user.')
setLoading(false) // moving this line INTO the catch call resolved the error!
}
}
Similar problem with my app, I use a useEffect to fetch some data, and then update a state with that:
useEffect(() => {
const fetchUser = async() => {
const {
data: {
queryUser
},
} = await authFetch.get(`/auth/getUser?userId=${createdBy}`);
setBlogUser(queryUser);
};
fetchUser();
return () => {
setBlogUser(null);
};
}, [_id]);
This improves upon Carlos Vallejo's answer.
useEffect(() => {
let abortController = new AbortController();
// your async action is here
return () => {
abortController.abort();
}
}, []);
in the above code, I've used AbortController to unsubscribe the effect. When the a sync action is completed, then I abort the controller and unsubscribe the effect.
it work for me ....
The easy way
let fetchingFunction= async()=>{
// fetching
}
React.useEffect(() => {
fetchingFunction();
return () => {
fetchingFunction= null
}
}, [])
options={{
filterType: "checkbox"
,
textLabels: {
body: {
noMatch: isLoading ?
:
'Sorry, there is no matching data to display',
},
},
}}

React .map is not a function

I'm trying to learn React and I'm a beginner when it comes to Javascript. Right now I'm working on an app that is fetching data from Flickr's API. The problem is that when I try to use the map method on the props in the Main.js component I get an error saying "Uncaught TypeError: this.props.photos.map is not a function". After searching here on Stackoverflow I think the problem is that this.props are javascript objects and not an array. The problem is that I can't figure out how to make it an array. Can anyone explain what I'm doing wrong?
My code:
class App extends Component {
constructor() {
super();
this.state = {
}
}
componentDidMount() {
let apiKey = 'xxxxxxxxxxxxxxxxxx';
let searchKeyword = 'nature';
let url = `https://api.flickr.com/services/
rest/?api_key=${apiKey}&method=flickr.photos.
search&format=json&nojsoncallback=1&&per_page=50
&page=1&text=${searchKeyword}`;
fetch(url)
.then(response => response.json())
.then(data => data.photos.photo.map((x) => {
this.setState({
farm: x.farm,
id: x.id,
secret: x.secret,
server: x.server})
// console.log(this.state)
}))
}
render() {
return (
<div className="App">
<Header />
<Main img={this.state.photos} />
<Navigation />
</div>
);
}
}
export default class Main extends Component {
render() {
return(
<main className="main">
{console.log(this.props.photos)}
</main>
)
}
}
Edit:
Why is this.props.img undefined first?
Screen shot from console.log(this.props.img)
fetch(url)
.then(response => response.json())
.then(data => data.photos.photo.map((x) => {
this.setState({
farm: x.farm,
id: x.id,
secret: x.secret,
server: x.server})
}))
What is happening is that your map function in your promise is resetting the component's state for every photo that is returned. So your state will always be the last object in your list of returned photos.
Here is a more simplified example of what I am referring to
const testArray = [1,2,3,4];
let currentState;
testArray.map((value) => currentState = value)
console.log(currentState);
What you want to do is this
const testArray = [1,2,3,4];
let currentState;
//Notice we are using the return value of the map function itself.
currentState = testArray.map((value) => value)
console.log(currentState);
For what you are trying to accomplish, you want your state to be the result of the map function (since that returns an array of your results from the map). Something like this:
fetch(url)
.then(response => response.json())
.then(data =>
this.setState({
photos:
data.photos.photo.map((x) => ({
farm: x.farm,
id: x.id,
secret: x.secret,
server: x.server
}))
})
)
This error might also happen if you try to provide something else other than the array that .map() is expecting, even if you declare the variable type properly. A hook-based example:
const [myTwinkies, setMyTwinkies] = useState<Twinkies[]>([]);
useEffect(() => {
// add a twinky if none are left in 7eleven
// setMyTwinkies(twinkiesAt711 ?? {}); // ---> CAUSES ".map is not a function"
setMyTwinkies(twinkiesAt711 ?? [{}]);
}, [twinkiesAt711, setMyTwinkies]);
return (<ul>
{myTwinkies.map((twinky, i)=> (
<li key={i}>Twinky {i}: {twinky?.expiryDate}</li>
))}
</ul>)
Just check the length of the array before going for the map. If the len is more than 0 then go for it, otherwise, ignore it.
data.photos.photo.map.length>0 && data.photos.photo.map(........)

Categories