1

I have a tempAddList that will contain a list of id's that I will set the state into the relInfo table and callback the addRelation function to submit the data. But when I run onAddClick for example if the tempAddList = [2,3,4] it would run addRelation 3 times with the latest setState id 4 but not 2 and 3. How would I get it to run for each individual id.

 onAddClick = () => {
    this.state.tempAddList.forEach((id) => {
        this.setState({
            relInfo: {
                ...this.state.relInfo,
                modId: id
            }
        }, () => this.addRelation());
    });
};

addRelation = () => {
    EdmApi.insertModifier(this.state.relInfo)
        .then(res => console.log(res))
        .catch(err => console.log(err));
};
KamSami
  • 387
  • 1
  • 4
  • 14

1 Answers1

2

The use of this.state together with setState is an antipattern. This may result in race conditions because state updates are asynchronous. This is use case for updater function.

Several setState calls will result in batch update, with addRelation called with latest updated state.

A workaround is to not update in batch and wait for state update, e.g. with await:

async onAddClick = () => {
    const setStateAsync = updater => new Promise(resolve => this.setState(updater, resolve));

    for (const id of this.state.tempAddList) {
        await setStateAsync(state => ({
            relInfo: {
                ...state.relInfo,
                modId: id
            }
        });
        this.addRelation();
    });
};

A preferable solution is to not rely on state updates in side effects (addRelation). The purpose of state is to be used in render. If state updates don't affect view (only the latest modId update will be shown), they aren't needed:

 onAddClick = () => {
    let { relInfo } = this.state;
    this.state.tempAddList.forEach((id) => {
        relInfo = { ...relInfo, modId: id };
        this.addRelation(relInfo);
    });

    this.setState({ relInfo });
};

addRelation = (relInfo) => {
    EdmApi.insertModifier(relInfo);
};

If modId isn't used in render, it could be excluded from the state. In this specific case the absence of updater function shouldn't be a problem because click handlers are triggered asynchronously, it's unlikely that they will cause race conditions by interfering with state updates.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • I get an error stating index.js:2178 Warning: Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`. – KamSami Dec 26 '18 at 20:17
  • Given that `onAddClick` is really triggered on click as the name suggests, it shouldn't result in this error, so the error is irrelevant to the code you posted. There may be another problem in your code. – Estus Flask Dec 26 '18 at 20:19
  • but it still only runs addRelation witht he latest Id – KamSami Dec 26 '18 at 20:21
  • Btw, what's the purpose of having `modId` in state? Do you display it in `render`? The answer may depend on that. – Estus Flask Dec 26 '18 at 20:32
  • Thank you so much! I don't display it on render but there is an object in my state that I manipulate based on given information by the user and I submit that object to add or delete information. – KamSami Dec 26 '18 at 20:47
  • Yes, this makes sense. Hope this helped. – Estus Flask Dec 26 '18 at 20:50