Input values not updating with nested state - reactjs

I've searched around quite a bit for this, but I'm not able to find a React Hooks example that works where the state has nested objects. I've been mostly following this tutorial. I've created an example fraction calculator component, wherein I want to recalculate the result whenever either input field changes:
import React, { useState } from 'react';
const initialState = {
numerator: {
value: 1
},
denominator: {
value: 2
}
};
export const Fraction = () => {
const [elements, setElements] = useState(initialState);
function changeNumerator(val) {
elements.numerator.value = parseInt(val);
setElements(elements);
}
function changeDenominator(val) {
elements.denominator.value = parseInt(val);
setElements(elements);
}
function calcResult(num, denom) {
if (denom === 0) return;
return num / denom;
}
return <div>
<input
value = {elements.numerator.value}
onChange = {e => changeNumerator(e.target.value)}
/>
/
<input
value = {elements.denominator.value}
onChange = {e => changeDenominator(e.target.value)}
/>
=
<b>{calcResult(elements.numerator.value, elements.denominator.value)}</b>
</div>
}
With this approach, the input values don't update when I change them, though the state seems to be changing (according to console logs and the React dev tools).
I thought maybe I was mutating state, so I attempted the update like this, but still no luck:
function changeNumerator(val) {
let newElements = elements;
newElements.numerator.value = parseInt(val);
setElements(newElements);
}
What am I missing? Is it a mistake to compose state in a nested manner like this? Should numerator and denominator be broken apart? In my real-world example, the structure will be more complex; I know I could make this specific example more straightforward by not structuring the state this way, but I'm looking specifically for how to deal with nested objects, unless there's a compelling reason I should not do that.

since the object pointer hasn't changed, React will not trigger another render.
change this:
function changeNumerator(val) {
elements.numerator.value = parseInt(val);
setElements(elements);
}
to this:
function changeNumerator(val) {
setElements({
...elements,
numerator: {
value: parseInt(val, 10),
},
});
}
and do the same thing with the other function.
In this way, you create a new object, and React will recognize the change.

Related

How to debug "Warning: Maximum update depth exceeded" in React [duplicate]

Is there an easy way to determine which variable in a useEffect's dependency array triggers a function re-fire?
Simply logging out each variable can be misleading, if a is a function and b is an object they may appear the same when logged but actually be different and causing useEffect fires.
For example:
React.useEffect(() => {
// which variable triggered this re-fire?
console.log('---useEffect---')
}, [a, b, c, d])
My current method has been removing dependency variables one by one until I notice the behavior that causes excessive useEffect calls, but there must be a better way to narrow this down.
I ended up taking a little bit from various answers to make my own hook for this. I wanted the ability to just drop something in place of useEffect for quickly debugging what dependency was triggering useEffect.
const usePrevious = (value, initialValue) => {
const ref = useRef(initialValue);
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => {
const previousDeps = usePrevious(dependencies, []);
const changedDeps = dependencies.reduce((accum, dependency, index) => {
if (dependency !== previousDeps[index]) {
const keyName = dependencyNames[index] || index;
return {
...accum,
[keyName]: {
before: previousDeps[index],
after: dependency
}
};
}
return accum;
}, {});
if (Object.keys(changedDeps).length) {
console.log('[use-effect-debugger] ', changedDeps);
}
useEffect(effectHook, dependencies);
};
Below are two examples. For each example, I assume that dep2 changes from 'foo' to 'bar'. Example 1 shows the output without passing dependencyNames and Example 2 shows an example with dependencyNames.
Example 1
Before:
useEffect(() => {
// useEffect code here...
}, [dep1, dep2])
After:
useEffectDebugger(() => {
// useEffect code here...
}, [dep1, dep2])
Console output:
{
1: {
before: 'foo',
after: 'bar'
}
}
The object key '1' represents the index of the dependency that changed. Here, dep2 changed as it is the 2nd item in the dependency, or index 1.
Example 2
Before:
useEffect(() => {
// useEffect code here...
}, [dep1, dep2])
After:
useEffectDebugger(() => {
// useEffect code here...
}, [dep1, dep2], ['dep1', 'dep2'])
Console output:
{
dep2: {
before: 'foo',
after: 'bar'
}
}
#simbathesailor/use-what-changed works like a charm!
Install with npm/yarn and --dev or --no-save
Add import:
import { useWhatChanged } from '#simbathesailor/use-what-changed';
Call it:
// (guarantee useEffect deps are in sync with useWhatChanged)
let deps = [a, b, c, d]
useWhatChanged(deps, 'a, b, c, d');
useEffect(() => {
// your effect
}, deps);
Creates this nice chart in the console:
There are two common culprits:
Some Object being pass in like this:
// Being used like:
export function App() {
return <MyComponent fetchOptions={{
urlThing: '/foo',
headerThing: 'FOO-BAR'
})
}
export const MyComponent = ({fetchOptions}) => {
const [someData, setSomeData] = useState()
useEffect(() => {
window.fetch(fetchOptions).then((data) => {
setSomeData(data)
})
}, [fetchOptions])
return <div>hello {someData.firstName}</div>
}
The fix in the object case, if you can, break-out a static object outside the component render:
const fetchSomeDataOptions = {
urlThing: '/foo',
headerThing: 'FOO-BAR'
}
export function App() {
return <MyComponent fetchOptions={fetchSomeDataOptions} />
}
You can also wrap in useMemo:
export function App() {
return <MyComponent fetchOptions={
useMemo(
() => {
return {
urlThing: '/foo',
headerThing: 'FOO-BAR',
variableThing: hash(someTimestamp)
}
},
[hash, someTimestamp]
)
} />
}
The same concept applies to functions to an extent, except you can end up with stale closures.
UPDATE
After a little real-world use, I so far like the following solution which borrows some aspects of Retsam's solution:
const compareInputs = (inputKeys, oldInputs, newInputs) => {
inputKeys.forEach(key => {
const oldInput = oldInputs[key];
const newInput = newInputs[key];
if (oldInput !== newInput) {
console.log("change detected", key, "old:", oldInput, "new:", newInput);
}
});
};
const useDependenciesDebugger = inputs => {
const oldInputsRef = useRef(inputs);
const inputValuesArray = Object.values(inputs);
const inputKeysArray = Object.keys(inputs);
useMemo(() => {
const oldInputs = oldInputsRef.current;
compareInputs(inputKeysArray, oldInputs, inputs);
oldInputsRef.current = inputs;
}, inputValuesArray); // eslint-disable-line react-hooks/exhaustive-deps
};
This can then be used by copying a dependency array literal and just changing it to be an object literal:
useDependenciesDebugger({ state1, state2 });
This allows the logging to know the names of the variables without any separate parameter for that purpose.
As far as I know, there's no really easy way to do this out of the box, but you could drop in a custom hook that keeps track of its dependencies and logs which one changed:
// Same arguments as useEffect, but with an optional string for logging purposes
const useEffectDebugger = (func, inputs, prefix = "useEffect") => {
// Using a ref to hold the inputs from the previous run (or same run for initial run
const oldInputsRef = useRef(inputs);
useEffect(() => {
// Get the old inputs
const oldInputs = oldInputsRef.current;
// Compare the old inputs to the current inputs
compareInputs(oldInputs, inputs, prefix)
// Save the current inputs
oldInputsRef.current = inputs;
// Execute wrapped effect
func()
}, inputs);
};
The compareInputs bit could look something like this:
const compareInputs = (oldInputs, newInputs, prefix) => {
// Edge-case: different array lengths
if(oldInputs.length !== newInputs.length) {
// Not helpful to compare item by item, so just output the whole array
console.log(`${prefix} - Inputs have a different length`, oldInputs, newInputs)
console.log("Old inputs:", oldInputs)
console.log("New inputs:", newInputs)
return;
}
// Compare individual items
oldInputs.forEach((oldInput, index) => {
const newInput = newInputs[index];
if(oldInput !== newInput) {
console.log(`${prefix} - The input changed in position ${index}`);
console.log("Old value:", oldInput)
console.log("New value:", newInput)
}
})
}
You could use this like this:
useEffectDebugger(() => {
// which variable triggered this re-fire?
console.log('---useEffect---')
}, [a, b, c, d], 'Effect Name')
And you would get output like:
Effect Name - The input changed in position 2
Old value: "Previous value"
New value: "New value"
There’s another stack overflow thread stating you can use useRef to see a previous value.
https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
The React beta docs suggest these steps:
Log your dependency array with console.log:
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);
Right-click on the arrays from different re-renders in the console and select “Store as a global variable” for both of them. It may be important not to compare two sequential ones if you are in strict mode, I'm not sure.
Compare each of the dependencies:
Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
This question was answered with several good and working answers, but I just didn't like the DX of any of them.
so I wrote a library which logs the dependencies that changed in the easiest way to use + added a function to log a deep comparison between 2 objects, so you can know what exactly changed inside your object.
I called it: react-what-changed
The readme has all of the examples you need.
The usage is very straight forward:
npm install react-what-changed --save-dev
import { reactWhatChanged as RWC } from 'react-what-changed';
function MyComponent(props) {
useEffect(() => {
someLogic();
}, RWC([somePrimitive, someArray, someObject]));
}
In this package you will also find 2 useful functions for printing deep comparison (diffs only) between objects. for example:
import { reactWhatDiff as RWD } from 'react-what-changed';
function MyComponent(props) {
useEffect(() => {
someLogic();
}, [somePrimitive, someArray, someObject]);
RWD(someArray);
}

Can I use ReactElement as useState argument?

I'm new in React and I wonder is using ReactElement as useState argument normal?
I try to do it and everything works fine. Is it anti-pattern or it's OK?
Unfortunately, I didn't find any information about it in documentation
const [infoBox, setInfobox] = useState<ReactElement|null>(null);
const catalogLoadedDataEmpty = useSelector(getCatalogLoadedDataEmptySelector);
const catalogHasErrors = useSelector(getCatalogHasErrorsSelector);
...
useEffect(() => {
let infoBoxTitle;
if (catalogLoadedDataEmpty) {
infoBoxTitle = t('pages.Brands.errors.noResults.title');
} else if (catalogHasErrors) {
infoBoxTitle = errorsByErrorCode[EErrorCodes.UNRECOGNIZED_ERROR](t);
} else {
setInfobox(null);
return;
}
setInfobox(<InfoBox
className={catalogInfoBoxClassname}
iconName={EInfoBoxIcon.error}
title={infoBoxTitle}
description={noResultsDescription}
/>);
}, [catalogLoadedDataEmpty, catalogHasErrors]);
You can, but it's easy to create bugs where you expect the page to update, but it doesn't, because you forgot to update the state. It's usually better to save data in state, and then use that data to render fresh elements on each render.
And in your case i'd go one step further: this shouldn't be a state variable at all. The values catalogLoadedDataEmpty and catalogHasErrors are enough to determine the desired output directly. You can thus remove the use effect, and in so doing get rid of the double-render that you currently have:
const catalogLoadedDataEmpty = useSelector(getCatalogLoadedDataEmptySelector);
const catalogHasErrors = useSelector(getCatalogHasErrorsSelector);
let infoBoxTitle;
if (catalogLoadedDataEmpty) {
infoBoxTitle = t('pages.Brands.errors.noResults.title');
} else if (catalogHasErrors) {
infoBoxTitle = errorsByErrorCode[EErrorCodes.UNRECOGNIZED_ERROR](t);
}
const infoBox = infoBoxTitle ? (
<InfoBox
className={catalogInfoBoxClassname}
iconName={EInfoBoxIcon.error}
title={infoBoxTitle}
description={noResultsDescription}
/>
) : null

Redux useDispatch Hook on click event of nodeListOf<HTMLElement>

I have a component that renders a grid. I'm trying to count the moves made (onclick of each grid box).
But when I include dispatch on the eventListener it returns an error. The moveCharacter function is supposed to move the character around those boxes and its working well. I just need a way to be able to count the moves made (onclick of each box) and store in general state to use in another component.
function GridBoxes():JSX.Element{
const gridValue: number = useSelector<IStateProps, IStateProps["grid"]>((state)=> state.grid);
const totalMoves: number = useSelector<IStateProps, IStateProps["totalMoves"]>((state)=> state.totalMoves);
const history = useHistory();
const dispatch = useDispatch();
useEffect(()=>{
const boxElements = document.querySelectorAll('.box');
boxElements.forEach(element => {
element.addEventListener('click', (e)=>{
moveCharacter(element.id, getCharacterPosition(boxElements));
dispatch({type: "COUNT_MOVES", totalMoves: totalMoves + 1});
console.log("moves");
});
});
});
useEffect(()=> {
let t = setInterval(()=> {
const timeSpent = document.getElementById("time-spent");
const indicator = document.getElementById("indicator");
let countDown = Number(timeSpent?.innerHTML);
countDown = countDown - 1;
let timeTakenPercent = ((gridValue*3) - countDown) / (gridValue*3) * 100;
dispatch({type:"SET_TIME", payload: (gridValue*3)-countDown});
(indicator as any).style.width = timeTakenPercent+"%";
(timeSpent as any).innerHTML = countDown.toString().length < 2 ? countDown.toString().padStart(2,"0") : countDown;
if(countDown < 1){
clearInterval(t);
history.push("/over");
play("https://freesound.org/data/previews/175/175409_1326576-lq.mp3");
}
}, 1000);
});
const [emptyBox, characterBox, foodBox]: string[] = ['<div class="box"></div>',`<div class="box"><img src=${assets.character} /></div>`, `<div class="box"><img src=${assets.food} /></div>`];
const generatedGrids: string[][] = gridPattern({grid: gridValue, box:emptyBox, character: characterBox, food: foodBox});
return (
<>
{generatedGrids.map((box, i)=> {
box = setElementId(box,i);
return <div key={i} className="col">{ReactHtmlParser(box.join(" "))}</div>;
})}
</>
);
}
export default GridBoxes;
Error gotten
The error does not really come from Redux, but from some manual DOM manipulation you are doing.
Your dispatch call only surfaces it: when calling dispatch, redux state will change which will trigger a React rerender - while you are manually fiddling around with the DOM and the two things collide.
My question is: why do you do all this? The whole point of React is that is builds the DOM for you and attaches event handlers for you. At no point should you be using something like react-html-parser, manually concatenate html strings, manually call addEventListener, modify innerHTML, style or anything else on DOM elements.
Let React build your DOM for you and this error will go away.

react.js useState hook is assigned a value but not used, but that is ok

I have this setup so that the render is forces when they click by simply updating the state of a hook. Is there a nicer or cleaner way to do this.. here is some code...
const [click, setClick] = useState();
function handle1Click() {
props.UserInfoObject.setWhichPlot(1)
setClick(1000 * 60 * 5)
}
return (
<div>
<button onClick={handle1Click}>5 Minutes</button>
</div>
I came accross this which is another option but I am trying to be as optimal as possible so I am unsure which to use, or if there is another method?
handleClick = () => {
// force a re-render
this.forceUpdate();
};
I only mention this because of the warning that pops up stating this "'click' is assigned a value but never used no-unused-vars
***EDIT
adding the UserInfoObject class for reference
class UserInformation {
constructor(airValue, waterValue){
this.airValue = airValue;
this.waterValue = waterValue;
this.getCalibrationsFlag = false;
this.numberDevices = 0;
this.deviceName = 'defaultName';
this.currentlyChangingName = false;
this.whichPlot = 1;
}
setAirValue(number) {
this.airValue = number;
}
setWaterValue(number) {
this.waterValue = number;
}
setNumberDevices(int){
this.numberDevices = int;
}
setDeviceName(name){
this.deviceName = name;
}
setCurrentlyChangingName(boolean){
this.currentlyChangingName = boolean;
}
setWhichPlot(number){
this.whichPlot = number;
}
}
let UserInfoObject = new UserInformation(10000, -10);
With React, you should generally use pure, functional programming when possible. Mutating objects makes it much, much harder to do things properly.
Create state of the UserInformation instead. When it needs to be changed, instead of mutating the existing object, create a new object. The fact that this object is new will tell React that the component needs to re-render.
const [userInformation, setUserInformation] = useState({
airValue, // this should be in the outer scope
waterValue, // this should be in the outer scope
getCalibrationsFlag: false,
numberDevices: 0,
// ...
});
Do that in the parent component, then pass both userInformation and setUserInformation down as props. In the child, handle1Click can then be changed to:
const handle1Click = () => setUserInformation({
...userInformation,
whichPlot: 1,
});
Neither state nor props should ever be mutated in React.

Two way data binding in React with Hooks

I am building an application with React where two components should be able to change state of each other:
Component A -> Component B
Component B -> Component A
The Component A is a set of buttons and the Component B is an input element.
I managed to make it work only in one way, A -> B or just B -> A, but can't make it work both. It works partly with use of useEffect hook, but with bugs and this is really stupid idea, I think.
I have read a lot that React don't work this way, but is there any roundabouts how it is possible to make it work? I really need this 2-way data binding for my application.
Thanks for any help!
The state for buttons is located in digits variable from custom context hook (useStateContext) as an array.
import { useStateContext } from "components/StateProvider/Context";
import { useState, useEffect } from "react";
import { baseConvert } from "utility/baseConvert";
const NumberInput = () => {
const [ { digits, baseIn, baseOut }, dispatch ] = useStateContext();
const convertedValue = baseConvert({
digits,
baseIn,
baseOut
});
const [ inputValue, setInputValue ] = useState(convertedValue);
/* useEffect(() => {
setInputValue(convertedValue)
}, [ digits, baseIn, baseOut ]) */
const regex = /^[A-Z\d]+$/;
const handleInput = ({ target: { value }}) => {
if (value === "") setInputValue(0);
console.log(value);
if (regex.test(value)) {
setInputValue(value);
dispatch({
type: "setDigits",
digits: baseConvert({
digits: value.split("").map(Number),
baseIn: baseOut,
baseOut: baseIn
})
})
}
};
return (
<input type="text" onChange={handleInput} value={inputValue} />
);
};
export default NumberInput;
Components should not directly manipulate the state of other components. If you need to have shared data, bring the state to a parent component and pass callbacks to the children that can change the state.
For example:
function ParentComponent() {
const [currentVal, setCurrentVal] = useState(0);
return
<>
<Child1 value={currentVal} onChange={setCurrentVal}/> // you might also pass a function that does some other logic and then calls setCurrentVal
<Child2 value={currentVal} onChange={setCurrentVal}/>
</>
}
#Jeff Storey
The solution seems a bit wrong. The first index is the value and the second index is the function which updates the value.
It should be:
const [currentVal, setCurrentVal] = useState(0);

Resources