Determine when Relay has fetched data and populated props - reactjs

When I call this.props.relay.setVariables, Relay will fetch data given these new variables, and afterwards I want to do stuff with this data. However I cannot figure out a good way to know exactly when this occur.
I first tried using the onReadyStateChange callback on setVariables but this didn't catch the moment when props was populated.
What I ended up doing to fix the problem was using a setTimeout in componentWillRecieveProps and by then the props is populated with the new data. But is there a better way to determine when data has been fetched?

There is no need to invoke setTimeout inside of componentWillReceiveProps(nextProps) as the lifecycle method gets called automatically once the component's props change so you just need a conditional block like this in the componentWillReceiveProps (assuming the variables can be distinguished via === check)
if(this.props.data !== nextProps.data){
//do your stuff
}
or you could use the length field if the data is an array:
if(this.props.data.length !== nextProps.data.length){
//do your stuff
}
If the condition holds, it means that you've received the new data you were looking for and you can process it.

Related

Using state variable values after promise resolution in react

I have used the following pattern in previous ReactJS projects - A LOT!
componentDidMount() {
var promises = [];
var promise1 = this.AsyncFunc1().then(res => {
this.setState({some_state1: res.data.results});
}).catch(error => {
//deal with error
});
var promise2 = this.AsyncFunc2().then(res => {
this.setState({some_state2: res.data.results});
}).catch(error => {
//deal with error
});
promises.push(promise1);
promises.push(promise2);
Promise.all(promises).then(() => {
// Use the state variables
}).catch(error => {
// deal with error
});
}
I understand that state is set asynchronously and not available right away - I avoided accessing the state immediately following the setting of that state in the then statements of the async functions - but I have consistently accessed the state variable values in the promise resolution section in previous projects - but in this new project I'm working on it is failing off and on because the state variable values aren't consistently available. I've resorted to setting temp variables to the res.data.results values and using those in the Promise.all section.
My previous projects have been in production for 2+ years without issues (that I'm aware of) - do I need to go back and rework that code? Did I make a bad assumption that the state variable values would be available in the Promise.all section?
I appreciate the help!
Yes, you made an incorrect assumption; your promise might be called before the state is available. From setState docs, emphasis mine:
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.
...
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
There's also a FAQ entry about your kind of case, emphasis again mine:
Why is setState giving me the wrong value?
In React, both this.props and this.state represent the rendered values, i.e. what’s currently on the screen.
Calls to setState are asynchronous - don’t rely on this.state to reflect the new value immediately after calling setState. Pass an updater function instead of an object if you need to compute values based on the current state (see below for details).
As in the second quote, the state variables won't be updated until after the render happens, and even though you're not immediately reading state after calling setState, it's still likely that your Promise.all will fire before the this.state is updated—and React absolutely reserves the right to do so. Auditing your previous use of this pattern is probably a good idea.
Some options for you if you really want the render to happen first:
Use componentDidUpdate and check that both state variables are set, which the FAQ recommends.
Use the second setState parameter, updater, which will call you back after the setState takes effect. This is not recommended compared to componentDidUpdate.
With hooks, use useEffect. You can pass your some_state1 and some_state2 in the second argument to useEffect to only take effect when the values change.
Within your AsyncFunc then handlers, wrap your code in flushSync. The docs heavily recommend against this option: flushSync is not as specific as the other options above, but for a stopgap measure on old code it might be effective for you.
If you don't need the UI to update before your Promise.all() handler runs, you can save the resolved values to local variables, or you can make sure to return the values from your AsyncFunc then handlers. If your // deal with error handlers provide fallback values, you can return those from your catch handlers there too; conversely, if you don't want your Promise.all() handler to run if either of those calls fail, you should ensure that they re-throw the exceptions from within the catch block so the promises you save stay rejected.
(Of course, you could also move both of your setState calls to the Promise.all handler so you don't even try to call setState before both values are there, but I presume you want the partial UI update that comes from each individual AsyncFunc.)

React: Filtering table based on array of objects state?

So im working on a pretty simple personal project to learn react. The state within my "SensorTable" component (Which is essentially just a react-bootstrap table currently that has a listing of my sensors (Which have values like id/temp/humidity/etc...).
Right now this components state is just an array of objects (That being my sensors) that I retrieve via axios/fetch from a GET request to JSON API that has all these objects. The GET request is made on componentDidMount.
Im really not fully comfortable with redux at this point, and this is simply a small toy app to get acquainted with React. But would I be correct in guessing the correct way to sort/filter this table would be to have a function that:
Retrieves the latest data from the API (Maybe?)
Updates the state either by sorting the array of objects in a specific order OR filtering them based off whatever filter type I want to use (Maybe filter by sensors that have a specific object key that is above or below or certain value). And then returns the new array to that state?
This sounds simple in theory to me...but wouldn't the componentDidMount just fire again once this function happens (resetting the data to how it was originally?). Or is there a way to make it NOT re-fire that method if I update the state in one of these filter/sorting functions?
componentDidMount only fires when the component is initialized and not on further renders, so your request will only fire once until you unmount and remount the component (by navigating away from and returning to the page, for example).
As for updating state, let's say you store the response data in a state variable called sensorData. If you set sensorData every time the user applies a filter, you are losing data, because you are effectively removing items from the sensorData array. This means that when the filter is removed, a refetch is required to regain the data you filtered out.
A better approach would be to store all the response data in sensorData, and apply the filter inline when rendering the table.
For example:
{sensorData.filter(d => d.someProp === "someVal").map(d => <TableRowItem />)}
This way, when removing the filter, you do not have to hit the server again and the UI is updated immediately.
In the simplest component case, yes, make API calls (or any number of side-effects) in the componentDidMount lifecycle function. componentDidMount is called only once when the component is mounted. componentDidUpdate however, is called upon any changes to state or props.
A sample component could look like this:
class MyComponent extends Component {
this.state = {
data: [];
};
componentDidMount() {
getDataFromAPI().then(
response => setState({
data: response.data.filter(someFilterFn) // filter result array and store in state
});
);
}
render() {
<div>
{this.state.data.map(mapElementFn)}
</div>
}
}
This is just an example, you could also just store all the data in state and filter (as needed) in the render function. It's really up to you and your app's needs.
componentDidMount triggers only ones.
Using state, you can maintain two keys one with intialList and modifiedList after filter / sort.
Local sort is based on normal js array sort.
ref : https://moduscreate.com/blog/ext-js-to-react-load-sort-and-filter-data-with-react/
You can filter in your local or rely on remote thing
When you use remote api calls to sort, then you need to use an event handler and invoke the fetch call explicity.
Ref: https://codereviewvideos.com/course/pagination-filtering-and-sorting/video/react-sorting
Ref: Sample https://github.com/codereviewvideos/react-symfony-3.example/blob/pagination-sort-filter/src/containers/list.js
Same applies for filter as well.

How to intercept setState function to capture individual state changes?

I am implementing activity logging in a React application to track users activity and a practical way would be to simply log each change to the component state. The state object is large in my scenario, so I would like to capture only the changed elements, which would also be easier to read in the log. To do so it would be ideal to intercept every setState call so that I can get the specific pieces that changed in the state. I have most of all the state in a higher level component so I would have to do this only in one component basically.
I don't want to do this in every call to setState across all my component's code. I want to be able intercept it for once an for all at a component level.
Is there an elegant way of intercepting setState that does not involve creating an alternate setState function stub?
Any tips greatly appreciated. Thanks.
The following lifecycle methods will be showing the changes when state/props changes in a component. You can use the state object to determine the activity logging.
// Happens before the component update
componentWillUpdate(object nextProps, object nextState)
// Happens after the component update
componentDidUpdate(object prevProps, object prevState)
For example,
componentDidUpdate(prevProps, prevState) {
if(prevState.isLoadingData !== this.state.isLoadingData) {
writeToActivityLog('user-requested-new-data');
}
}
The state object is large in my scenario, so I would like to capture
only the changed elements, which would also be easier to read in the
log.
For this scenario, you can make use of utility library like lodash to get all the changed properties or do a deep equal between this.state and prev/nextState. Even then you may require conditions to log different events.

ComponentWillMount and ComponentDidMount not synced

I am making a react application. I need to make an ajax call to fetch the data from server and populate this data in the rows of my component's table. I do my ajax call in componentWillMount and store the response in the state. Now my render fetched this state and populates it. I have a few questions though:
Am I doing it right to place ajax in componentWillMount?
My componentDidMount doesn't get the value of state which get's set in componentWillMount. This means the async is not complete while componentDidMount is trying to access it. How to solve this? setting async:false is really a bad option.
What all thing can be used in componentWillMount?
Yes, componentWillMount is called once before the render. This is the place to fetch data.
Probably when the component finished rendering, the componentDidMount called although the async operation didn't finished yet, therefore you get empty result.
You can use componentWillUpdate
componentWillUpdate() is invoked immediately before rendering when new
props or state are being received. Use this as an opportunity to
perform preparation before an update occurs. This method is not called
for the initial render.
Or shouldComponentUpdate,
shouldComponentUpdate() is invoked before rendering when new props
or state are being received. Defaults to true. This method is not
called for the initial render or when forceUpdate() is used.
It's a broad question, basically fetching data from actions, calling api's etc.
More info in docs
Am I doing it right to place ajax in componentWillMount?
The recommend approach is to place your initial network requests that modify state inside of componentDidMount, because in theory your ajax request could come back prior to React completing the mounting process which might lead to your state getting overwritten.
However, in practise people use both as the above scenario should never happen.
My componentDidMount doesn't get the value of state which get's set in
componentWillMount. This means the async is not complete while
componentDidMount is trying to access it.
That is the correct behaviour.
How to solve this? setting
async:false is really a bad option.
If you stop it then it's no longer async.
The best practise here would be to show a loading indicator:
add a loading property to state
set it to true just above your fetch request
inside .then and .catch set loading to false
inside render check if your data has arrived - if not display the loading indicator
If you don't want to show anything until your request comes back
You can return null inside of render which will prevent your component from being added to the DOM. You can then combine this with a flag state property to make sure the component gets mounted once your request comes back, i.e. something like this:
render() {
if (!this.state.initialRequestHasCompleted) return (null);
return (
<div>
...
</div>
);
}
What all thing can be used in componentWillMount?
What is the purpose of having functions like componentWillMount in React.js?

Reactjs- countering undefined error in referencing state sub elements in render methods

I run frequently into problem while using React in a specific way.
I have a hierarchy of components. The application state lives in the common owner uppermost component. I set initial state with empty objects, or arrays whatever in getInitialState. I fetch data via ajax with calling a method of this state owner uppermost component in this component's componentDidMount. But whether I use the state set in the success callback of the ajax call in this same component, or in sub components, by passing the state to them via props, I almost always get a variable can't find or something undefined sort of errors.
Thinking conceptually, the render method invoked when I mount the components, so it will not find data in the variables embedded in it. But we have getInitialState. This seems ok but what if the nested objects are being used in the render methods. States in the getInitialState are just empty objects, so references to the to be sub elements are undefined.
Even using componentWillMount does not work. Only way seems having initial states and not to reference any undefined sub elements of those in the render. However it seems necessary in most cases. Is the issue related to this necessity I see being actually an anti-pattern.
I have found React very useful for me at first tutorials and simpler cases, but now I cannot make use of it efficiently.
How to counter this behaviour correctly and in the best practice possible?
FIRST SOLUTION:
The method I use is simply check is the state exists in the render function
render: function() {
var result = { this.state.field ?
this.state.field.embeddedfield ?
<div> this.state.field.embeddedfield </div>
: <div> Data are loading! </div>
: "" }
return {result};
}
SECOND SOLUTION
If you want your data to be ready before rendering you need to execute the first fetch outside :
$.ajax({...}).success(function(res) {
<MyView data={res} /> // render your function on success
});
See this : bind(this) not working on ajax success function
To answer about why componentWillMount won't work:Putting ajax call in componentWillMount won't work because it won't wait for your call to be fetched and cycle concurrency may also occurs.

Resources