Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React useState hook not triggering re-render of child component

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)
like image 964
Jonny B Avatar asked Nov 29 '22 08:11

Jonny B


1 Answers

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);
 };
like image 92
Maiya Avatar answered Dec 04 '22 08:12

Maiya