Immutable update pattern in react for nested data - javascript

Redux specifies that "The key to updating nested data is that every level of nesting must be copied and updated appropriately."
// changing reference in top level
const newState = {...oldState}
newState.x.y.z = 10;
setState(newState)
// or updating reference in all levels
const newState = {...oldState}
newState.x = {...newState.x}
newState.x.y = {..newState.x.y}
newState.x.y.z = 10
Is this is same for react, can't find the documentation regarding this on react.

Yes, you should apply this concept when updating your state in React also. If you don't, you can have issues with components not re-rendering when you update your state. Normally, React will rerender your component when your state changes, in both of your examples you're doing this, as you're creating a new object reference at the top-level which will cause React to rerender your component and any of its child components (ie: the children components also get rerender when the parent component rerenders). However, there is an exception to this. If you've memoized a component to help with efficiency using React.memo(), your memoized child component will only update when the props that it's passed change. If you are passing a child component an object that is nested within your original state, React won't rerender that component unless that object reference has changed. This could lead to issues if your memoized children components are trying to use state from your parent component.
For example:
const { useState, memo } = React;
const App = () => {
const [state, setState] = useState({
x: {
y: {
z: 0
}
}
});
const changeStateBad = () => {
// Your first technique (can cause issues)
const newState = {...state};
newState.x.y.z += 1;
setState(newState);
}
const changeStateGood = () => {
// Your second technique for updating at all levels (just rewritten slightly)
const newState = {
...state,
x: {
...state.x,
y: {
...state.x.y,
z: state.x.y.z + 1
}
}
};
setState(newState);
}
return <div>
<NumberDisplay x={state.x} />
<br />
<button onClick={changeStateGood}>Good update</button>
<br />
<button onClick={changeStateBad}>Bad update</button>
</div>
}
// A memoized component that only rerenders when its props update
const NumberDisplay = memo(({x}) => {
return <h1>{x.y.z}</h1>;
});
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>

data types in JS can be divided into primitive data types (eg. string, number) and non-primitive data types (eg. object),
primitive data types can not be modified (eg. using String.prototype.replace will always return a new instance of a string) while non-primitive data types can - and the reference pointing to that data will be "updated" as well (that's a big simplification, but executing eg. x.y = 2 will not create a new instance of x - instead the x will be keept the same in every place it's referenced),
which means to detect a change in the new version of the state (in a most performant way) it is required to create a new instance of an Object, Array (and other non-primitive data type representations)
// changing reference in top level
const newState = { ...oldState }
// changing nested reference
const newState = {
...oldState,
x: {
...oldState.x,
y: 2 // new "y" value
}
}
// changing nested reference, another example
const newState = {
...oldState,
x: {
...oldState.x,
y: {
...oldState.x.y,
z: 'test' // new "z" value
}
}
}
you can read more how change is detected on a basic level here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#comparing_objects

Related

convert react class to react hooks useState?

Can someone help me convert this to react hooks I believe it would be react useState()
https://github.com/react-grid-layout/react-grid-layout/blob/master/test/examples/7-localstorage.jsx
import React from "react";
import RGL, { WidthProvider } from "react-grid-layout";
const ReactGridLayout = WidthProvider(RGL);
const originalLayout = getFromLS("layout") || [];
/**
* This layout demonstrates how to sync to localstorage.
*/
export default class LocalStorageLayout extends React.PureComponent {
static defaultProps = {
className: "layout",
cols: 12,
rowHeight: 30,
onLayoutChange: function() {}
};
constructor(props) {
super(props);
this.state = {
layout: JSON.parse(JSON.stringify(originalLayout))
};
this.onLayoutChange = this.onLayoutChange.bind(this);
this.resetLayout = this.resetLayout.bind(this);
}
resetLayout() {
this.setState({
layout: []
});
}
onLayoutChange(layout) {
/*eslint no-console: 0*/
saveToLS("layout", layout);
this.setState({ layout });
this.props.onLayoutChange(layout); // updates status display
}
render() {
return (
<div>
<button onClick={this.resetLayout}>Reset Layout</button>
<ReactGridLayout
{...this.props}
layout={this.state.layout}
onLayoutChange={this.onLayoutChange}
>
<div key="2" data-grid={{ w: 2, h: 3, x: 8, y: 0 }}>
<span className="text">5</span>
</div>
</ReactGridLayout>
</div>
);
}
}
function getFromLS(key) {
let ls = {};
if (global.localStorage) {
try {
ls = JSON.parse(global.localStorage.getItem("rgl-7")) || {};
} catch (e) {
/*Ignore*/
}
}
return ls[key];
}
function saveToLS(key, value) {
if (global.localStorage) {
global.localStorage.setItem(
"rgl-7",
JSON.stringify({
[key]: value
})
);
}
}
if (process.env.STATIC_EXAMPLES === true) {
import("../test-hook.jsx").then(fn => fn.default(LocalStorageLayout));
}
trying my best to understand react classes since I only know react hooks so any patince help would be so amazing and helpful please and thank you
React classes are simple if you understand the lifecycle of a component.
A component is instantiated, then mounted (associated with DOM) & then it renders.
After every update (state or props) it's re-rendered.
Constructor
The instantiation of a component or any JS class happens in the constructor. It is run only once.
Since class components inherit from React.Component, they pass the props to React.Component by calling super(props). This initializes the props field.
super calls should be the 1st line in a constructor.
You cannot access the following in the constructor: window, document, fetch, localStorage, sessionStorage.
render
The render method returns the rendered Element/Fragment. It corresponds to the returned part of a function component. It runs every time the component is rendered.
Event listener binding
The hardest part of classes is the event method binding.
The implicit this object, is necessary to access state, props & other component methods. However, inside an event listener method*. it refers to the event target. Hence the bind calls. Nevertheless, this problem, can be bypassed by using arrow methods as the arrow functions do not have their own this (ie. this does not refer to event target).
Function components don't have this problem, as they don't need this.
State
The state is initialized in the constructor.
Unlike function components, classes treat the state as a single object.
Instead of,
let [x, setX] = useState(0); let [y, setY] = useState(1);
In classes it's:
this.state = {x:0, y:1};
And to update state, we use the setState method,
this.setState({x: 3}); // updates only x, & y is unchanged: setX(3)
this.setState({x: 3, y: 4}); // updates both x & y: setX(3); setY(4)
this.setState((state)=>({ y: state.y - 1)) // updates y using previous value: setY(y=>y-1)
An advantage of classes is that we can update a state property using another state property's previous value.
this.setState((state)=>({x: (y%2)? 100: 50})); // updates x using y's value
Also, the state can be updated with respect to props:
this.setState((state,props)=>({x: state.x + props.dx}));
In addition, setState has an optional 2nd callback argument. This callback is run immediately after the state update.
this.setState(state=>({x: state.x+1}), ()=> {
// called after x is updated
sessionStorage.setItem('x', this.state.x);
});
// function version
setX(x=>x+1);
useEffect(()=> {
localStorage.setItem('x', x);
}, [x]); // runs every time x is updated
Mounting
After mounting, componentDidMount() is called. Now, component can access the window & document objects.
Here here, you can
Call setInterval/setTimeout
Update state with API calls (fetch) or storage (localStorage & sessionStorage)
It's equivalent to useEffect(()=> {}, [])
Unmounting
Before unmounting componentWillUnmount() is called. Typically, clearTimeout/clearInterval are called here to clean up the component.
It's equivalent to the returned method of useEffect.
useEffect(()=>{
//....
return ()=> {
// componentWillUnmount code
};
}, [])

Is it good Practice to Stringify Object for useState() Hook To Avoid Re-Rendering

According to this and that question the spread operator seems to be used for updating an object managed in a useState hook.
I created a super simple example and found out, that even when the content of the object does not change, a re-render is triggered (which is clear, because the object changed):
import React from "react";
function useFriendStatus() {
const [person, setPersonProps] = React.useState({name:'Mark',age:23});
React.useEffect(() => {
console.log("rerender");
const interval = setInterval(() => {
setPersonProps({...person}); //simply set the object again -> no content changed
console.log('update');
}, 1000);
return () => clearInterval(interval);
}, [person]);
return person;
}
export default function App() {
const person = useFriendStatus();
return <div className="App">Hello World: {"" + person.name}</div>;
}
Here you see a screenshot from my profiler which shows that a re-rendering seems to be fired (even if the displayed name did not change):
I am wondering if this is a "good practice" as EVERYTHING seems to be re-rendered. Sometimes you get deeply nested objects from an API and breaking them down to super-simple non-object userState hooks is not possible.
Wouldn't it be better to Stringify everything?
import React from "react";
function useFriendStatus() {
const [person, setPersonProps] = React.useState(JSON.stringify({name:'Mark',age:23}));
React.useEffect(() => {
console.log("rerender");
const interval = setInterval(() => {
const personCopy=JSON.parse(person);
setPersonProps(JSON.stringify({...personCopy}));
console.log('update');
}, 1000);
return () => clearInterval(interval);
}, [person]);
return person;
}
export default function App() {
const person = JSON.parse(useFriendStatus());
return <div className="App">Hello World: {"" + person.name}</div>;
}
How do you handle that in practice?
I created a super simple example and found out, that even when the content of the object does not change, a re-render is triggered (which is clear, because the object changed)
It has nothing to do with the "content" of the object, your component re-renderers because you create a shallow copy {...person} of an object (changing its reference).
On render phase React makes a shallow comparison with the previous state in order to decide if render will occur, and in javascript, {} === {} is always false.
the spread operator seems to be used for updating an object managed in a useState hook.
As the state should be treated as immutable it is common to use the spread operator to make a shallow copy.
"It should only re-render if the name property of the object changes"
It is common to just add a condition before calling setState:
React.useEffect(() => {
const newPerson = { ...person }; // fetch from some source
// or check if person.name !== newPerson.name
if (!isEqual(person, newPerson)) {
setPerson(newPerson);
}
}, [person]);

How does React links dependencies in `useEffect` hook to state?

I imagine very good how React's "useState" hook works. State - is a list of values mapped to certain component, and theres also index of useState call, which is requesting state value index. When React renderer encouters component, it writes it into outer reference, to which useState have access, so it returns state value by useState call index, from "current" component's state list.
But I have no idea, how does React identify and link dependencies in "useEffect" to actual state which obviously is plain values like numbers and strings. How is that done?
import React from 'react'
function Component() {
const [plainString, setPlainString] = React.useState('Hello')
const [anotherPlainString, setAnotherPlainString] = React.useState('Good bye')
// update `plainString` after mount
React.useEffect(() => {
setPlainString('Updated Hello')
}, [])
// callback get triggered after `plainString` update!
// but how it links dependency to state if its just 'Hello' string?
React.useEffect(
() => {
console.log(plainString) // "Updated Hello"
},
[plainString]
)
return <h1>plainString</h1>
}
It's simply whether the values in the dependency array change, they don't even have to be stateful.
useEffect(() => {
}, dependencyArray) // React loops over these values to check for changes
You can test this pretty easily with random numbers
dependencyArray = [Math.random(), Math.random(), Math.random()]
Which will very likely create an array with different values on rerenders.
If the first time it gets [0.1, 0.2, 0.3] and the next time it gets [0.1, 0.2, 0] It will trigger the effect again because the values in the array are not the same.
This is how React maintains hooks mappings. This is just to give an idea of hooks implementation.
const MyReact = (function() {
let hooks = [],
currentHook = 0 // array of hooks, and an iterator!
return {
render(Component) {
const Comp = Component() // run effects
Comp.render()
currentHook = 0 // reset for next render
return Comp
},
useEffect(callback, depArray) {
const hasNoDeps = !depArray
const deps = hooks[currentHook] // type: array | undefined
const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true
if (hasNoDeps || hasChangedDeps) {
callback()
hooks[currentHook] = depArray
}
currentHook++ // done with this hook
},
useState(initialValue) {
hooks[currentHook] = hooks[currentHook] || initialValue // type: any
const setStateHookIndex = currentHook // for setState's closure!
const setState = newState => (hooks[setStateHookIndex] = newState)
return [hooks[currentHook++], setState]
}
}
})()
You can read more about how hooks works in React here.

Updating and merging state object using React useState() hook

I'm finding these two pieces of the React Hooks docs a little confusing. Which one is the best practice for updating a state object using the state hook?
Imagine a want to make the following state update:
INITIAL_STATE = {
propA: true,
propB: true
}
stateAfter = {
propA: true,
propB: false // Changing this property
}
OPTION 1
From the Using the React Hook article, we get that this is possible:
const [count, setCount] = useState(0);
setCount(count + 1);
So I could do:
const [myState, setMyState] = useState(INITIAL_STATE);
And then:
setMyState({
...myState,
propB: false
});
OPTION 2
And from the Hooks Reference we get that:
Unlike the setState method found in class components, useState does
not automatically merge update objects. You can replicate this
behavior by combining the function updater form with object spread
syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
As far as I know, both works. So, what is the difference? Which one is the best practice? Should I use pass the function (OPTION 2) to access the previous state, or should I simply access the current state with spread syntax (OPTION 1)?
Both options are valid, but just as with setState in a class component you need to be careful when updating state derived from something that already is in state.
If you e.g. update a count twice in a row, it will not work as expected if you don't use the function version of updating the state.
const { useState } = React;
function App() {
const [count, setCount] = useState(0);
function brokenIncrement() {
setCount(count + 1);
setCount(count + 1);
}
function increment() {
setCount(count => count + 1);
setCount(count => count + 1);
}
return (
<div>
<div>{count}</div>
<button onClick={brokenIncrement}>Broken increment</button>
<button onClick={increment}>Increment</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
If anyone is searching for useState() hooks update for object
Through Input
const [state, setState] = useState({ fName: "", lName: "" });
const handleChange = e => {
const { name, value } = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};
<input
value={state.fName}
type="text"
onChange={handleChange}
name="fName"
/>
<input
value={state.lName}
type="text"
onChange={handleChange}
name="lName"
/>
Through onSubmit or button click
setState(prevState => ({
...prevState,
fName: 'your updated value here'
}));
The best practice is to use separate calls:
const [a, setA] = useState(true);
const [b, setB] = useState(true);
Option 1 might lead to more bugs because such code often end up inside a closure which has an outdated value of myState.
Option 2 should be used when the new state is based on the old one:
setCount(count => count + 1);
For complex state structure consider using useReducer
For complex structures that share some shape and logic you can create a custom hook:
function useField(defaultValue) {
const [value, setValue] = useState(defaultValue);
const [dirty, setDirty] = useState(false);
const [touched, setTouched] = useState(false);
function handleChange(e) {
setValue(e.target.value);
setTouched(true);
}
return {
value, setValue,
dirty, setDirty,
touched, setTouched,
handleChange
}
}
function MyComponent() {
const username = useField('some username');
const email = useField('some#mail.com');
return <input name="username" value={username.value} onChange={username.handleChange}/>;
}
Which one is the best practice for updating a state object using the state hook?
They are both valid as other answers have pointed out.
what is the difference?
It seems like the confusion is due to "Unlike the setState method found in class components, useState does not automatically merge update objects", especially the "merge" part.
Let's compare this.setState & useState
class SetStateApp extends React.Component {
state = {
propA: true,
propB: true
};
toggle = e => {
const { name } = e.target;
this.setState(
prevState => ({
[name]: !prevState[name]
}),
() => console.log(`this.state`, this.state)
);
};
...
}
function HooksApp() {
const INITIAL_STATE = { propA: true, propB: true };
const [myState, setMyState] = React.useState(INITIAL_STATE);
const { propA, propB } = myState;
function toggle(e) {
const { name } = e.target;
setMyState({ [name]: !myState[name] });
}
...
}
Both of them toggles propA/B in toggle handler.
And they both update just one prop passed as e.target.name.
Check out the difference it makes when you update just one property in setMyState.
Following demo shows that clicking on propA throws an error(which occurs setMyState only),
You can following along
Warning: A component is changing a controlled input of type checkbox to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
It's because when you click on propA checkbox, propB value is dropped and only propA value is toggled thus making propB's checked value as undefined making the checkbox uncontrolled.
And the this.setState updates only one property at a time but it merges other property thus the checkboxes stay controlled.
I dug thru the source code and the behavior is due to useState calling useReducer
Internally, useState calls useReducer, which returns whatever state a reducer returns.
https://github.com/facebook/react/blob/2b93d686e3/packages/react-reconciler/src/ReactFiberHooks.js#L1230
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
...
try {
return updateState(initialState);
} finally {
...
}
},
where updateState is the internal implementation for useReducer.
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
If you are familiar with Redux, you normally return a new object by spreading over previous state as you did in option 1.
setMyState({
...myState,
propB: false
});
So if you set just one property, other properties are not merged.
One or more options regarding state type can be suitable depending on your usecase
Generally you could follow the following rules to decide the sort of state that you want
First: Are the individual states related
If the individual state that you have in your application are related to one other then you can choose to group them together in an object. Else its better to keep them separate and use multiple useState so that when dealing with specific handlers you are only updating the relavant state property and are not concerned about the others
For instance, user properties such as name, email are related and you can group them together Whereas for maintaining multiple counters you can make use of multiple useState hooks
Second: Is the logic to update state complex and depends on the handler or user interaction
In the above case its better to make use of useReducer for state definition. Such kind of scenario is very common when you are trying to create for example and todo app where you want to update, create and delete elements on different interactions
Should I use pass the function (OPTION 2) to access the previous
state, or should I simply access the current state with spread syntax
(OPTION 1)?
state updates using hooks are also batched and hence whenever you want to update state based on previous one its better to use the callback pattern.
The callback pattern to update state also comes in handy when the setter doesn't receive updated value from enclosed closure due to it being defined only once. An example of such as case if the useEffect being called only on initial render when adds a listener that updates state on an event.
Both are perfectly fine for that use case. The functional argument that you pass to setState is only really useful when you want to conditionally set the state by diffing the previous state (I mean you can just do it with logic surrounding the call to setState but I think it looks cleaner in the function) or if you set state in a closure that doesn't have immediate access to the freshest version of previous state.
An example being something like an event listener that is only bound once (for whatever reason) on mount to the window. E.g.
useEffect(function() {
window.addEventListener("click", handleClick)
}, [])
function handleClick() {
setState(prevState => ({...prevState, new: true }))
}
If handleClick was only setting the state using option 1, it would look like setState({...prevState, new: true }). However, this would likely introduce a bug because prevState would only capture the state on initial render and not from any updates. The function argument passed to setState would always have access to the most recent iteration of your state.
Both options are valid but they do make a difference.
Use Option 1 (setCount(count + 1)) if
Property doesn't matter visually when it updates browser
Sacrifice refresh rate for performance
Updating input state based on event (ie event.target.value); if you use Option 2, it will set event to null due to performance reasons unless you have event.persist() - Refer to event pooling.
Use Option 2 (setCount(c => c + 1)) if
Property does matter when it updates on the browser
Sacrifice performance for better refresh rate
I noticed this issue when some Alerts with autoclose feature that should close sequentially closed in batches.
Note: I don't have stats proving the difference in performance but its based on a React conference on React 16 performance optimizations.
I find it very convenient to use useReducer hook for managing complex state, instead of useState. You initialize state and updating function like this:
const initialState = { name: "Bob", occupation: "builder" };
const [state, updateState] = useReducer(
(state, updates) => {...state, ...updates},
initialState
);
And then you're able to update your state by only passing partial updates:
updateState({ occupation: "postman" })
The solution I am going to propose is much simpler and easier to not mess up than the ones above, and has the same usage as the useState API.
Use the npm package use-merge-state (here). Add it to your dependencies, then, use it like:
const useMergeState = require("use-merge-state") // Import
const [state, setState] = useMergeState(initial_state, {merge: true}) // Declare
setState(new_state) // Just like you set a new state with 'useState'
Hope this helps everyone. :)

Changing a child components state changes the parent components props

Parent component is a header
Child component is a form which is used to change values appearing in the header after a save which fires a redux action.
I set the child state with
constructor(props) {
super(props);
this.state = {
object: { ...props.object },
hidden: props.hidden,
};
}
The form is used to render the state.object and modify the state.object.
When I modify state.object, the props from the parent component change as well.
handleObjectChange = (event, key, subkey) => {
console.log('props', this.props.object.params);
console.log('state', this.state.object.params);
event.preventDefault();
const value = this.handlePeriod(event.target.value);
this.setState((prevState) => {
const object = { ...prevState.object };
object[key][subkey] = value;
return { object };
});
}
Console output:
newvalueijusttyped
newvalueijusttyped
This behavior actually goes all the way up to modifying the redux store without ever having dispatched an action.
Would appreciate a solution for this issue
Update:
Changing the constructor to this solved the issue
constructor(props) {
super(props);
this.state = {
object: JSON.parse(JSON.stringify(props.object)),
hidden: props.hidden,
};
}
Why doesn't the object spread operator achieve what I'm trying to accomplish?
Javascript object are assigned by reference so when you do
constructor(props) {
super(props);
this.state = {
object: props.object,
hidden: props.hidden,
};
}
state is referencing the redux state object(if it is a redux state). So now when you use
this.setState((prevState) => {
const object = { ...prevState.object };
object[key][subkey] = value;
return { object };
});
Although you would assume that you have cloned the object value into a new object. However Spread syntax does only a one level copy of the object.
From the Spread Syntax MDN docs:
Note: Spread syntax effectively goes one level deep while copying an
array. Therefore, it may be unsuitable for copying multidimensional
arrays as the following example shows (it's the same with
Object.assign() and spread syntax).
var a = [1, [2], [3]]; var b = [...a]; b.shift().shift(); // 1 //
Now array a is affected as well: [[], [2], [3]]
So effectively
object[key][subkey] = value;
changes the value directly in redux store.
Solution is create a nested copy like
const object = { ...prevState.object,
[key]: {
...prevState[key],
[subkey]: { ...prevState[key][subkey]}
}
};
object[key][subkey] = value;
Objects in javascript are 'passed by reference'.
If you are passing the parent's props as state to the children, then when you change the state, you are in effect mutating the very same object that was passed to it.
Use Object.assign() to create a clone of the data before you make it part of the child's state.

Categories