Keeping track of the second result of useState [duplicate] - reactjs

Is useState's setter able to change during a component life ?
For instance, let's say we've got a useCallback which will update the state.
If the setter is able to change, it must be set as a dependency for the callback since the callback use it.
const [state, setState] = useState(false);
const callback = useCallback(
() => setState(true),
[setState] // <--
);

The setter function won't change during component life.
From Hooks FAQ:
(The identity of the setCount function is guaranteed to be stable so it’s safe to omit.)
The setter function (setState) returned from useState changes on component re-mount, but either way, the callback will get a new instance.
It's a good practice to add state setter in the dependency array ([setState]) when using custom-hooks. For example, useDispatch of react-redux gets new instance on every render, you may get undesired behavior without:
// Custom hook
import { useDispatch } from "react-redux";
export const CounterComponent = ({ value }) => {
// Always new instance
const dispatch = useDispatch();
// Should be in a callback
const incrementCounter = useCallback(
() => dispatch({ type: "increment-counter" }),
[dispatch]
);
return (
<div>
<span>{value}</span>
// May render unnecessarily due to the changed reference
<MyIncrementButton onIncrement={dispatch} />
// In callback, all fine
<MyIncrementButton onIncrement={incrementCounter} />
</div>
);
};

The short answer is, no, the setter of useState() is not able change, and the React docs explicitly guarantee this and even provide examples proving that the setter can be omitted.
I would suggest that you do not add anything to the dependencies list of your useCallback() unless you know its value can change. Just like you wouldn't add any functions imported from modules or module-level functions, constant expressions defined outside the component, etc. adding those things is just superfluous and makes it harder to read your handlers.
All that being said, this is all very specific to the function that is returned by useState() and there is no reason to extend that line of reasoning to every possible custom hook that may return a function. The reason is that the React docs explicitly guarantee the stable behavior of useState() and its setters, but it does not say that the same must be true for any custom hook.
React hooks are still kind of a new and experimental concept and we need to make sure we encourage each other to make them as readable as possible, and more importantly, to understand what they actually do and why. If we don't it will be seen as evidence that hooks are a "bad idea," which will prohibit adoption and wider understanding of them. That would be bad; in my experience they tend to produce much cleaner alternatives to the class-based components that React is usually associated with, not to mention the fact that they can allow organizational techniques that simply aren't possible with classes.

Related

Dispatch in dependencies. Where is true? We should or not to add dispatch in react hooks?

We should or not to add dispatch in dependencies of react hooks? Is there exact opinion about this thing? Who knows true? Some says yes, some no
const Component = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(someAction());
}, [dispatch]);
return (
<div>
Component
</div>
);
};
If it is good idea to add the dispatch function to the dependancy array, depends on what you want to reach. If you want that your useEffect is executed every time your "dispatch" function changes, then the answer would be yes. If you don't want to track the dispatch function, then the answer would be no.
In general the dispatch function does not change at all, thats why adding it to the dependancy array will be with no difference. But there are some libraries and cases, that may change the function assignment, which could cause unwanted re-run of the useEffect.
There is no opinion, values coming from 3rd party libraries should be in dep array.
That's because you can't know if its value persists (unless it mentioned in docs or you check the source code).
Removing it from the dependencies will get you an eslint warning react-hooks/exhaustive-deps.
Specific for Redux's useDispatch, the value is persisted, so removing it won't make a difference.
Note: like in React's useReducer, the returned dispatch function identity is stable and won't change on re-renders.
But as mentioned, it may not be true for other libraries, so having it in dependencies is an extra safety without fighting the linter.

React useContext with "setState"

I recently started using the Context React Api with useContext Hook.
I have observed that when we have a state variable i.e. const someState = useState(state, setState), some
developers pass setSate directly in the provider values and then calling it in children components.
Is that a good practice?
When you do not use context you have to create a handler to "access" setState in a child component.
I am still using handler functions and pass them into the provider values, to import them from context
in children.
Is passing setState in context a good pracice? I still have some doubts since normally you cannot pass setState directly into a component.
Is there any difference in performance or any other drawbacks I should be considering?
Thank you.
Edit: I actually think I got you wrong, but I'm not sure. My reply is valid for the case if you write your own provider that has a state. If you just use a default provider that provides a setter, I would agree with the reply of Amel.
I personally wouldn't do it, but that's more of an opionion. However, like always, it pretty much depends on what goal you want to reach.
A positive aspect of doing it is, that state setters given by useState always stay the same for each rerender. If you pass a custom value, you should avoid that it changes too often because every component listening to the change using useContext would rerender.
I would still prefer to pass a custom object (e.g. coming from a useMemo to avoid unnecessary rerenders) with a callback to set the state. It's easier to extend if you want to provide more stuff in the future.
Something like this (very simplistic example, that of course doesn't make sense like this, it's just for comprehension):
function MyProvider({children}) {
const [state, setState] = useState(0);
const provided = useMemo(() => ({
value: state,
setValue: (value) => setState(value)
}, [value]);
return <MyContext.Provider value={provided}>{children}</MyContext.Provider>;
}
It would be easier to extend without changing code everyhwere where the context is used. However, I still think there is nothing particular bad in passing just the setter, if that is what you want to achive.
If I understood correctly the difference is that in one way the state is set from the parent component and in the other the state is set from the child component.
Sometimes people do it that way to avoid loop rendering with changing the state. There should be no drawbacks, but using handler functions is the regular way to go.
you can use state as variable not as spreaded one
const state = useContext(0);
state[0] //it's the getter for state you can access the values from this
(state[1])(10) //it is setter for state you can set values with is
(state[1])((oldvalues)=>{//write you code here})

Is using a ref in a custom hook feasible or advised for mutating the DOM?

I am working on incorporating a 3rd-party library into a React app, and hooks are making this very easy overall.
However I have encountered a few issues, and I was hoping for some clarity on what is going on 'under the hood'.
For simplicity, let's say this is my 3rd-party code that mutates the DOM directly:
const renderStuff = (div, txt) => {
if(div) div.innerHTML = txt;
}
And my component is something like this:
export const EffectRender = () => {
const divRef = useRef();
useRenderer(divRef, "Hello, world");
return <div ref={divRef}></div>;
}
Here is the proposed custom hook:
const useRenderer = (ref, txt) => {
const div = ref.current;
useEffect(() => {
renderStuff(div, txt);
},[div, txt])
};
This works if one of the params (in this case, txt) is late-updated, say as a result of an async load.
But the useEffect never recognizes when the ref.current value changes.
So if the txt is set before ref.current is set (as in this case) the component never renders.
I understand that I can fix that by using setState in the custom hook, as in this example.
But this starts to feel cumbersome.
I also understand that I could put the renderStuff call in a useEffect hook on the main component, and that guarantees the ref.current is set.
So: useEffect(() => { renderStuff(divRef.current, txt); },[txt]); is fine.
My question really is whether this whole approach of using a ref inside a custom hook is a good idea.
Is there a simpler way of getting the hook to recognize when the ref has changed?
Or is this a case where custom hooks are not suited to the task?
The problem is that const div = ref.current; in useRenderer is declared outside of the hook. In this moment of the cycle the ref is still not assigned, so its value is null.
If I understood the issue correctly, then the solution is to simply move the ref inside the useEffect callback. This is one of your proposals and I believe its the correct way:
const useRenderer = (ref, txt) => {
useEffect(() => {
const div = ref.current;
renderStuff(div, txt);
}, [txt]);
};
useEffect dependencies will not trigger when the ref the changes. This is ok in your case as the ref already has its assigned value when useEffect runs. However, if the ref changed and you needed to track the change the way to go is using a useState.
Thank you Alvaro for clarifying the issue. I want to explain where my thinking went wrong here, assuming I'm not the only one to make this mistake.
My bad logic
We use effect hooks because the code is mutating the DOM, and useEffect exists to handle exactly this sort of side-effect.
We use ref hooks to connect to a given DOM element. The ref instance is like a singleton, in that the instance doesn't change during the life of the app. Only the ref.current property changes.
And directly under the relevant section in the hooks reference docs I read this:
Keep in mind that useRef doesn’t notify you when its content changes.
Mutating the .current property doesn’t cause a re-render.
From that, I understood that relevant dependencies need to be passed to the useEffect call. And that the element to be updated (in ref.current) was one of these dependencies. And since changing the ref.current property doesn't trigger a re-render, it presumably wasn't triggering the useEffect call either.
BTW: this thinking was reinforced by es-lint demanding I add ref.current (in my case, div = ref.current) into the dependencies list: React Hook useEffect has a missing dependency: 'div'. Either include it or remove the dependency array. I always trust the linter, of course.
My conclusion: there was no simple way to use an effect hook to render to a ref instance. I needed to put the ref.current into a useState setter somehow. Ugly!
My core assumption
I assumed that the way the useEffect handles side-effects is not relevant. The effect hook is a black-box buried in the bowels of the typescript source code, and it moves in mysterious ways.
But the only thing 'buried' was this sentence in the use-effect docs:
The function passed to useEffect will run after the render is
committed to the screen.
"After the render". Of course. That is how useEffect handles side-effects, by guaranteeing not to run until the DOM is ready to go.
The solution
As Alvaro's answer suggests, make sure to read the ref.current property inside the useEffect hook. The ref never changes, and ref.current is guaranteed to be already populated with a DOM element.
As usual, it is obvious in hindsight. Thanks again, Alvaro.

useMemo vs useState for React hooks constants

Defining a calculated (initialized) constant using React hooks can be performed in two ways that seem functionally equivalent. I don't want to get into the use cases for this, but suffice to say that there are cases where a constant value can be derived from initial props or state that isn't expected to change (think route data, bound dispatch, etc).
First, useState
const [calculatedConstant] = useState(calculateConstantFactory);
Second, useMemo
const calculatedConstant = useMemo(calculateConstantFactory, []);
Both of these seem functionally equivalent, but without reading the source code, I'm not sure which is better in terms of performance or other considerations.
Has anyone done the leg work on this? Which would you use and why?
Also, I know some people will recoil at the assumption that state can be "considered constant". I'm not sure what to tell you there. But even without state, I may want to define a constant within a component that has no state at all, for example, creating a block of JSX that doesn't change.
I could define this outside of the component, but then it's consuming memory, even when the component in question is not instantiated anywhere in the app. To fix this, I would have to create a memoization function and then manually release the internal memoized state. That's an awful lot of hassle for something hooks give us for free.
Edit: Added examples of the approaches talked about in this discussion.
https://codesandbox.io/s/cranky-platform-2b15l
You may rely on useMemo as a performance optimization, not as a semantic guarantee
Meaning, semantically useMemo is not the correct approach; you are using it for the wrong reason. So even though it works as intended now, you are using it incorrectly and it could lead to unpredictable behavior in the future.
useState is only the correct choice if you don't want your rendering to be blocked while the value is being computed.
If the value isn't needed in the first render of the component, you could use useRef and useEffect together:
const calculatedConstant = useRef(null);
useEffect(() => {
calculatedConstant.current = calculateConstantFactory()
}, [])
// use the value in calcaulatedConstant.current
This is the same as initializing an instance field in componentDidMount. And it doesn't block your layout / paint while the factory function is being run. Performance-wise, I doubt any benchmark would show a significant difference.
The problem is that after initializing the ref, the component won't update to reflect this value (which is the whole purpose of a ref).
If you absolutely need the value to be used on the component's first render, you can do this:
const calculatedConstant = useRef(null);
if (!calculatedConstant.current) {
calculatedConstant.current = calculateConstantFactory();
}
// use the value in calculatedConstant.current;
This one will block your component from rendering before the value is set up.
If you don't want rendering to be blocked, you need useState together with useEffect:
const [calculated, setCalculated] = useState();
useEffect(() => {
setCalculated(calculateConstantFactory())
}, [])
// use the value in calculated
Basically if you need the component to re-render itself: use state. If that's not necessary, use a ref.

Is there a way to emulate the run frequency of constructor code using the React Hooks API?

Sometimes, I have found it useful to place some code in the constructor of a React class component, to do some processing once (when the component is instantiated) and be able to reference the result throughout. Is there now a way to do this with a functional component using the React Hooks API?
example:
constructor(props) {
super(props);
const componentFactory = createComponentFactory(props.context);
this.components = props.components.map(componentFactory);
It looks like you want something like instance variables.
You can do that using the useRef() hook.
Docs: Hooks API
Essentially, useRef is like a “box” that can hold a mutable value in its .current property.
You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with , React will set its .current property to the corresponding DOM node whenever that node changes.
However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
Docs: Hooks FAQ
Is there something like instance variables?
Yes! The useRef() Hook isn’t just for DOM refs. The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class.
useEffect can be used to run the code once but this happens on component mount, so it's a counterpart to componentDidMount, not a constructor:
let components = useRef();
useEffect(() => {
components.current = props.components.map(createComponentFactory(props.context))
}, []);
// components.current === null
// on first render
useMemo can be used to run the code once on first render:
const components = useMemo(
() => props.components.map(createComponentFactory(props.context)),
[]
);
It isn't guaranteed to run the code once in future React versions:
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 and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.
useState can be used to run the code once on first render, too:
const [components] = useState(
() => props.components.map(createComponentFactory(props.context))
);
Due to limitations of other options, the last option can be considered a preferable way to do this.

Resources