set state in a callback of an async function - reactjs

I am new to React, so bear with me please. I have a component that calls another component that takes a property. This property will get it's value on a callback of a function, something like this:
render(){
myFunc((p) => {
if(!_.isEqual(p, this.state.myProp))
this.setState({myProp: p})
});
return <MyComponent myProp={this.state.myProp}/>
}
myFunc will or will not make an API request and depending on that will call the callback sooner or later. This seems to work fine when API request is made and the callback takes longer to return. However, when the request is not needed and callback returns instantaneously (or almost) I am getting a Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
What am I doing wrong and what is the right way to approach this? Where would be the right place to put this code? Basically what I need is to re-render MyComponenent if this.state.myProp changes

You shouldn't be calling setState inside the render method, you might end up having an infinite loop.
The call to myFunc should be somewhere else (depending on the business logic you have). When the function finishes, it will update the state and then trigger a re-render so MyComponent will get the latest value.
UPDATE
I don't know which conditions will require calling myFunc again, but you can do:
state = {
myProp: null // or some other value that MyComponent can handle as a null state
}
componentDidMount () {
myFunc((p) => {
if(!_.isEqual(p, this.state.myProp)) // This is needed only if you get null back from the callback and you don't want to perform an unnecesary state update
this.setState({myProp: p})
}
}
render(){
const { myProp } = this.state
// You can also do if (!myProp) return null
return <MyComponent myProp={myProp}/>
}

Related

useEffect not triggering when object property in dependence array

I have a context/provider that has a websocket as a state variable. Once the socket is initialized, the onMessage callback is set. The callback is something as follows:
const wsOnMessage = (message: any) => {
const data = JSON.parse(message.data);
setProgress(merge(progress, data.progress));
};
Then in the component I have something like this:
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress[pvc.metadata.uid]])
return (
{/* stuff */}
);
}
However, the effect isn't triggering when the progress variable gets updated.
The data structure of the progress variable is something like
{
"uid-here": 0.25,
"another-uid-here": 0.72,
...etc,
}
How can I get the useEffect to trigger when the property that matches pvc.metadata.uid gets updated?
Or, how can I get the component to re-render when that value gets updated?
Quoting the docs:
The function passed to useEffect will run after the render is
committed to the screen.
And that's the key part (that many seem to miss): one uses dependency list supplied to useEffect to limit its invokations, but not to set up some conditions extra to that 'after the render is committed'.
In other words, if your component is not considered updated by React, useEffect hooks just won't be called!
Now, it's not clear from your question how exactly your context (progress) looks like, but this line:
setProgress(merge(progress, data.progress));
... is highly suspicious.
See, for React to track the change in object the reference of this object should change. Now, there's a big chance setProgress just assignes value (passed as its parameter) to a variable, and doesn't do any cloning, shallow or deep.
Yet if merge in your code is similar to lodash.merge (and, again, there's a huge chance it actually is lodash.merge; JS ecosystem is not that big these days), it doesn't return a new object; instead it reassigns values from data.progress to progress and returns the latter.
It's pretty easy to check: replace the aforementioned line with...
setProgress({ ...merge(progress, data.progress) });
Now, in this case a new object will be created and its value will be passed to setProgress. I strongly suggest moving this cloning inside setProgress though; sure, you can do some checks there whether or not you should actually force value update, but even without those checks it should be performant enough.
There seems to be no problem... are you sure pvc.metadata.uid key is in the progress object?
another point: move that dependency into a separate variable after that, put it in the dependency array.
Spread operator create a new reference, so it will trigger the render
let updated = {...property};
updated[propertyname] =value;
setProperty(()=>updated);
If you use only the below code snippet, it will not re-render
let updated = property; //here property is the base object
updated[propertyname] = value;
setProperty(()=>updated);
Try [progress['pvc.metadata.uid']]
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress['pvc.metadata.uid']])
return (
{/* stuff */}
);
}

Not getting current state in onClick function

New to react and redux and struggling with state management a bit. In the following code, I have a button. Clicking it triggers a two step function:
1) Call a reducer to do some validation and update a flag in state based on whether it succeeded or not.
2) Use the updated flag in state to notify a user if the validation failed, or proceed with the normal process if it succeeded.
transfer() {
this.props.validateData();
this.notifyOrSend(this.props.validationSucceeded);
}
notifyOrSend(validationSucceeded) {
if (validationSucceeded === false) {
this.refs.validationModal.showModal();
} else {
this.props.sendTransfer();
}
}
render() {
return (
<div>
<Button label="Transfer" onClickFn={this.transfer}/>
<ValidationModal ref="validationModal"/>
</div>
);
}
...
function mapStateToProps(state) {
return {
validationSucceeded: state.validatedData.validationSucceeded,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
validateData,
}, dispatch);
}
My issue is that I am getting an old version of the flag (this.props.validationSucceeded). The flag's initial state is true. If it fails validation on the first click of the button, the value of this.props.validationSucceeded remains true. The change to false is only picked up on the second click. I can see in the console that the action that gets triggered has the latest value of the flag in nextState. How can I change this code to get that latest value?
setState is more like an event, it doesn't happen right away hence why you can pass a second function to setState that will execute once it actually happens.
Since you are calling notifyOrSend immediately after, state will still be the same until the next render occurs. I would look into moving the notifyOrSend function into the appropriate lifecycle method for the component. I would assume it would either be componentDidUpdate or componentWillUpdate depending on what the notifyOrSend function actually does.
from the react docs:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.

Throttling dispatch in redux producing strange behaviour

I have this class:
export default class Search extends Component {
throttle(fn, threshhold, scope) {
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
}
}
render() {
return (
<div>
<input type='text' ref='input' onChange={this.throttle(this.handleSearch,3000,this)} />
</div>
)
}
handleSearch(e) {
let text = this.refs.input.value;
this.someFunc();
//this.props.onSearch(text)
}
someFunc() {
console.log('hi')
}
}
All this code does it log out hi every 3 seconds - the throttle call wrapping the handleSearch method takes care of this
As soon as I uncomment this line:
this.props.onSearch(text)
the throttle methods stops having an effect and the console just logs out hi every time the key is hit without a pause and also the oSearch function is invoked.
This onSearch method is a prop method passed down from the main app:
<Search onSearch={ text => dispatch(search(text)) } />
the redux dispatch fires off a redux search action which looks like so:
export function searchPerformed(search) {
return {
type: SEARCH_PERFORMED
}
}
I have no idea why this is happening - I'm guessing it's something to do with redux because the issue occurs when handleSearch is calling onSearch, which in turn fires a redux dispatch in the parent component.
The problem is that the first time it executes, it goes to the else, which calls the dispatch function. The reducer probably immediately update some state, and causes a rerender; the re-render causes the input to be created again, with a new 'throttle closure' which again has null 'last' and 'deferTimer' -> going to the else every single time, hence updating immediately.
As Mike noted, just not updating the component can you get the right behavior, if the component doesn't need updating.
In my case, I had a component that needed to poll a server for updates every couple of seconds, until some state-derived prop changed value (e.g. 'pending' vs 'complete').
Every time the new data came in, the component re-rendered, and called the action creator again, and throttling the action creator didn't work.
I was able to solve simply by handing the relevant action creator to setInterval on component mount. Yes, it's a side effect happening on render, but it's easy to reason about, and the actual state changes still go through the dispatcher.
If you want to keep it pure, or your use case is more complicated, check out https://github.com/pirosikick/redux-throttle-actions.
Thanks to luanped who helped me realise the issue here. With that understood I was able to find a simple solution. The search component does not need to update as the input is an uncontrolled component. To stop the cyclical issue I was having I've used shouldComponentUpdate to prevent it from ever re-rendering:
constructor() {
super();
this.handleSearch = _.throttle(this.handleSearch,1000);
}
shouldComponentUpdate() {
return false;
}
I also moved the throttle in to the constructor so there can only ever be once instance of the throttle.
I think this is a good solution, however I am only just starting to learn react so if anyone can point out a problem with this approach it would be welcomed.

How can I ensure a reactjs state is updated, and then call a function?

I'm trying to use reactjs to update a state, and once it is updated fire an ajax call requesting a new page. Just before the ajax call fires an offset variable is set: var offset = this.state.npp * this.state.page; However I find after clickNextPage() is fired, the value of this.state.page is not updated.
I fundamentally do not understand what is happening here, this appears to be a race condition, because I watch the state change on my render() function with {this.state.page}.
How can I ensure my this.state.page is updated, and then fire findByName()?
clickNextPage: function(event){
console.log("clicked happend")
page = this.state.page;
console.log(page)
page += 1
console.log(page)
this.setState({page: page});
console.log(this.state.page)
this.findByName()
},
JS Console:
clicked happend
0
1
0
setState is asynchronous in that this.state will not be updated right away. The short answer to your quandary is use the callback (the second parameter to setState) which is invoked after the internal state is been updated. For example
this.setState({page: page}, function stateUpdateComplete() {
console.log(this.state.page)
this.findByName();
}.bind(this));
The longer answer is that after you call setState, three functions are called (see here for more details about each https://facebook.github.io/react/docs/component-specs.html):
shouldComponentUpdate this allows you to inspect the previous and new state to determine whether the component should update itself. If you return false, the following functions are not executed (although the this.state will still be updated within your component)
componentWillUpdate this gives you a chance to run any code before the new state is set internally and rendering happens
render this happens between the component "will" and "did" functions.
componentDidUpdate this gives you a chance to run any code after the new state is set and the component has re-rendered itself
When calling this.setState the new state isn't set directly, React puts it in a pending state which by calling this.state can return the old state.
This is because React might batch your state updates and therefore offers no guarantee that this.setState is synchronous.
What you want to do is called this.findByName() within componentDidUpdate, componentWillUpdate or through the callback offered as a second argument to this.setState depending on your use case. Note that the callback to this.setState won't be fired until after your call has passed and the component has re-rendered itself.
Furthermore in your case you could pass a function do this.setState instead of doing a variable dance to increase readability.
this.setState(function (prevState, currentProps) {
return {page: prevState.page + 1}
}, this.findByName);
Using nameless functions this code can be written in a shorter format.
myReactClassFunction = (param) => {
this.setState({
key: value,
},
() => this.myClassFunction());
}

redux-saga, call function on network complete?

I'd like to call a component's function when network fetch completes.
function callRestApi({config, schema}) {
return axios(config).then((response) => {
if (schema) {
var data = normalize_json(response.data, schema)
response.entities = data.entities
}
return response
})
}
function* fetchEventList(action) {
try {
const response = yield call(callRestApi, action.payload);
// here I want to call a component's method if possible
yield put({type: action.response.action_type_success, response});
} catch (e) {
}
}
I can think of two ways to do this, and wonder if one is prefered over another or if there's a better way?
method1:
I include the component in the action payload so that I can call the method
method2:
on action.response.action_type_success, change redux state.
Then, component's componentWillReceiveProps compare if the state variable changed and calls the method
The second. You are using redux-saga to handle side effects, so keep it that way. You could add a callback to the action as method1 but I wouldn't mix concepts.
If you update the store on success, it will re-render the component and as you said you could check the newly updated prop in componentWillReceiveProps and trigger the function, however, check nextProps instead of this.props (but I bet you already know that).
This way everything flows one way, no callback hell :) + you can easily test the component just by passing a prop.
Although it's not a bad pattern per se, passing callbacks would be bi-directional flow, which breaks the first rule of flux: Unidirectional flow.

Resources