Stop requesting when changing the route in ReactJs - javascript

I make an request in react js and i get data using useSelector. So in useEffect i show the message from backend after the request.
useEffect(() => {
if (selector.response ? .message) {
console.log(selector);
message.success(selector.response?.message, 2);
setLoading(false);
}
console.log('render');
}, [selector.response]);
The code works fine, but appears an issue when i change the page(route). Clicking on another menu item i go to another page and when i come back, the useEffect is triggered again and user again sees the message from message.success(selector.response?.message, 2);. Question: How to stop showing the message each time after i come back to my route and to show the message just one time?

You have to use User Effects with Cleanup
You need to clean up the effects from the previous render or actions. Otherwise it will hold the previous state
You will find more details in official tutorial
userEffect Cleanup Process
Update Answer
import React, {useState, useEffect} from "react";
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
function handleChange(val) {
setCount(val)
}
window.addEventListener('load', handleChange(1))
return () =>{
handleChange(0);
console.log(count)
};
})
return (
<div className="App">
{count}
</div>
);
}
Here I take a count variable and setCount methods to set the count variable
Inside useEffect I create a function which will be responsible for setting up count value
Then I create an addEventListner when the page will load it will set the count value to 1
Then I call an anonymous function for clean up things which will remove the previously set value to 0
After that I set a console to just check if its sets the value
So when user come back to your page again initially it will find the default value then set the dynamic value.
You can make it more efficient way. I just give you an example
Below I share a link which will help you to handle api response
How to call api and cleanup response using react usereffect hooks

It seems like in your case your component unmounts upon page change and remounts when the user comes back. When the componet remounts, the useEffect always fires. That's simply how it works.
Answering your question, this is a simple thing you can do for your case.
// this will create a function that will return true only once
const createShouldShowSuccessMessage = () => {
let hasShownMessage = false;
()=> {
if(hasShownMessage) {
return false;
} else {
hasShownMessage = true;
return true;
}
}
};
const shouldShowSuccessMessage = createShouldShowSuccessMessage();
const SomeComponent = () => {
useEffect(() => {
if(shouldShowSuccessMessage()) {
message.success(selector.response?.message, 2);
}
setLoading(false);
}, [selector.response]);
}
I would advise you got with a better setup that performing your side effects inside your components but I hope my answer helps you till you get there

Related

Why does useEffect trigger in first time render even though i gave it dependency arrays? [duplicate]

With React's new Effect Hooks, I can tell React to skip applying an effect if certain values haven't changed between re-renders - Example from React's docs:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
But the example above applies the effect upon initial render, and upon subsequent re-renders where count has changed. How can I tell React to skip the effect on the initial render?
As the guide states,
The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.
In this example from the guide it's expected that count is 0 only on initial render:
const [count, setCount] = useState(0);
So it will work as componentDidUpdate with additional check:
useEffect(() => {
if (count)
document.title = `You clicked ${count} times`;
}, [count]);
This is basically how custom hook that can be used instead of useEffect may work:
function useDidUpdateEffect(fn, inputs) {
const didMountRef = useRef(false);
useEffect(() => {
if (didMountRef.current) {
return fn();
}
didMountRef.current = true;
}, inputs);
}
Credits go to #Tholle for suggesting useRef instead of setState.
Here's a custom hook that just provides a boolean flag to indicate whether the current render is the first render (when the component was mounted). It's about the same as some of the other answers but you can use the flag in a useEffect or the render function or anywhere else in the component you want. Maybe someone can propose a better name.
import { useRef, useEffect } from 'react';
export const useIsMount = () => {
const isMountRef = useRef(true);
useEffect(() => {
isMountRef.current = false;
}, []);
return isMountRef.current;
};
You can use it like:
import React, { useEffect } from 'react';
import { useIsMount } from './useIsMount';
const MyComponent = () => {
const isMount = useIsMount();
useEffect(() => {
if (isMount) {
console.log('First Render');
} else {
console.log('Subsequent Render');
}
});
return isMount ? <p>First Render</p> : <p>Subsequent Render</p>;
};
And here's a test for it if you're interested:
import { renderHook } from '#testing-library/react-hooks';
import { useIsMount } from '../useIsMount';
describe('useIsMount', () => {
it('should be true on first render and false after', () => {
const { result, rerender } = renderHook(() => useIsMount());
expect(result.current).toEqual(true);
rerender();
expect(result.current).toEqual(false);
rerender();
expect(result.current).toEqual(false);
});
});
Our use case was to hide animated elements if the initial props indicate they should be hidden. On later renders if the props changed, we did want the elements to animate out.
I found a solution that is more simple and has no need to use another hook, but it has drawbacks.
useEffect(() => {
// skip initial render
return () => {
// do something with dependency
}
}, [dependency])
This is just an example that there are others ways of doing it if your case is very simple.
The drawback of doing this is that you can't have a cleanup effect and will only execute when the dependency array changes the second time.
This isn't recommended to use and you should use what the other answers are saying, but I only added this here so people know that there is more than one way of doing this.
Edit:
Just to make it more clear, you shouldn't use this approach to solving the problem in the question (skipping the initial render), this is only for teaching purpose that shows you can do the same thing in different ways.
If you need to skip the initial render, please use the approach on other answers.
I use a regular state variable instead of a ref.
// Initializing didMount as false
const [didMount, setDidMount] = useState(false)
// Setting didMount to true upon mounting
useEffect(() => { setDidMount(true) }, [])
// Now that we have a variable that tells us wether or not the component has
// mounted we can change the behavior of the other effect based on that
const [count, setCount] = useState(0)
useEffect(() => {
if (didMount) document.title = `You clicked ${count} times`
}, [count])
We can refactor the didMount logic as a custom hook like this.
function useDidMount() {
const [didMount, setDidMount] = useState(false)
useEffect(() => { setDidMount(true) }, [])
return didMount
}
Finally, we can use it in our component like this.
const didMount = useDidMount()
const [count, setCount] = useState(0)
useEffect(() => {
if (didMount) document.title = `You clicked ${count} times`
}, [count])
UPDATE Using useRef hook to avoid the extra rerender (Thanks to #TomEsterez for the suggestion)
This time our custom hook returns a function returning our ref's current value. U can use the ref directly too, but I like this better.
function useDidMount() {
const mountRef = useRef(false);
useEffect(() => { mountRef.current = true }, []);
return () => mountRef.current;
}
Usage
const MyComponent = () => {
const didMount = useDidMount();
useEffect(() => {
if (didMount()) // do something
else // do something else
})
return (
<div>something</div>
);
}
On a side note, I've never had to use this hook and there are probably better ways to handle this which would be more aligned with the React programming model.
Let me introduce to you react-use.
npm install react-use
Wanna run:
only after first render? -------> useUpdateEffect
only once? -------> useEffectOnce
check is it first mount? -------> useFirstMountState
Want to run effect with deep compare, shallow compare or throttle? and much more here.
Don't want to install a library? Check the code & copy. (maybe a star for the good folks there too)
Best thing is one less thing for you to maintain.
A TypeScript and CRA friendly hook, replace it with useEffect, this hook works like useEffect but won't be triggered while the first render happens.
import * as React from 'react'
export const useLazyEffect:typeof React.useEffect = (cb, dep) => {
const initializeRef = React.useRef<boolean>(false)
React.useEffect((...args) => {
if (initializeRef.current) {
cb(...args)
} else {
initializeRef.current = true
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dep)
}
Here is my implementation based on Estus Flask's answer written in Typescript. It also supports cleanup callback.
import { DependencyList, EffectCallback, useEffect, useRef } from 'react';
export function useDidUpdateEffect(
effect: EffectCallback,
deps?: DependencyList
) {
// a flag to check if the component did mount (first render's passed)
// it's unrelated to the rendering process so we don't useState here
const didMountRef = useRef(false);
// effect callback runs when the dependency array changes, it also runs
// after the component mounted for the first time.
useEffect(() => {
// if so, mark the component as mounted and skip the first effect call
if (!didMountRef.current) {
didMountRef.current = true;
} else {
// subsequent useEffect callback invocations will execute the effect as normal
return effect();
}
}, deps);
}
Live Demo
The live demo below demonstrates the different between useEffect and useDidUpdateEffect hooks
I was going to comment on the currently accepted answer, but ran out of space!
Firstly, it's important to move away from thinking in terms of lifecycle events when using functional components. Think in terms of prop/state changes. I had a similar situation where I only wanted a particular useEffect function to fire when a particular prop (parentValue in my case) changes from its initial state. So, I created a ref that was based on its initial value:
const parentValueRef = useRef(parentValue);
and then included the following at the start of the useEffect fn:
if (parentValue === parentValueRef.current) return;
parentValueRef.current = parentValue;
(Basically, don't run the effect if parentValue hasn't changed. Update the ref if it has changed, ready for the next check, and continue to run the effect)
So, although other solutions suggested will solve the particular use-case you've provided, it will help in the long run to change how you think in relation to functional components.
Think of them as primarily rendering a component based on some props.
If you genuinely need some local state, then useState will provide that, but don't assume your problem will be solved by storing local state.
If you have some code that will alter your props during a render, this 'side-effect' needs to be wrapped in a useEffect, but the purpose of this is to have a clean render that isn't affected by something changing as it's rendering. The useEffect hook will be run after the render has completed and, as you've pointed out, it's run with every render - unless the second parameter is used to supply a list of props/states to identify what changed items will cause it to be run subsequent times.
Good luck on your journey to Functional Components / Hooks! Sometimes it's necessary to unlearn something to get to grips with a new way of doing things :)
This is an excellent primer: https://overreacted.io/a-complete-guide-to-useeffect/
Below solution is similar to above, just a little cleaner way i prefer.
const [isMount, setIsMount] = useState(true);
useEffect(()=>{
if(isMount){
setIsMount(false);
return;
}
//Do anything here for 2nd render onwards
}, [args])
You can use custom hook to run use effect after mount.
const useEffectAfterMount = (cb, dependencies) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Here is the typescript version:
const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Example:
useEffectAfterMount(() => {
document.title = `You clicked ${count} times`;
}, [count])

Can't update parent state from child using functional components

I am having an issue with my React app. I am trying to set the state of the parent component based on the child component's value. I can see in the dev tools and log window that the child's value is being received by the parent; however, the setState is not working as it should. I have tried creating a separate function just to set the values; hoping for it to act as a middleware but no luck.
I have been through about a couple of StackOverflow threads but not many cater for functional components. I found the following codegrepper snippet for reference but it does not help either.
link: https://www.codegrepper.com/code-examples/javascript/react+function+component+state
Most of the threads deal with how to get the value to the parent component; however, my issue is more "setting the state" specific.
import React, { useEffect, useState } from "react";
import Character from "../component/Character";
import Filter from "../component/Filter";
import Pagination from "../component/Pagination";
import axios from "axios";
import "./Home.css";
const Home = (props) => {
const [API, setAPI] = useState(`https://someapi.com/api/character/?gender=&status=&name=`);
const [characterData, setCharacterData] = useState([]);
const [pagination, setPagination] = useState(0);
const makeNetworkRequest = (data) => {
setAPI(data);
setTimeout(() => {
axios.get(data).then(resp => {
setPagination(resp.data.info)
setCharacterData(resp.data.results)
})
}, 1000)
}
const handleFormCallBack = (childData) => {
setAPI(childData);
makeNetworkRequest(API);
console.log(`Parent handler data ${childData}`)
console.log(`Parent handler API ${API}`)
}
useEffect(() => {
makeNetworkRequest(API)
}, [characterData.length]);
const mappedCharacters = characterData.length > 0 ? characterData.map((character) => <Character key={character.id} id={character.id} alive={character.status} /* status={<DeadOrAlive deadoralive={character.status} /> }*/ gender={character.gender} name={character.name} image={character.image} />) : <h4>Loading...</h4>
return (
<div className="home-container">
<h3>Home</h3>
<Filter parentCallBack={handleFormCallBack} />
<div className="characters-container">
{mappedCharacters}
</div>
{/* <Pagination pages={pagination.pages}/> */}
</div>
)
}
export default Home;
In the code above I am using a callback function on the parent named "handleFormCallBack", mentioned again below to get the information from the child filter component. When I log the value, the following results are being generated.
const handleFormCallBack = (childData) => {
setAPI(childData);
makeNetworkRequest(API);
console.log(`Parent handler data ${childData}`)
// Parent handler data https://someapi.com/api/character/?gender=&status=&name=charactername
console.log(`Parent handler API ${API}`)
// Parent handler API https://someapi.com/api/character/?gender=&status=&name=
}
I am not sure what I am doing wrong but any sort of help would be much appreciated.
Kind Regards
useState works pretty much like setState and it is not synchronous, so when you set the new value using setAPI(childData); react is still changing the state and before it actually does so both of your console.log() statements are being executed.
Solution - after setting the new value you need to track if it has changed, so use a useEffect hook for the endpoint url and then when it changes do what you want.
useEffect(() =< {
// do anything you want to here when the API value changes. you can also add if conditions inside here.
}, [API])
Just to check what I have explained, after calling setAPI(childData); add a setTimeout like
setTimeout(() => {
// you will get new values here. this is just to make my point clear
console.log(Parent handler data ${childData})
console.log(Parent handler API ${API})
}, 5000);

useEffect causing it to call the method to get posts way too many times. I only want to get the posts when my query changes

I am trying to call the reddit API. The post titles are showing up, but I want them to rerender when my query changes. I just want to know how to call a method when a piece of my state changes(aka my query). I’m using useEffect from react to do it but that calls it whenever anything changes in the component, causing it to call the method to get posts way to many times. I only want to get the posts when my query changes.
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
function Results()
{
const query = useSelector(state => state.query);
const results = useSelector(state => state.results);
const dispatch = useDispatch();
let fetchResults = () =>
{
let postTitles = [];
let postSrcs = [];
fetch('https://www.reddit.com/r/' + query + '.json')
.then(response => response.json())
.then(body => {
for (let i = 0; i < body.data.children.length; ++i) {
if (body.data.children[i].data.post_hint === 'image')
{
let img_url = body.data.children[i].data.url_overridden_by_dest;
postSrcs.push(img_url);
}
let title = body.data.children[i].data.title;
postTitles.push(title);
}
dispatch({type: "QUERY_RESULTS", payload: postTitles})
}).catch((err) => {
console.log(err)
});
}
useEffect(() => {
fetchResults();
console.log("use effect triggered")
})
return (
<>
<h1>Query: {query}</h1>
{ !results
? <h1>No Results</h1>
: results.map(p => <h6> {p} </h6>)
}
</>
)
}
export default Results;
For example in the console log that tells me when use effect is triggered. and when i search for a post the use effect triggered is stacking up.
useEffect has a differents mode. You can check how to use in official document https://reactjs.org/docs/hooks-reference.html#useeffect
So the main you must know is 3 things
useEffect is the last render in react. So first render a components and read other code when it finish useEffect run.
useEffect may run code only one time adding []. for example
useEffect ( () => {
...code
}, [])
This code will run only one time.
useEffect may run code watching variables adding variables into []. For example
useEffect ( () => {
...code
}, [ count, name , ... ])
This code will run first time and later would run if count or name change
To achieve that you need to prevent useEffect to be called on any changes, and only once the query changes.
NOTE: Since you're using dispatch within fetchResults, it's better to make sure that dispatch is ready before calling fetchResults.
Your useEffect may look like the following to achieve that:
useEffect(() => {
// To prevent call fetchResults if dispatch only is changed
if (query) {
fetchResults();
console.log("use effect triggered");
}
}, [dispatch, query]);
Hooks like useEffect are used in function components. The Class component comparison to useEffect are the methods componentDidMount, componentDidUpdate, and componentWillUnmount.
useEffect will run when the component renders, which might be more times than you think.
So useEffect takes a second parameter
The second param is an array of variables that the component will check to make sure changed before re-rendering. You could put whatever bits of props and state you want in here to check against.
In your case add [query] as a second para:
useEffect(() => {
fetchResults();
console.log("use effect triggered")
},[query])
https://css-tricks.com/run-useeffect-only-once/

How to prevent state updates from a function, running an async call

so i have a bit of a weird problem i dont know how to solve.
In my code i have a custom hook with a bunch of functionality for a fetching a list
of train journeys. I have some useEffects to that keeps loading in new journeys untill the last journey of the day.
When i change route, while it is still loading in new journeys. I get the "changes to unmounted component" React error.
I understand that i get this error because the component is doing an async fetch that finishes after i've gone to a new page.
The problem i can't figure out is HOW do i prevent it from doing that? the "unmounted" error always occur on one of the 4 lines listed in the code snippet.
Mock of the code:
const [loading, setLoading] = useState(true);
const [journeys, setJourneys] = useState([]);
const [hasLaterDepartures, setHasLaterDepartures] = useState(true);
const getJourneys = async (date, journeys) => {
setLoading(true);
setHasLaterDepartures(true);
const selectedDateJourneys = await fetchJourney(date); // Fetch that returns 0-3 journeys
if (condition1) setHasLaterDepartures(false); // trying to update unmounted component
if (condition2) {
if (condition3) {
setJourneys(something1); // trying to update unmounted component
} else {
setJourneys(something2) // trying to update unmounted component
}
} else {
setJourneys(something3); // trying to update unmounted component
}
};
// useEffects for continous loading of journeys.
useEffect(() => {
if (!hasLaterDepartures) setLoading(false);
}, [hasLaterDepartures]);
useEffect(() => {
if (hasLaterDepartures && journeys.length > 0) {
const latestStart = ... // just a date
if (latestStart.addMinutes(5).isSameDay(latestStart)) {
getJourneys(latestStart.addMinutes(5), journeys);
} else {
setLoading(false);
}
}
}, [journeys]);
I can't use a variable like isMounted = true in the useEffect beacuse it would reach inside the if statement and reach a "setState" by the time i'm on another page.
Moving the entire call into a useEffect doesn't seem to work either. I am at a loss.
Create a variable called mounted with useRef, initialised as true. Then add an effect to set mounted.current to false when the component unmounts.
You can use mounted.current anywhere inside the component to see if it's mounted, and check that before setting any state.
useRef gives you a variable you can mutate but which doesn't cause a rerender.
When you use useEffect hook with action which can be done after component change you should also take care about clean effect when needed. Maybe example help you, also check this page.
useEffect(() => {
let isClosed = false
const fetchData = async () => {
const data = await response.json()
if ( !isClosed ) {
setState( data )
}
};
fetchData()
return () => {
isClosed = true
};
}, []);
In your use case, you probably want to create a Store that doesn't reload everytime you change route (client side).
Example of a store using useContext();
const MyStoreContext = createContext()
export function useMyStore() {
const context = useContext(MyStoreContext)
if (!context && typeof window !== 'undefined') {
throw new Error(`useMyStore must be used within a MyStoreContext`)
}
return context
}
export function MyStoreProvider(props) {
const [ myState, setMyState ] = useState()
//....whatever codes u doing with ur hook.
const exampleCustomFunction = () => {
return myState
}
const getAllRoutes = async (mydestination) => {
return await getAllMyRoutesFromApi(mydestination)
}
// you return all your "getter" and "setter" in value props so you can use them outside the store.
return <MyStoreContext.Provider value={{ myState, setMyState, exampleCustomFunction, getAllRoutes }}>{props.children}</MyStoreContext.Provider>
}
You will wrap the store around your entire App, e.g.
<MyStoreProvider>
<App />
</MyStoreProvider>
In your page where you want to use your hook, you can do
const { myState, setMyState, exampleCustomFunction, getAllRoutes } = useMyStore()
const onClick = async () => getAllRouters(mydestination)
Considering if you have client side routing (not server side), this doesn't get reloaded every time you change your route.

How to prevent infinite re-rendering with useEffect() in React [duplicate]

This question already has answers here:
react useEffect comparing objects
(7 answers)
Closed 2 years ago.
I have an app that checks the user id on startup and loads the list of todo items based on the user logged in. I have the useEffect change only when data changes, but I have setData in the body of useEffect() meaning data changes and it re-runs infinitum.
However if I change [data] to [] in the second parameter, then it renders once BUT I have to refresh the page everytime I add a todo item for it to render rather than it render automatically. How can I have it render automatically without looping infinitely?
const [data, setData] = useState([])
useEffect(() => {
UserService.getUserById(localStorage.getItem("userId")).then(res => {
if (res.data !== null) {
setData(res.data.todos)
}
})
}, [data])
You can add a condition in the call back function that checks if a certain condition is met, e.g. if data is empty. If it is empty, then fetch data, otherwise do nothing. This will prevent the infinite loop from happening.
const getData = useEffect(()=>{
const fetchData = () => {
UserService.getUserById(localStorage.getItem("userId"))
.then(res => {
if (res.data !== null) {
setData(res.data.todos)
}
})
.catch(error => {
// do something with error
})
}
if (data.length === 0)
fetchData()
},[data]);
Alternatively, you use an empty dependency array so that the callback function in the useEffect is called once.
useCallback Hook can be used with slight modifications in your code.
You will need to import useCallback from "react" first.
import {useCallback} from "react";
And then use this useCallback around our getData function. (Have modified the answer a bit)
const getData = useCallback(()=>{
UserService.getUserById(localStorage.getItem("userId")).then(res => {
if (res.data !== null) {
setData(res.data.todos)
}
})
},[data]);
useEffect(() => {
getData();
}, [data])
This React Hook will make sure that the getData() function is only created when the second argument data changes.
In your code UserService.getUserById(localStorage.getItem("userId")) return a promise and it get data one time so you just have to call getUserById one time at the time of load by using [] and if you want to call it again make a function and use it wherever on refresh function or on adding todos item or update or delete function. Otherwise you have to use observable or useCallBack hook
You need to pass the reset param to prevent loop. once callback trigger reset value false. so that execution not running again until reset the value
Codesanbox
export default function App() {
let i = 1;
const [data, setData] = useState([]);
const [reset, setReset] = useState(true);
useEffect(() => {
if (reset) {
setTimeout(() => {
//callback
setReset(false);
setData(Math.random());
}, 1000);
}
}, [data]);
return (
<div className="App">
<h1>{data}</h1>
<button
onClick={() => {
setReset(true);
setData("");
}}
>
Click this and see the data render again. i just reset the data to empty
</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
Use a condition to stop the loop by setting a condition to stop it. You can check if a certain value is set or check if there are any values sent at all.

Categories

Resources