React function after dom has finished rendering - javascript

I have a map of the world and whenever you click on a particular country, it pings and API and gets schools in that country. React then uses leaflet to display all the dots(GEOJSON). After the schools are loaded I have a Dock type thing that will pop up. The problem is that I need to know when the react is done updating. I tried using the react lifecycle methods in the Map.jsx file I have the following code to try to see when the GEOJSON is done rendering:
componentWillUpdate() {
console.log('CWU');
}
componentDidUpdate() {
console.log('CDU');
}
But in the console I get the following printouts:
CWU, CDU, CWU, CDU, CWU, CDU
So both functions run 3 times, for another country both functions run twice. So I cant put the function that brings up the Dock in either componentWillUpdate or componentDidUpdate because I would need to run the function after the 3rd 'CDU' or for the other country after the second 'CDU'. Is there anyway way to know when react has finished rendering ?

I found a way to solve it. I'll try my best to explain it:
I have a state variable called
didUpdate = false
The API changes a particular entry in the props
so in componentWillUpdate I check to see if the next state is different than my current one if so I set didUpdate to false. This makes it so that it doesn't display.
Then in my componentDidUpdate I check to see if previous state is different than my current one if so I then set didUpdate to true. This then makes it so the dock is shown.
Here is some code:
componentWillUpdate(nextProps, nextState) {
if (nextProps.X !== this.props.X) {
this.setState({
didUpdate: false,
})
}
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.X !==this.props.X) {
this.setState({
didUpdate: true
})
}
}

You can use
componentWillUnmount()
componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().

Related

React: Avoid nondeterministic first render (hack with "mounted" state)

TL;DR React sometimes renders a loading state and sometimes not, without changes in the UI. This is probably due to batched updates.
I would like to know if the problem below is due to batched updates. If the answer is "yes", I would like to know if there's preferred way to opt-out of batched updates in React to get deterministic render behavior. Go down to "Experiment" if you want to skip the setup.
Setup
Here's the setup, a chart that takes a long time to render. So long that the render is blocking. There are three different ways to render the chart here:
one is the normal way
one with a "mounted" render hack
one with the same "mounted" render hack, but with an additional setTimeout
Option 2 & 3 both have a small useState to check whether they've been mounted. I do this to show a "Loading" state conditionally:
function ChartWithMountHack({ data }: { data: Data }) {
// initially not mounted
const [isMounted, setIsMounted] = useState<boolean>(false);
useEffect(() => {
// "Now I've been mounted!"
setIsMounted(true);
}, []);
return !isMounted ? <p>Loading</p> : <Chart data={data} />;
}
I did this, because I want to show a "Loading" state instead of a blocking render, so e.g. page switches or ternary rendering (e.g. hasData ? <p>No data</p> : <Chart />) are shown immediately, instead of blocking. (If there are better ways, please let me know!)
Experiment
Now, each button will render one of the three options/charts. Again, the second and third chart have a small hack to check whether they're mounted or not.
Try clicking on the first button and the second button back & forth quickly.
You will see that sometimes the "Chart with mount hack" will ("correctly") render the "Loading" state, but sometimes it just doesn't render the "Loading" - instead it blocks the render up until the chart is finished rendering (skips the "Loading" state).
I think this is due to the render cycles and whether you get the two updates in one cycle of the batching. (first: isMounted === false -> second: isMounted === true)
I can't really tell how to reproduce this, hence the "nondeterministic" in the title. Sometimes you also have to click on "Regenerate data" and click back & forth after that.
Cross-check
Option 3 ("Chart with mount hack with timeout") ALWAYS gives me the "Loading" state, which is exactly what I want. The only difference to option 2 is using a setTimeout in the useEffect where isMounted is set to true. setTimeout is used here to break out of the update batching.
Is there a better way to opt-out of the batching, so isMounted will always render with its initial value (false)? Using setTimeout here feels like a hack.
React has concurrent features to handle these sort of things, for example React Suspense tags or you make use of Subscription libraries like Rxjs, which its subscription should be done in the componentDidMount and componentWillUnmount to unsubscribe the data.
Then the isMounted is just a work around for a pending issue, probably from the library you're using or sometimes just your bundler/build tool acting out a bit.
lastly to avoid unnecessary re-render, you can use React memoization of component using React.Memo.
Kindly read more on these.

Why react lifecycle work everything multiple times

i have problem in react and long time i can't figure out with this.
I can't understand what happened.
What a scenario:
My app using React and Redux. I keep all my state in redux.
i set some dataRefreshed state to redux state for handling re-rendering page when data updated from api.
i'm using componentWillReceiveProps lifecycle method.
in my redux state
let initialState = {
dataRefreshed: false
}
when my request starting, in redux
case START_REQUEST:
return {
...state,
dataRefreshed: false
};
case SUCCESS_REQUEST:
return {
...state,
dataRefreshed: true
};
So in my component when i make request and get from api new data:
componentWillReceiveProps(nextProps) {
if (nextProps.Reducer.dataRefreshed) {
apiCall();
}
}
so if thinking with logic:
1- when my request start and request getting ok status, my dataRefreshed setting true
2- and here nextProps.dataRefreshed and this.props.dataRefreshed not equal.
Till here everything work well. my Condition working and apiCall() runned.
but apiCall function runned 10 times
why my state toggling one time so my state turning one to to true from false. But inside condition my function calling million times.
i can't understand what is the logic here.
I'm seriously think anymore living react because of that
This is what your code actually does:
set dataRefreshed=true
trigger props change
call apiCall() (cause dataRefreshed==true)
START_REQUEST: setting dataRefreshed=false
trigger props change (does nothing cause dataRefreshed==false)
apiCall got response from server,
SUCCESS_REQUEST: setting dataRefreshed=true
goto 2 (infinite loop)
You should introduce some flag like shouldRefresh, which will be set to false once data is fetched and not automatically set back to true unless it is meant to.
Is not that it is doing it twisted. The problem is componentWillReceiveProps method, that’s why react remove it to avoid this type of things

re-render component without setting state react js

I am deleting a record from db, for this I am calling an API. When I received an
API response of a successful deletion, I need to re-render all the component again like reload does. I tried it with this.forceUpdate and shouldComponentAgain but no luck.
I also tried with componentDidUpdate, it works but it is calling API infinite times. Below is my code how I used componentDidUpdate:
componentDidUpdate(){
let newThis = this;
getAccounts().then(function(response){
if(response.status===200){
newThis.setState({
Accounts:response.data
})
}
});
}
Please tell me the way to re-render like reload do, but without re-loading the whole page.
When using componentDidUpdate, you should always have a conditional setState which denotes that you need to perform something because the current state or current props is not equal to previous state or props.
componentDidUpdate always gets called whenever your component has updated. In your case what is happening is that you are calling setState without any condition which updates your component, and setState is called again causing an infinite loop in updating the component.
You should have something like this check here:
componentDidUpdate(prevProps, prevState){
let newThis = this;
if(newThis.props.{some-variable} !== prevProps.{some-variable}) {
getAccounts().then(function(response){
if(response.status===200){
newThis.setState({
Accounts:response.data
})
}
});
}
}
Adding conditional setState is very important here else you will end up in an infinite loop.
As per the official docs as well:
You may call setState() immediately in componentDidUpdate() but note
that it must be wrapped in a condition or you’ll cause an infinite
loop. It would also cause an extra re-rendering which, while not
visible to the user, can affect the component performance. If you’re
trying to “mirror” some state to a prop coming from above, consider
using the prop directly instead.
Hope it helps.
If you want to render the component again, then change the props from the parent. If props change then child component automatically going to render. And by this features, you can also render the selective component.

React native: State seems to retain previous state even after reload on the simulator

I have a code that does something like this:(Pseudo Code)
class extends Component{
constructor(){
this.state={
condition1 : false,
condition2 : false,
condition3 : false,
condition4 : false,
}
//set all 4 condition states to true once data loading has completed from the store
}
render(){
return(
if (this.state.condition1 && this.state.condition2 && this.state.condition3 && this.state.condition4)
Render Main component
else
Render loading screen
);
}
}
Basically I have 4 states that are used to check if data has been loaded before loading the main component.Once the Store (from another file) has finished loading the data, it emits an event,causing the 4 states to be set to true, loading the main component.However, once I hit the reload button from the Genymotion emulator, the 4 states remain as true and does not get reset to false, causing the app to load the main component and crash.(Since the data is not ready yet) I have tried resetting all 4 states to false again in the ComponentWillMount method but it seems like the states doesn't get set back to false in time before the main app attempts to load the main component. In which component of the life cycle should I reset the states to false instead? Or should I ignore this issue since it is "unrealistic" to reload the app on an actual device?
In which component of the life cycle should I reset the states to
false instead?
The docs recommend using componentWillReceiveProps to update the state prior to rendering. I've done this for my projects and it works well, though depending on your use case you may need to check whether newProps is different than this.props.
https://facebook.github.io/react/docs/component-specs.html
componentWillReceiveProps
Invoked when a component is receiving new props. This method is not
called for the initial render.
Use this as an opportunity to react to a prop transition before
render() is called by updating the state using this.setState(). The
old props can be accessed via this.props. Calling this.setState()
within this function will not trigger an additional render.
I have managed to get around this issue by reducing the number of network fetch requests. It seems that having multiple network requests at the same time can cause instability, even though it is unclear why so.

ComponentDidUpdate SetState ReactJS Infinite loop

Even though there are many questions with the same subject line, I could not get an answer for my problem.
Problem
I have a select dropdown. On click of which, I call an Api which fetches some key values. I consider this set of key value input fields as a component. So each and every time onChange of my select drop-down, I have used lifecycle methods to handle API Calls. Also, I record these input key values and send back their state to parent component.
According to ReactJS lifecycle methods:
I use
componentDidMount
To call the API for the first time after initial render. This works.
componentDidUpdate
To call the API for subsequent API calls on select drop-down change. But here is the problem. When I try to update the state of input fields the program falls into an infinite loop and hence there are infinite API calls. I am pretty sure after debugging that the problem is with setState(), But I couldnt find the best way to handle states in componentDidUpdate method.
This link exactly replicates my problem but i want a standardized solution
Hope this is clear.
Thanks for the help in advance!
This is spelled out pretty clearly in the docs:
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
You may call setState() immediately in componentDidUpdate() but note
that it must be wrapped in a condition like in the example above, or
you’ll cause an infinite loop.
You can use setState() within componentDidUpdate(). But you have to use the condition for that. Otherwise, it get infinite loop.
As the example,
componentDidUpdate(){
if(this.props.id !== this.state.id) {
this.setState({
id: this.props.id
});
}
}
This happens because setState triggers a call to componentDidUpdate.
When componentDidUpdate is called, setState does not check whether or not state change has occurred. It simply calls componentDidUpdate again and again, which leads to stackoverflow.
class Component extends React.Component{
constructor(props){
super(props);
this.state = {changeState: false}
}
componentDidMount(){
this.setState({changeState: true});
}
componentDidUpdate(){
this.setState({changeState: false});
}
}
Here, first changeState is set to false in constructor, and then componentDidMount is triggered, which sets state of changeState to true. This state change triggers componentDidUpdate, which sets the state of changeState again to true. This triggers componentDidUpdate again and again.
You have to check the real difference between two state objects.
Below you can find my solution, My state object has movies, which is an array of objects. I edited and movie and then comparing these two arrays.
async componentDidUpdate(prevProps, prevState) {
if (prevState.movies.filter (e => this.state.movies.includes(e))) {
const response = await axios.get("http://localhost:3002/movies");
this.setState({ movies: response.data })
}
}
Yes you cannot setState() inside componentDidUpdate it would lead to infinite loop.Instead you can call a function onChange event and change the state there.

Categories