The useEffect function got into a loop...
That's because you create a new object for state
every time, and state
is listed as a dependency.
When using hooks, instead of building multi-part state objects, you're usually better off using smaller pieces. In this case, I'd use three:
const [shape, setShape] = useState("square");
const [width, setWidth] = useState(100);
const [height, setHeight] = useState(100);
useEffect(() => {
setShape(width === height ? "square" : "rect");
}, [width, height]);
Now, what you're setting (shape
) isn't a dependency of the effect hook, so it won't fire endlessly.
const {useState, useEffect} = React;
function Example() {
const [shape, setShape] = useState("square");
const [width, setWidth] = useState(100);
const [height, setHeight] = useState(100);
useEffect(() => {
setShape(width === height ? "square" : "rect");
}, [width, height]);
function onWidthInput({target: {value}}) {
setWidth(+value);
}
function onHeightInput({target: {value}}) {
setHeight(+value);
}
return <div>
<div>
Width: <input type="number" value={width} onInput={onWidthInput} />
</div>
<div>
Height: <input type="number" value={height} onInput={onHeightInput} />
</div>
<div>
Shape: {shape}
</div>
</div>;
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
You can probably do that with your existing state object if you want, though:
useEffect(() => {
setState(current => {
const {size: {width, height}} = current;
return {
...current,
shape: width === height ? "square" : "rect"
};
});
}, [state.size, state.size.width, state.size.height]);
const {useState, useEffect} = React;
function Example() {
const [state, setState] = useState({
shape: 'square',
size: {
width: 100,
height: 100
}
});
useEffect(() => {
setState(current => {
const {size: {width, height}} = current;
return {
...current,
shape: width === height ? "square" : "rect"
};
});
}, [state.size, state.size.width, state.size.height]);
function onSizePropInput({target: {name, value}}) {
setState(current => {
return {
...current,
size: {
...current.size,
[name]: +value
}
};
});
}
const {shape, size: {width, height}} = state;
return <div>
<div>
Width: <input type="number" name="width" value={width} onInput={onSizePropInput} />
</div>
<div>
Height: <input type="number" name="height" value={height} onInput={onSizePropInput} />
</div>
<div>
Shape: {shape}
</div>
</div>;
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
Note the use of the callback form of setState
in that. You want the then-current version of the state, not the state as it was when the effect callback was first created, since part of what you use for the update isn't a dependency (and so may be stale).