Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call function only after multiple states have completed updating

Logic:

I have a dialog for converting units. It has two stages of choice for the user: units to convert from and units to convert to. I keep this stage as a state, dialogStage, for maintainability as I'm likely going to need to reference what stage the dialog is in for more features in the future. Right now it's being used to determine what action to take based on what unit is clicked.

I also have a state, dialogUnits, that causes the component to rerender when it's updated. It's an array of JSX elements and it's updated via either foundUnitsArray or convertToUnitsArray, depending on what stage the dialog is at. Currently both states, dialogStage and dialogUnits, are updated at the same moment the problem occurs.

enter image description here

Problem:

When choosing the convertTo units, displayConversionTo() was still being called, as though dialogStage was still set to 'initial' rather than 'concertTo'. Some debugging led to confusion as to why the if (dialogStage == 'initial') was true when I'd set the state to 'convertTo'.

I believe that my problem was that the dialogStage state wasn't updated in time when handleUnitClick() was called as it's asynchronous. So I set up a new useEffect that's only called when dialogStage is updated.

The problem now is that the dialog shows no 'convertTo' units after the initial selection. I believe it's now because dialogUnits hasn't updated in time? I've swapped my original problem from one state not being ready to another state not being ready.

Question

How do I wait until both states are updated before continuing to call a function here (e.g. handleUnitClick()?).

Or have I mistaken what the problem is?

I'm new to react and, so far, I'm only familiar with the practice of state updates automatically rerendering a component when ready, unless overridden. Updating dialogUnits was displaying new units in the dialog until I tried to update it only when dialogStage was ready. It feels like an either/or situation right now (in terms of waiting for states to be updated) and it's quite possible I've overlooked something more obvious, as it doesn't seem to fit to be listening for state updates when so much of ReactJs is built around that already being catered for with rerenders, etc.

Component code:

function DialogConvert(props) {

    const units = props.pageUnits;

    const [dialogUnits, setDialogUnits] = useState([]);
    const [dialogStage, setDialogStage] = useState('initial');

    let foundUnitsArray = [];
    let convertToUnitsArray = [];

    units.unitsFound.forEach(element => {
        foundUnitsArray.push(<DialogGroupChoice homogName={element} pcbOnClick={handleUnitClick} />);
    });

    useEffect(() => {
        setDialogUnits(foundUnitsArray);
    }, []);


    useEffect(() => {
        if (dialogStage == "convertTo") {
            setDialogUnits(convertToUnitsArray);
        }
    }, [dialogStage]);
    
    
    function handleClickClose(event) {
        setDialogStage('initial');
        props.callbackFunction("none");
    }


    function handleUnitClick(homogName) {
        if (dialogStage == "initial") {
            // getConversionChoices is an external function that returns an array. This returns fine and as expected
            const choices = getConversionChoices(homogName);
            displayConversionTo(choices);
        } else if (dialogStage == "convertTo") {
            // Can't get this far
            // Will call a function not displayed here once it works
        }
    }


    function displayConversionTo(choices) {
        let canConvertTo = choices[0]["canconvertto"];
        if (canConvertTo.length > 0) {
            canConvertTo.forEach(element => {
                convertToUnitsArray.push(<DialogGroupChoice homogName={element} pcbOnClick={handleUnitClick} />);
            });
            setDialogStage('convertTo');
        }
    }
    

    return (
        <React.Fragment>
            <div className="dialog dialog__convertunits" style={divStyle}>
                <h2 className="dialogheader">Convert Which unit?</h2>
                <div className='js-dialogspace-convertunits'>
                <ul className="list list__convertunits">
                    {dialogUnits}
                </ul>
                </div>
                <button className='button button__under js-close-dialog' onClick={handleClickClose}>Close</button>
            </div>
        </React.Fragment>
    )
}
like image 846
biscuitstack Avatar asked Aug 12 '20 14:08

biscuitstack


1 Answers

So, there are some issues with your implementations:

  1. Using non-state variables to update the state in your useEffect:

Explanation: In displayConversionTo when you run the loop to push elements in convertToUnitsArray, and then set the state dialogStage to convertTo, you should be facing the issue that the updated values are not being rendered, as the change in state triggers a re-render and the convertToUnitsArray is reset to an empty array because of the line:

let convertToUnitsArray = [];

thus when your useEffect runs that is supposed to update the dialogUnits to convertToUnitsArray, it should actually set the dialogueUnits to an empty array, thus in any case the updated units should not be visible on click of the initial units list.

useEffect(() => {
  if (dialogStage == "convertTo") {
    // as your convertToUnitsArray is an empty array
    // your dialogue units should be set to an empty array.
    setDialogUnits(convertToUnitsArray)
  }
}, [dalogStage]);
  1. You are trying to store an array of react components in the state which is not advisable: http://web.archive.org/web/20150419023006/http://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html#what-components-should-have-state

Also, refer https://stackoverflow.com/a/53976730/10844020

Solution: What you can do is try to save your data in a state, and then render the components using that state, I have created a code sandbox example how this should look for your application. I have also made some changes for this example to work correctly.

like image 81
Yash Bansal Avatar answered Sep 30 '22 14:09

Yash Bansal