useState Hook inside loop or conditional statement - reactjs

I was reading the updated react docs whose link is here there is statement related to useState
If you want to useState in a condition or a loop, extract a new
component and put it there.
But what I know by the rules of hooks is that you cannot use hooks inside loops, conditions, or nested functions which is mentioned in the docs here
So can someone give an example of the first sentence, because they didn't gave an example or there.
My question is can we call hooks inside loops and conditional statements ?

What they are saying is that if you want to have something with state in loop component:
const App = ()=>{
const fields = [someArray]
return(
<div>{fields.map((field)=>{
const [value, setValue] = useState() //wrong usage!
return (<input
onChange={(e)=>setValue(e)}
value={value}
/>)})})
</div>
)}
You need to specify component that you wanna loop in new Component and use hook there:
const Input = ()=>{
const [value, setValue]= useState()
return <input
onChange={(e)=>setValue(e)}
value={value}
/>
}
and use it in loop
const App = ()=>{
const fiels = [someArray];
return (
fields.map((field)=>
<Input />
)
)
}

const Input = ()=>{
const [value, setValue]= useState()
return <input
onChange={(e)=>setValue(e)}
value={value} */ Don't mutate state /*
/>
}
We should not be mutating state. React needs to compare it's virtual DOM to the current DOM to determine what to re-render. Futhermore, react hooks are asynchronous, meaning that value may not be updated to e at the point you try and assign to value. This was already achieved with setValue(e).

Related

Automatic batching in React 18 is not working?

So, I have a controlled input component and have a div that takes another state variable. The two states are updated in a single call-back function. Why the value in the input and the text in the div below are not synched?
`import React, {useState, useRef} from "react";
const Input =()=>{
const [search, setSearch] = useState('jk');
const [text, setText] = useState('');
const onChange =(e)=>{
setSearch(e.target.value)
setText(search)
}
return(
<>
<input type='text' value={search} onChange={onChange} />
<div>{text}</div>
</>
)
};
export default Input`
I know about closure and stale state, but wasn't react 18's automatic batching was supposed to solve this?
React state updates are immutable. When you setSearch(e.target.value), you are not mutating the search variable within the component's closure. The search variable will not reflect your new value until after a subsequent render. Otherwise, we'd all just use regular variables and mutate them as needed, but then React would have no way of knowing that it should rerender.
Just to be more explicit about this:
const [search, setSearch] = useState('jk');
const [text, setText] = useState('');
const onChange = (e) => {
// the `search` variable above will not reflect this new value until the next render
setSearch(e.target.value)
// false (unless they typed "jk" again)
search === e.target.value
// you are still using the old search value from the last render when search was defined as "jk"
setText(search)
}
There are two ways around this. One is to simply set them both to the same value:
const newSearch = e.target.value
setSearch(newSearch)
setText(newSearch)
^ I personally prefer that one in this example because it is simple and should result in only a single rerender, but another pattern is to use useEffect, since the text being displayed is derived from the search value, or in other words you want to update the text as a side-effect of the search value changing.
const onChange = (e) => {
setSearch(e.target.value)
}
// whenever search changes, update the text
useEffect(() => {
setText(search)
}, [search])

How to pass the data from useSelector to useState to prevent re render

I want to pass the data from redux to useState but this cause multiple re-renders. It is another variant to do it?
This is my current variant that I`m using
export default function Test(){
const dispatch = useDispatch();
const currentData= useSelector(state => state.currentData);
const [value, setValue] = React.useState(null);
React.useEffect(() => {
dispatch(getData());
},[dispatch]);
React.useEffect(() => {
setValue(currentData.item);
},[data])
return (
<Autocomplete
options={data}
value={value}
/>
)
}
Thanks! And sorry for my English
If you just want to store the new value coming from the store without second render, you can just use useRef, in case you need to keep and read that value in an event handler, let's say.
If you only need the currentData.item part just for the current render, I don't see a reason to use any hooks, but just normal JS variable or directly pass currentData.item to your dependent components. Here's how it may look like with useRef.
export default function Test(){
const dispatch = useDispatch();
const currentData= useSelector(state => state.currentData);
const value = React.useRef(null);
value.current = currentData.item;
React.useEffect(() => {
dispatch(getData());
},[dispatch]);
return (
<Autocomplete
options={data}
value={value.current}
/>
)
}
EDIT:
Based on the sandbox provided, I created a fork. Note that I removed React.StrictMode in index.js. This doubles the number of renders in development only (by design). I also merged the country and location into a single state object, so now calling setState will cause 1 additional render instead of 2 as before. In total 2 renders on init - first, the usual one and second because of setting state in the effect.

Approach for useEffect to avoid exhaustive-dep warning

I keep running into situations where the exhaustive-dep rule complains. In a simple below I want to reset the value whenever the props.id value changes:
const TestComponent = props => {
const [value, setValue] = useState(props.value);
useEffect(() => {
setValue(props.value);
}, [props.id]);
const saveValue = () => {
props.save(value);
}
return <input value={value} onChange={e => setValue(e.target.value)} onBlur={saveValue} />;
};
However, the lint rule wants me to also add props.value to the dependencies. If I do that it will effectively break the component since the effect will run on every change to the input and thus reset the state.
Is there a "right" way of achieving this behavior?
The way you're written your component is in a half controlled half uncontrolled style, which is difficult to do. I would recommend going either fully controlled, or fully uncontrolled.
A "controlled" component is one where it just acts on the props its given. It takes the value from its props and forwards it to the input, with no state. When the input changes, it calls an onChange prop. When the input blurs, it calls an onBlur prop. All the business logic is handled by the parent component.
The other option is an uncontrolled component, which is mostly what you have. The parent will pass in a prop for the initial value. I'd recommend naming it initialValue so it's clear this will only be checked once. After that, everything is managed by the child component. This means your useEffect goes away entirely:
const TestComponent = props = {
const [value, setValue] = useState(props.initialValue);
const saveValue = () => {
props.save(value);
}
return <input value={value} onChange={e => setValue(e.target.value)} onBlur={saveValue} />;
}
So how do you reset this then? With a key. The parent component can use a standard react key to tell react that it should unmount the old component and mount a new one:
const ParentComponent = props = {
// some code...
return <TestComponent key={id} initialValue={"whatever"} />
}
As long as the id doesn't change, the TestComponent will do its thing, starting with the initialValue passed in. When the id changes, the TestComponent unmounts and a new one mounts in its place, which will start with the initialValue it's given.

How to use Refs in Function based Component [duplicate]

I was going through the hooks documentation when I stumbled upon useRef.
Looking at their example…
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
…it seems like useRef can be replaced with createRef.
function TextInputWithFocusButton() {
const inputRef = createRef(); // what's the diff?
const onButtonClick = () => {
// `current` points to the mounted text input element
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Why do I need a hook for refs? Why does useRef exist?
The difference is that createRef will always create a new ref. In a class-based component, you would typically put the ref in an instance property during construction (e.g. this.input = createRef()). You don't have this option in a function component. useRef takes care of returning the same ref each time as on the initial rendering.
Here's an example app demonstrating the difference in the behavior of these two functions:
import React, { useRef, createRef, useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [renderIndex, setRenderIndex] = useState(1);
const refFromUseRef = useRef();
const refFromCreateRef = createRef();
if (!refFromUseRef.current) {
refFromUseRef.current = renderIndex;
}
if (!refFromCreateRef.current) {
refFromCreateRef.current = renderIndex;
}
return (
<div className="App">
Current render index: {renderIndex}
<br />
First render index remembered within refFromUseRef.current:
{refFromUseRef.current}
<br />
First render index unsuccessfully remembered within
refFromCreateRef.current:
{refFromCreateRef.current}
<br />
<button onClick={() => setRenderIndex(prev => prev + 1)}>
Cause re-render
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
tldr
A ref is a plain JS object { current: <some value> }.
React.createRef() is a factory returning a ref { current: null } - no magic involved.
useRef(initValue) also returns a ref { current: initValue } akin to React.createRef(). Besides, it memoizes this ref to be persistent across multiple renders in a function component.
It is sufficient to use React.createRef in class components, as the ref object is assigned to an instance variable, hence accessible throughout the component and its lifecyle:
this.myRef = React.createRef(); // stores ref in "mutable" this context (class)
useRef(null) basically is equivalent to useState(React.createRef())[0] 1.
1 Replace useRef with useState + createRef
Following tweet has been enlightening for me:
useRef() is basically useState({current: initialValue })[0].
With insights from the tldr section, we now can further conclude:
useRef(null) is basically useState(React.createRef())[0].
Above code "abuses" useState to persist the returned ref from React.createRef(). [0] just selects the value part of useState - [1] would be the setter.
useState causes a re-render in contrast to useRef. More formally, React compares the old and new object reference for useState, when a new value is set via its setter method. If we mutate the state of useState directly (opposed to setter invocation), its behavior more or less becomes equivalent to useRef, as no re-render is triggered anymore:
// Example of mutating object contained in useState directly
const [ref] = useState({ current: null })
ref.current = 42; // doesn't cause re-render
Note: Don't do this! Use the optimized useRef API instead of reinventing the wheel. Above is for illustration purposes.
createRef always returns a new ref, which you'd generally store as a field on a class component's instance. useRef returns the same ref upon every render of a functional component's instance. This is what allows the state of the ref to persist between renders, despite you not explictly storing it anywhere.
In your second example, the ref would be re-created upon every render.
Just to highlight a purpose:
createRef is as simple as return {current: null}. It's a way to handle ref= prop in most modern way and that's it(while string-based is toooo way magic and callback-based looks too verboose).
useRef keeps some data before renders and changing it does not cause re-render(as useState does). They are rarely related. Everything you expect for class-based component go to instance fields(this.* =) looks like candidate to be implemented with useRef in functional components.
Say useCallback works as bounded class methods(this.handleClick = .....bind(this)) and may be re-implemented(but we should not re-invent the wheel for sure) with useRef.
Another examples are DOM refs, timeout/interval IDs, any 3rd party libraries' identifiers or references.
PS I believe React team better chose different naming for useRef to avoid confusion with createRef. Maybe useAndKeep or even usePermanent.
A ref is a plain JS object { current: }.
React.useRef(initValue) return a ref { current: initValue }
it is remember ref value across multiple render of function component.
It is advise to use in Function component
React.createRef(initValue) also return a ref { current: initValue }
it is not remember ref value across multiple render of function components. It is advise to use in class based component
Yet another but important addition to other's answers.
You can't set a new value for createRef. But you can for useRef.
const ur = useRef();
const cr = createRef();
ur.current = 10; // you can do it, and value is set
cr.current = 10; // you can, but it's no good, it will not change it

Is it possible to get rid of useContext calculations each rerender?

We can prevent unnecessary calculations for useEffect with an empty array as the second argument in the hook:
// that will be calculated every single re-rendering
useEffect(() => {...some procedures...})
// that will be calculated only after the first render
useEffect(() => {...some procedures...}, [])
But, for the useContext hook we can't do like above, provide the second argument.
Also, we can't wrap useContext by useCallback, useMemo.
For instance, we have a component:
const someComponent = () => {
const contextValue = useContext(SomeContext);
const [inputValue, setInputValue] = useState('');
return (
<Fragment>
<input value={inputValue} onChange={onInputChange} />
<span>{contextValue}</span>
</Fragment>
)
The problem is that every single typing will launch re-rendering and we will have unnecessary useContext re-rendering each time. One of the decision is brake the component on two:
const WithContextDataComponent = () => {
const contextValue = useContext(SomeContext);
return <JustInputComponent contextValue={contextValue} />
const JustInputComponent = (props) => {
const [inputValue, setInputValue] = useState('');
return <input value={inputValue} onChange={onInputChange} />
So, now the problem is disappeared, but two components we have, though. And in the upper component instead <SomeComponent /> we should import <WithContextDataComponent />, that a little bit ugly, I think.
Can I stop the unnecessary re-rendering for useContext without splitting into two components?
From React Hooks API Reference:
https://reactjs.org/docs/hooks-reference.html#usecontext
useContext
const value = useContext(MyContext);
Accepts a context object (the value returned from React.createContext) and returns the current context value for that context. The current context value is determined by the value prop of the nearest above the calling component in the tree.
When the nearest above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider.
As you can see from the documentation, the useContext() hook will only cause a re-render of your component if the values that it provides change at some point. Ans this is probably your intended behavior. Why would you need stale data in your context hook?
When your component re-render by itself, without changes in the context, the useContext() line will simply give back the same values that it did on the previous render.
It seems that you're using the useContext() hook the way it it meant to be used. I don't see anything wrong with it.

Resources