React clearing all keys rather than shallow merging single key - javascript

I am pulling in data from an API, and then allowing a user to modify that data within react, after which I will send the updated state to the server.
My API provides the following JSON:
{
"id": 1,
"title": "Test Campaign",
"impressions": 12,
}
And my component looks like so:
function Campaign() {
const [data, setData] = useState([])
useEffect(async () => {
const result = await axios('/campaigns/1')
setData(result.data);
}, []);
const handleChange = (event) => {
setData({[event.target.name]: event.target.value});
}
return (
<div className='row'>
<div className='col-12'>
<h2>{ data.title }</h2>
<input
type='number'
value={data.impressions}
name='impressions'
onChange={e => handleChange(e)}
/>
</div>
</div>
)
}
However, running this code causes all keys in the data state to be overwritten (and thus my h2 tag displays nothing). It was my understanding that React State carried out a shallow merge, and so in this case would only update the impressions key:value?

Functional component's useState hook doesn't actually shallow merge state updates so you need to manage this yourself.
Use a functional state update to access the previous state to shallow copy into the next state.
Note
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};
});
You may want to also ensure you maintain your state invariant. Since you are updating your state via key-value pairs I'm assuming you are actually using an object.
const [data, setData] = useState({}); // <-- empty object initial state
const handleChange = (event) => {
setData(data => ({
...data, // <-- shallow copy existing state
[event.target.name]: event.target.value,
}));
}

Related

Is there any way to trigger React.useEffect from different component?

Imagine two components like this in React:
import MyComponent2 from "./components/MyComponent2";
import React from "react";
export default function App() {
const [myState, setMyState] = React.useState([]);
React.useEffect(() => {
console.log("useEffect triggered");
}, [myState]);
return <MyComponent2 myState={myState} setMyState={setMyState} />;
}
import React from "react";
export default function MyComponent2(props) {
const [inputValue, setInputValue] = React.useState("");
function handleChange(e) {
setInputValue(e.target.value);
let list = props.myState;
list.push(`${e.target.value}`);
props.setMyState(list);
console.log(props.myState);
}
return (
<div>
<input
type="text"
value={inputValue}
name="text"
onChange={handleChange}
/>
</div>
);
}
As you can see I am making changes with props.setMyState line in second component. State is changing but Somehow I could not trigger React.useEffect in first component even tough It is connected with [myState]. Why ?
In short form of my question : I can not get "useEffect triggered" on my console when i make changes in input
Instead of providing myState and setMyState to MyComponent2, you should only provide setMyState and use the functional update argument in order to access the current state.
In your handleChange function you are currently mutating the React state (modifying it directly):
let list = props.myState; // This is an array that is state managed by React
list.push(`${e.target.value}`); // Here, you mutate it by appending a new element
props.setMyState(list);
// ^ You update the state with the same array here,
// and since they have the same object identity (they are the same array),
// no update occurs in the parent component
Instead, you should set the state to a new array (whose object identity differs from the current array):
props.setMyState(list => {
const newList = [...list];
newList.push(e.target.value);
return newList;
});
// A concise way to write the above is like this:
// props.setMyState(list => [...list, e.target.value]);

Cannot read property 'length' of undefined in functional component while it works in class component [duplicate]

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. :)

React useState with an empty object causes an infinite loop

Using React hooks with a child component that should get the initial state from the parent and update the parent on every internal state change.
I figured that since it's always the same reference the useEffect of the child should not get called infinitely.
If the initial state of the child is an empty object I get an infinite loop.
If the initial state of the child is taken from the props it works great.
Not sure what's causing it.
You can change the first useState inside the child component to an empty object to make the infinite loop start.
Please review the sandbox below:
https://codesandbox.io/s/weird-initial-state-xi5iy?fontsize=14&hidenavigation=1&theme=dark
Note: I've added a counter to the sandbox to stop the loop after 10 runs and not crash the browser.
import React, { useState, useEffect, useCallback } from "react";
const problematicInitialState = {};
/* CHILD COMPONENT */
const Child = ({ onChange, initialData }) => {
const [data, setData] = useState(initialData); // if initialData is {} (a.k.a problematicInitialState const) we have an infinite loop
useEffect(() => {
setData(initialData);
}, [initialData]);
useEffect(() => {
onChange(data);
}, [data, onChange]);
return <div>Counter is: {data.counter}</div>;
};
/* PARENT COMPONENT */
export default function App() {
const [counterData, setCounterData] = useState({ counter: 4 });
const onChildChange = useCallback(
(data) => {
setCounterData(data);
},
[setCounterData]
);
return (
<div className="App">
<Child onChange={onChildChange} initialData={counterData} />
</div>
);
}
How about putting the state only in the parent component instead, and have the child only reference the props passed down to it, without any state of its own?
const Child = ({ counterData, setCounterData }) => {
return (
<div>
<div>Counter is: {counterData.counter}</div>
<button
onClick={() => setCounterData({ counter: counterData.counter + 1 })}
>increment</button>
</div>
);
};
const App = () => {
const [counterData, setCounterData] = React.useState({ counter: 4 });
return (
<div className="App">
<Child {...{ counterData, setCounterData }} />
</div>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class="react"></div>
Problem is that in JS {} !== {} because objects, unlike primitive values, are compared by reference, not value.
In you useEffect you're comparing 2 objects, because they always have different reference, the'll never be the same in JS land and your useEffect will trigger, setting new object and you got yourself an infinite loop.
You shouldn't use hooks in the same way you used class components in react, meaning you should do
const [counter, setCounter] = useState(4);
This way, you'll pass primitive value down to your child component and useEffect will have much more predictable behaviour.
Also, while this is a test case, you should rarely (read: never) try to set child sate to parent state. You already pass that data from parent to child, no need to create redundant state in your child component, just use the passed in data.
Regarding solutions I propose that you don't set any initial state (or set it as empty object {}) in your child component. The first useEffect will handle the first update.
const Child = ({ onChange, initialData }) => {
const [data, setData] = useState({});
useEffect(() => {
setData(initialData);
}, [initialData]);
useEffect(() => {
onChange(data);
}, [data, onChange]);
return <div>Counter is: {data.counter}</div>;
};
as of the other comments, I agree, rather pass the state from parent to child.

React updating UI after a change in Props feeded to useReducer

I have a React Functional Component that behaves in this way:
const Child = ({objValues, number}) => {
const emptyState = {
a: { b: "", c:""}
d: { e: "", f:""}
}
const initialState = {...emptyState, ...objValues}
const [state, dispatch] = useReducer(reducer, {...initialState})
return (
// render something based on state's nested objects.
// and the number prop.
)
}
const Parent = () => {
[objValues, setObjValues] = useState({});
[number, setNumber] = useState("");
//... some network requests to populate values
// and change number (using setState);
useEffect(() => {
// fetchAPI and then set values using spread operator
// to force creating a new object.
setObjValues({...values})
}, [number])
return ( <Child objValues={objValues} number={number}/>
I always get unique items (number, objValues) from the API I am using. Inside the component, I see that whenever I fire a new network request, the value of number gets updated in the UI, but the objValues shows the same values as the previous object. In the Component tabs in the react dev tools, I see that these values do update, nevertheless the UI stays the same.
const Child = ({objValues, number}) => {
const emptyState = {
a: { b: "", c:""}
d: { e: "", f:""}
}
const initialState = {...emptyState, ...objValues}
const [state, dispatch] = useReducer(reducer, {...initialState})
useEffect(()=>{
dispatch({
...objValues
})
},[objValues])
return (
// render something based on state's nested objects.
// and the number prop.
)
}
Couple of things
do not use the spread everywhere, dont see a reason for any of them in your code, if you pass object to setState it will take the current value and put it to state as value not reference so no reason to "force"
try typescript or try to not mix types, in your numer state you have default value a string, either have it empty, null or something that is close to a number
To your problem: since the reducer will resolve data, I dont think it has to update based on change in its initial data, what you should do, is ditch this populating some data and mixing them with redux data, but make the api request and once its done, save the data to redux and your reducer in another component will update itself
EDIT: Iam dumb its not useSelector from redux, regardless initial data got to be used only once, and they dont force the hook to update its value, basicaly any solid library for react or react itself, will look at initial data only when its called the first time, and then it doesnt matter how many times or how hard you change the initial data, it shouldnt affect it (most component or form libraries do follow this rule), basically in this case I would do useeffect and I would update the reducer with the data, or if that is not possible declare new constant, where you spread your default value and then spread your reducer data

Use local state for forms when data is in redux

Use React for ephemeral state that doesn’t matter to the app globally
and doesn’t mutate in complex ways. For example, a toggle in some UI
element, a form input state. Use Redux for state that matters globally
or is mutated in complex ways. For example, cached users, or a post
draft.
My redux state is only representative of what has been saved to my backend database.
For my use case there is no need for any other part of the application to know about a record in an adding/editing state..
However, all my communication with my API is done through redux-thunks. I have found, getting data from redux into local state for editing is tricky.
The pattern I was trying to use:
const Container = () => {
// use redux thunk to fetch from API
useEffect(() => {
dispatch(fetchThing(id));
}, [dispatch, id]);
// get from redux store
const reduxThing = useSelector(getThing);
const save = thing => {
dispatch(saveThing(thing));
};
return (
{!fetching &&
<ThingForm
defaults={reduxThing}
submit={save}
/>}
);
};
const ThingForm = ({defaults, submit}) => {
const [values, setValues] = useState({ propA: '', propB: '', ...defaults});
const handleChange = { /*standard handleChange code here*/ };
return (
<form onSubmit={() => submit(values)}>
<input type="text" name="propA" value={values.propA} onChange={handleChange} />
<input type="text" name="propB" value={values.propB} onChange={handleChange} />
</form>
);
};
How I understand it, ThingForm is unmounted/mounted based upon "fetching." However, it is a race condition whether or not the defaults get populated. Sometimes they do, sometimes they don't.
So obviously this isn't a great pattern.
Is there an established pattern for moving data into local state from redux for editing in a form?
Or should I just put my form data into redux? (I don't know if this would be any easier).
EDIT: I think this is essentially what I am fighting: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
But no recommendation really clearly fits. I am strictly using hooks. I could overwrite with useEffect on prop change, but seems kind of messy.
EDIT:
const Container = () => {
// use redux thunk to fetch from API
useEffect(() => {
dispatch(fetchThing(id));
}, [dispatch, id]);
// get from redux store
const reduxThing = useSelector(getThing);
const save = thing => {
dispatch(saveThing(thing));
};
return (
{!fetching &&
<ThingForm
defaults={reduxThing}
submit={save}
/>}
);
};
const ThingForm = ({defaults, submit}) => {
const [values, setValues] = useState({ propA: '', propB: '', ...defaults});
const handleChange = { /*standard handleChange code here*/ };
useEffect(() => {
setValues({...values, ...defaults})
}, [defaults]);
const submitValues = (e) => {
e.preventDefault();
submit(values)
}
return (
<form onSubmit={submitValues}>
<input type="text" name="propA" value={values.propA} onChange={handleChange} />
<input type="text" name="propB" value={values.propB} onChange={handleChange} />
</form>
);
};
What you are doing is the right way, there's no reason why you should put the form data in the redux store. Like you said, "there is no need for any other part of the application to know about a record in an adding/editing state"
And that's correct.
The only problem you have is here:
{!fetching &&
<ThingForm
defaults={reduxThing}
submit={save}
/>}
Assuming fetching is true on every dispatch:
Instead of trying to hide the component (unmounting essentially), you should maybe use a spinner that overlays the page?
I don't know the rest of your code to comment on a better approach.
You also don't have to add dispatch to the dependency array
useEffect(() => {
dispatch(fetchThing(id));
}, [id]);
From the react docs:
React guarantees that dispatch function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.

Categories

Resources