6

I have a parent functional component <EditCard/> that is a modal opened when the edit table row button is selected. This edit card contains a state variable called data which consists of the data from the table row being edited. I am setting/modifying state on <EditCard/> with the useState hook.

<EditCard/> has a child component <CategoryDropdown/> which is a dropdown that accepts a prop data.assignCategory as its selected value and a callback handleChange() which updates the state value data with the value selected from the dropdown.

When I select a new value from the dropdown handleChange() is called and setData() is called and I see the state being updated but <CategoryDropdown/> is not re-rendered with the new selected value.

EditCard Component Code

export default function EditCard(props) {
    const [data, setData] = useState(props.data);

    const handleChange = () => event => {
        let d = data;
        d.assignCategory = event.target.value;
        setData(d);
    };

    let assignCategoryCol = data.assignCategory !== undefined ? <AssignCategoryCol data={data} handleChange={handleChange}/> : <></>;

    return (
        <div>
            {assignCategoryCol}
            <Button>Update</Button>
        </div>
    )
}

{props.data.bucketTotal}`} <Lock/></Typography>)
};

const AssignCategoryCol = (props) => {
    return (
        <CategoryDropdown id={props.data.id} assignedCategory={props.data.assignCategory} handleDropdownChange={props.handleChange}/>)
};

const useStyles = makeStyles(theme => ({}));

CategoryDropdown Component

class CategoryDropdown extends Component {
    constructor(props) {
        super(props);
        //TODO Get Categories from DB and set default
        this.state = {
            categories: ['Select One', 'Category1', 'Category2', 'Category3'],
        };
    }

    render() {
        return (
            <div id={'categoryDropdown'}>
                <Select onChange={this.props.handleDropdownChange(this.props.id)} value={this.props.assignedCategory}>
                    {this.state.categories.map((category) => {
                        return <MenuItem value={category}>{category}</MenuItem>
                    })}
                </Select>
            </div>
        )
    }
}


const styles = theme => ({});

export default withStyles(styles)(CategoryDropdown)
Jonny B
  • 680
  • 2
  • 6
  • 29
  • 1
    React will not know that just one key in the object (that is stored to the state) has changed. You need to replace the entire object all together. Do this by copying the object that is in the state (creating a new object), changing that object with the value you want to add. Then, store this new object to the state. In other words entirely replace the original object. – Maiya Sep 25 '19 at 21:41
  • 1
    https://stackoverflow.com/questions/43638938/updating-an-object-with-setstate-in-react – Maiya Sep 25 '19 at 21:41

3 Answers3

20

In order for React to know that something has changed in the state, you need to replace the current object with an entirely new object. Currently, react sees that it is the same object, so does nothing. It does not search the object to see if any properties have changed in it.

In your handleChange event handler, you have:

let d = data;

This is not copying the data object. Instead, now the variable "d" is pointing to the same object (in memory) that the variable "data" points to.

(For more info, read about "pass by reference" vs "pass by value").

To fix that, see the destructuring/ copying below:

const [data, setData] = useState(props.data)

const handleChange = event => {
        let newData = {...data} //copy the object
        newData.assignCategory = event.target.value;
        setData(newData);
 };
Maiya
  • 932
  • 2
  • 10
  • 26
  • I tried this solution but it didn't work, by copying the object or array into new variable and call respective setfunction call, but still it didn't update children elements – Praveen Saboji Jun 03 '20 at 07:31
  • It is not that you simply set it to a new variable, it is that you create a whole different object. If I have var A = data. And then I say var B = data. Now, var B is pointing to the same object that var A is pointing to. (Look up "pass by reference" vs. "pass by value" to understand why). Are you being sure to copy your original object or array as a totally new object or array? – Maiya Jun 06 '20 at 11:00
4

For me I had this issue with HTML5 video not updating when the url updated by setState. I also was already doing the above as mentioned by Maiya.

My fix was by adding a key to the element.

e.g.

<video key={ item.videoUrl } />

Dylan w
  • 2,565
  • 1
  • 18
  • 30
  • Thanks. For some reason, one of my child components does not rerender unless I have this key. – Taku Sep 13 '21 at 16:04
0

You can set state like this because react re-renders only if object changes:

const handleChange = () => event => {
     setData({...data, assignCategory: event.target.value})
};
Aziza Kasenova
  • 1,501
  • 2
  • 10
  • 22