Please look at this
useEffect(() => {
setName('John');
}, [name]);
How come it does not cause an infinite loop? assuming name is not already equal to John.
Because useState is using a diffing algorithm (or they are calling it Bailing out - in the official documentation) to make a comparison for objects/values. So in this scenario, React will compare the previous value of name and will see that it's the same and won't invoke the setter.
The same will happen if you try to update the same object,
obj = { name: 'John' }
(obj) React will make a comparison and see that it's the same object and won't make any change, but if you spread that object and invoke the setName({ ...obj }) in this scenario React will treat it like different object and invoke the setName.
Related
When it comes to handling complex state in React everybody suggests to flatten the state to avoid something like this just to update a single property:
setState({ …state, user:{ …state.user, profile:{…state.user.profile, address:{…state.user.profile.address, city:’Newyork’}} }});
Which is really cubersome to work with. There is another way: use an object holding your state and return that from a memoized function. Then whenever you made a change simply force a re-render.
// note the reference cannot be changed, but values can.
const data = useMemo(() => ({
user: {
name: "",
profile: {
address: {
city: "New york"
}
}
}
}), []);
// use dummy data to trigger an update
const [toggle, setToggle] = useState(false);
function forceUpdate() {
setToggle(prev => !prev);
}
function makeChanges() {
// make any change on data without any copying.
data.user.address.city = "new city name";
// hydrate the changes to the view when you're done
forceUpdate();
}
return (
<div onClick={() => makeChanges()}>{data.user.address.city }</div>
)
Which works perfectly. Even with massive and complex data structures.
From what I can tell state is really just a memoized values which will trigger an update upon change.
So, my one question: What is the downside of using this?
The docs say useMemo is not a guarantee:
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values [...]
If you'd really want to do something like this and you are absolutely positively sure you're willing to do things unlike anyone else in React land, you'd use useRef for state storage that doesn't cause rerenders by itself. I'm not going to add an example of that, because I don't recommend it in the least.
You should also note that your method will not cause memoized (React.memo()) components to rerender, since they will not "see" changes to props if their identity does not change. Similarly, if another component uses one of your internally mutated objects as a dependency for e.g. an effect, those effects will not fire. Finding bugs caused by that will be spectacularly annoying.
If modifying deep object structures is otherwise cumbersome, see e.g. the Immer library, which does Proxy magic internally to let you modify deep objects without trouble – or maybe immutability-helper if you're feeling more old-school.
I have a large and deep object which I render using a React functional component (composed of child components). I also use Redux in the project which interacts with an API. To keep the app fast, I keep a local copy of the object in the component state and dispatch changes when occurred. The objects can also change in different parts of the app.
Each time the object changes in Redux its lastUpdated field is updated with the current time (epoch) by Redux. So instead of doing a deep (and expensive) diff on the whole object it is sufficient to compare the object id (in case the component has to display a different object) and the lastUpdated value (in case the object itself got changed).
This is what my functional component looks like:
interface Item {
id: string
lastUpdated: number
// more fields here
}
interface Props {
item : Item
}
export default function ItemPage(props: Props){
const [displayItem, setDisplayItem] = useState<Item>(props.item)
useEffect(() => {
if (props.item.id !== display.item.id && props.item.lastUpdated !== displayItem.lastUpdated){
setDisplayItem(props.item)
// ... some logic comes here
}
}, [props.item.id, props.item.lastUpdated])
return (
// ... render code here
)
}
This code cause the following error:
React Hook useEffect has missing dependencies: 'item.id' and
'item.lastUpdated'. Either include them or remove the dependency array
react-hooks/exhaustive-deps
I have disable the error with:
// eslint-disable-next-line react-hooks/exhaustive-deps
Questions:
Is it safe to to disable the error as I know the logic of my Redux (and use the more efficient diff)? Is there a better solution?
Is it safe to completely remove the useEffect array as I make the id/lastUpdated diff myself?
Note: I am not looking for the general "do not disable as it will come back to bite you ..." answer.
Looks like you need React.memo with second argument isEqual. Is equal works like shouldComponentUpdate. Read reactjs docs more about React.memo
UPDATE:
I've added codesandbox as example
Guys i have this example code bellow:
const [data, setData] = useState([{question: '', option: ['']}]);
Then data and setData will pass to my component, like:
<Question setData={setData} data={data}/>
My code inside Question component is:
const handleAddOption = (questionIndex: number) => {
let newArray = data;
newArray.map((item: any, i: number) => {
if (i === questionIndex) {
newArray[questionIndex].options.push('');
}
});
setData(newArray);
};
The problem is, if i add a new entire Object it will "refresh" my page and show, but, when i add like the last lines of code, only a new string inside the array, it will not "re-render".
Anyone knows how to solve this?
In react first rule of thumb is don't mutate state directly. It works for class-based components and setState, it works for redux's reducers, it works for hook-based useState too.
You need to
setData((data) => data.map((item, index) => {
if (index !== questionIndex) return item;
return {
...item,
options: [
...item.options,
''
]
};
}));
There are several items to highlight here:
as well as for class-based component's setState there is possible to provide callback into updater function. I better skip describing it in details here. May suggest to take a look into SO question and ReactJS docs. Yes, it's not about hooks but it uses the same logic.
We need to merge our old data with changes to keep properties we don't want to change still present. That's all this spread operator is so hardly used. Take a look into article on handling arrays in state for React
Last but not least, we have check to directly return item if it's not we want to update without any change. This makes us sure no related children components will re-render with actually exact the same data. You may find additional information by searching for query "referential equality"(here is one of article you may find).
PS it may look much easier to force update instead of rewriting code completely. But mutating state directly typically later ends up with different hard-to-reproduce bugs.
Also you may want to change components hierarchy and data structure. So no component would need to traverse whole array to update single nested property.
It seems like You have typo write newArray[questionIndex].option.push(''); instead of newArray[questionIndex].options.push('');
But if it doesn't help try forceUpdate(); more details You can find in this answer How can I force component to re-render with hooks in React? or try to use this package https://github.com/CharlesStover/use-force-update
Good Luck :)
Rough implementation
I think you can change:
const [data, setData] = useState([{question: '', option: ['']}]);
// into
const [questions, setQuestions] = useState([]);
And as you receive new question objects, you can do setQuestions([...questions, newQuestion]).
That is, assuming, you have a form that is receiving inputs for new questions. If this is the case, in your onSubmit function for your form, you can generate your new question object and then do setQuestions([...questions, newQuestion]).
Unless I'm mistaken, this is valid code:
useEffect(() => {
if (prop1) {
doSomething();
}
}, []);
(prop1 is a prop). But when linting I get the following error:
React Hook useEffect has a missing dependency: 'prop1'. Either include it or remove the dependency array.
(react-hooks/exhaustive-deps)
I don't want to pass prop1 as a dependency because I would lose the "only run on mount" behaviour. But I need to access the prop to doSomething().
Any suggestions?
Hooks were new when this question was written, so maybe you already know this, but in case you or someone else wants to know:
React thinks that because your effect uses the value of prop1, it "depends" on prop1 and should be re-run whenever it changes. That's why the linter is complaining that it's not listed as a dependency.
However because you want the effect to only run "on mount", you want it to use the value of prop1 from the initial/first render, and never run again even if prop1 changes. This is at odds with the conceptual idea that the array lists all the variables the effect depends on, which is what the linter is focused on.
The solution alluded to in the React Hooks FAQ is to use useRef to keep track of whether or not this is the first render (edited):
const firstRenderRef = useRef(true)
useEffect(() => {
if (firstRenderRef.current) {
firstRenderRef.current = false
doSomething(prop1)
}
}, [prop1])
This solution satisfies the linter, specifically because it follows the idea of listing all dependencies of the effect in the array. The ref allows the effect to also depend on a variable for whether this is the first render or not, but without rerendering when the value changes.
You could probably raise this here.. [ESLint] Feedback for 'exhaustive-deps' lint rule
Though I get the feeling where this is a case where you should add an eslint "ignore" comment if you are sure you don't want the effect to run on update of prop1.
A legit case for relaxing the warning was raised here..
https://github.com/facebook/react/issues/14920#issuecomment-467896512
Also check what version of the plugin you are running..
https://github.com/facebook/react/issues/14920#issuecomment-468801699
try this code
const usemount = ( functionToDoSomeThing, data ) => {
useEffect( () => {
functionToDoSomeThing( data );
},[] );
};
usemount( console.log, props );
i define a function to do Something and pass it to hook
in example i use console.log function
I was looking at some example react code (in the antd docs), and I noticed they have code that is equivalent to:
this.setState(prevState => { prevState.name = "NewValue"; return prevState; });
This looks a bit naughty, but does it actually break anything? Since it's using the arrow function it's not breaking the ordering of changes being applied even if React batches them up in the background.
Of course setState is intended to expect a partial state so there might be performance side effects there as it might try to apply the whole state to itself.
edit: (in response to #Sulthan)
The actual code is this:
handleChange(key, index, value) {
const { data } = this.state;
data[index][key].value = value;
this.setState({ data });
}
n.b. data is an array, so its just being copied by reference then mutated.
It's actually completely wrong as its not even using the arrow function to get the latest state.
It comes from the editable table example here: https://ant.design/components/table/
Your example can be also rewritten as:
this.setState(prevState => {
prevState.name = "NewValue"
return "NewValue";
});
When a function is passed to the state the important thing is not to mutate the passed parameter and return the new state. Your example fails both.
...prevState is a reference to the previous state. It should not be directly mutated. Instead, changes should be represented by building a new state object based on the input from prevState...
(from setState)
I am not sure whether it was ever possible to use setState like in your example but looking into the change log I really doubt it.