I am trying to integrate hooks with some legacy React code. My old react code triggers a callback from a componentDidMount. I wanted to use a hook in the callback to update some state, but that doesnt seem to work: the hook gets stuck with the original value. Here's a pared down, reproducible example:
https://jsfiddle.net/0yf2xm96/16/
const Test = () => {
const [value, setValue] = React.useState(0);
return (
<div>
<Test2 onMount={() => setValue(value + 1)} />
<Test2 onMount={() => setValue(value + 1)} />
{ value /* displays 1, but I'd like to see 2 */ }
</div>
);
}
class Test2 extends React.Component {
componentDidMount() {
this.props.onMount();
}
render() { return null; }
}
ReactDOM.render(<Test />, document.querySelector("#app"))
I want that to display 2, not 1 - how would i go about doing that?
You can pass a function to the useState hook that takes the previous state value as an argument, as seen in the docs.
Change your onMount to:
<Test2 onMount={() => setValue(prev=>prev + 1)} />
<Test2 onMount={() => setValue(prev=>prev + 1)} />
Ciao, I suggest you to modify your code like this:
const Test = () => {
const [value, setValue] = React.useState(0);
return (
<div>
<Test2 onMount={() => setValue(value => value + 1)} />
<Test2 onMount={() => setValue(value => value + 1)} />
{ value }
</div>
);
}
Explanation: you know hook are async and if you call just setValue(value + 1) you are not considering the previous value of value. Bu tif you use arrow funcition the problem will be solved.
Here your code modified.
TLDR: Value is "stale". Use a functional update.
What's causing the problem
Lets think about this in plain javascript terms first.
Test is a function. Yes, its a component so it gets used like a special function with special JSX syntax, but ultimately its just a function.
With that understanding, we can also understand that value, while it is part of a useState hook, is just a javascript variable. In fact, its a const which means its value cannot be changed after it has been declared. I think this is a misunderstanding that most people have about the useState hook - just because it's stateful does not mean it can abandon the rules of being a const.
When you call a useState updater, it will signal React that the component needs to re-render. This re-render may not happen immediately. When the component is re-rendered, the function Test is called again. The implications of this are that a new const called value is created. The new const is assigned the value passed from the useState updater. This is what makes the hook so powerful. Setting state is not a simple variable assignment, it is a way to maintain values through multiple function calls.
Back to the issue:
When you declare two arrow functions like you did here:
<Test2 onMount={() => setValue(value + 1)} />
<Test2 onMount={() => setValue(value + 1)} />
You can hopefully now see that value in both of these functions is 0. Even after the first function is called, value is a const and still retains the value 0. Both functions get called before the next render cycle, which means the second one to be called is victim to value going stale.
Solution
Use the callback version of setting state referred to as functional updates. Most people eventually figure out the solution whether they understand why or not. When using the updater form, the value of state passed into the callback function is always guaranteed to use the most recent version, thereby removing the issue.
Related
I'm having a hard time understanding the 'exhaustive-deps' lint rule.
I already read this post and this post but I could not find an answer.
Here is a simple React component with the lint issue:
const MyCustomComponent = ({onChange}) => {
const [value, setValue] = useState('');
useEffect(() => {
onChange(value);
}, [value]);
return (
<input
value={value}
type='text'
onChange={(event) => setValue(event.target.value)}>
</input>
)
}
It requires me to add onChange to the useEffect dependencies array. But in my understanding onChange will never change, so it should not be there.
Usually I manage it like this:
const MyCustomComponent = ({onChange}) => {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
onChange(event.target.value)
}
return (
<input
value={value}
type='text'
onChange={handleChange}>
</input>
)
}
Why the lint? Any clear explanation about the lint rule for the first example?
Or should I not be using useEffect here? (I'm a noob with hooks)
The reason the linter rule wants onChange to go into the useEffect hook is because it's possible for onChange to change between renders, and the lint rule is intended to prevent that sort of "stale data" reference.
For example:
const MyParentComponent = () => {
const onChange = (value) => { console.log(value); }
return <MyCustomComponent onChange={onChange} />
}
Every single render of MyParentComponent will pass a different onChange function to MyCustomComponent.
In your specific case, you probably don't care: you only want to call onChange when the value changes, not when the onChange function changes. However, that's not clear from how you're using useEffect.
The root here is that your useEffect is somewhat unidiomatic.
useEffect is best used for side-effects, but here you're using it as a sort of "subscription" concept, like: "do X when Y changes". That does sort of work functionally, due to the mechanics of the deps array, (though in this case you're also calling onChange on initial render, which is probably unwanted), but it's not the intended purpose.
Calling onChange really isn't a side-effect here, it's just an effect of triggering the onChange event for <input>. So I do think your second version that calls both onChange and setValue together is more idiomatic.
If there were other ways of setting the value (e.g. a clear button), constantly having to remember to call onChange might be tedious, so I might write this as:
const MyCustomComponent = ({onChange}) => {
const [value, _setValue] = useState('');
// Always call onChange when we set the new value
const setValue = (newVal) => {
onChange(newVal);
_setValue(newVal);
}
return (
<input value={value} type='text' onChange={e => setValue(e.target.value)}></input>
<button onClick={() => setValue("")}>Clear</button>
)
}
But at this point this is hair-splitting.
The main purpose of the exhaustive-deps warning is to prevent the developers from missing dependencies inside their effect and lost some behavior.
Dan abramov – developer on Facebook core – strongly recommend to keep that rule enabled.
For the case of passing functions as dependencies, there is a dedicated chapter in the React FAQ:
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
tl;dr
If you have to put a function inside your dependencies array:
Put the function outside of your component, so you are sure that the reference won't be changed on each render.
If you can, call the function outside of your effect, and just use the result as dependency.
If the function must be declared in your component scope, you have to memoize the function reference by using the useCallback hook. The reference will be changed only if the dependencies of the callback function change.
I am having some OOP issues that are probably pretty simple. I have a class that renders some html. However it has an onClick that calls a function that sets a flag inside the class if the image is clicked. Now here is the issue, when I render this class object and click the button from a separate js file, it stays false. I want it to permanently change the flag to true when clicked. here is the class...
class Settings extends React.Component {
handleClick() {
this.flag = true;
console.log(this.flag)
}
render(){
return(
<img src="./img/leaf.png" alt="" onClick={() => this.handleClick()}/>
);
}
}
and here is the code that calls it from a separate file...
const settingsObj = new Settings();
console.log(settingsObj.flag);
I want the flag to be false until the button is clecked and then it permamently changes to true. But it only goes true until my page rerenders as new data comes in and it resets to false. I have tried constructors and a few other techniques with no success.
Normal OOP design principles don't always apply directly to React components. Components don't usually have instance properties, they mostly just have props and state (there are a few exceptions where you do use an instance property, like Animation objects in react-native, but these are rare).
You're kind of mixing the two things in a way that doesn't quite make sense here. Settings is a React component that renders an image, but it's also an object which you instantiate by calling new Settings(). If there are other components which depend on the value of flag, you might want to separate the accessing and storing of the flag from the render component, passing a value and a callback to the renderer.
const Settings = ({setFlag}) => {
return(
<img src="./img/leaf.png" alt="" onClick={() => setFlag(true)}/>
);
}
You've suggested that you like the Context API as a solution for making the flag value globally available. There are a few ways to set this up, but here's one.
Outside of any component, we create a FlagContext object that has two properties: a boolean value flag and callback function setFlag. We need to give it a default fallback value, which is hopefully never used, so our default callback just logs a warning and does nothing.
const FlagContext = createContext<FlagContextState>({
flag: false,
setFlag: () => console.warn("attempted to use FlagContext outside of a valid provider")
});
This FlagContext object gives up Provider and Consumer components, but it's up to us to give a value to the FlagContext.Provider. So we'll create a custom component that handles that part. Our custom FlagProvider uses a local state to create and pass down the value. I've used a function component, but you could use a class component as well.
const FlagProvider = ({children}) => {
const [flag, setFlag] = useState(false);
return (
<FlagContext.Provider value={{
flag,
setFlag
}}>
{children}
</FlagContext.Provider>
)
}
We want to put the entire App inside of the FlagProvider so that the whole app has the potential to access flag and setFlag, and the whole app gets the same flag value.
When you want to use the value from the context in a component, you use either the useContext hook or the Consumer component. Either way, I like to creating an aliased name and export that rather than exporting the FlagContext object directly.
export const FlagConsumer = FlagContext.Consumer;
export const useFlagContext = () => useContext(FlagContext);
With the Consumer, the child of the consumer is a function that takes the value of the context, which in out case is an object with properties flag and setFlag, and returns some JSX.
This is usually a function you define inline:
const SomePage = () => {
return (
<FlagConsumer>
{({flag, setFlag}) => (<div>Flag Value is {flag.toString()}</div>)}
</FlagConsumer>
)
}
But it can also be a function component. Note that when using a function component as the child, you must pass the component itself ({Settings}) rather than an executed version of it (<Settings />).
const Settings = ({ setFlag }) => {
return <img src="./img/leaf.png" alt="" onClick={() => setFlag(true)} />;
};
const SomePage = () => {
return <FlagConsumer>{Settings}</FlagConsumer>;
};
The preferred method nowadays is with hooks. We call useFlagContext() inside the body of the function component and it returns our context object.
const SomePage = () => {
const {flag, setFlag} = useFlagContext();
return <Settings setFlag={setFlag}/>
};
Both the consumer and the hook only work if they are inside of a flag context provider, so that's why we put it around the whole app!
const App = () => {
return (
<FlagProvider>
<SomePage />
</FlagProvider>
);
};
Complete example on CodeSandbox
For this kind of interactions, I highly recommend you to use Redux
Another think I'm sure you will benefit from, is switching to hooks and function components: less boilerplate and much flexible code.
Back to the goal, using Redux your code would look similar to this:
const Settings = (props) => {
const dispatch = useDispatch();
const flag = useSelector(state => state.yourStoreObj.flag);
handleClick() {
dispatch(yourCustomAction("UPDATE_FLAG", true));
}
return(
<img src="./img/leaf.png" alt="" onClick={() => handleClick()}/>
);
}
Explanation:
First of all, spend 15 mins and get used to React Redux. Here's a good practical article to start with. If you're not familiar with hooks, start learning them as that will change a lot, while you don't need to change a single line of what you've done so far.
We suppose there's a property in the store that is the "flag" property of that specific element. In this way, the property can be read by the component itself with the useSelector() operator, or can be read anywhere in your application with the same methodology from any other component.
In the same way, you can change the value by dispatching a change (see dispatch() function) and in the same way, you can do that from any other components.
So, let's say you want to change that property when a click occurs on a completely different component, this is how the other component may looks like
const OtherCoolComp = (props) => {
const dispatch = useDispatch();
handleClick() {
dispatch(yourCustomAction("UPDATE_FLAG", true));
}
return(
<button onClick={() => handleClick()}>
Click me!
</button>
);
}
So you're dispatching the same action, setting it to the value you prefer, from a component that doesn't know who is displaying that value.
I have a functional component managing several states, amongst other things a state, which stores the index, with which I am rendering a type of table, when clicking a suitable button. OnClick that button calls a callback function, which runs a click handler. That click handler changes the index state, to the same 'index' as the array entry, in which I store an object with information for the rendering of a child component.
I would expect, that onClick the state would change before the rendering happens, so the component could render correctly. Yet it only happens a render later.
I already tried calling the useEffect-hook, to re-render, when that index state changes, but that didn't help neither.
Here is a shortened version of the code:
export const someComponent = () => {
[index, setIndex] = useState(-1);
const handleClick = (id) => {
setIndex(id);
// This is a function, I use to render the table
buildNewComponent(index);
}
}
Further 'down' in the code, I got the function, which is rendering the table entries. There I pass the onClick prop in the child component of the table as following:
<SomeEntryComponent
onClick={() => handleClick(arrayEntry.id)}
>
// some code which is not affecting my problem
</SomeEntryComponent>
So as told: when that onClick fires, it first renders the component when one presses it the second time, because first then the state changes.
Could anyone tell me why that happens like that and how I could fix it to work properly?
As other have stated, it is a synchronicity issue, where index is being updated after buildComponent has been invoked.
But from a design standpoint, it would be better to assert index existence by its value, as opposed to flagging it in a handler. I don't know the details behind buildComponent, but you can turn it into a conditional render of the component.
Your component rendering becomes derived from its data, as opposed to manual creation.
export const someComponent = () => {
[index, setIndex] = useState(-1);
const handleClick = (id) => setIndex(id);
const indexHasBeenSelected = index !== -1
return (
<div>
{indexHasBeenSelected && <NewComponent index={index} />}
</div>
)
}
When calling buildNewComponent the index is not yet updated. You just called setState, there is no guarantee that the value is updated immediately after that. You could use id here or call buildNewComponent within a useEffect that has index as its dependency.
I believe that you can have the correct behavior if you use useEffect and monitor index changes.
export const someComponent = () => {
[index, setIndex] = useState(-1);
useEffect(() => {
// This is a function, I use to render the table
buildNewComponent(index);
}, [buildNewComponent, index])
const handleClick = (id) => {
setIndex(id);
}
}
The process will be:
When the use clicks and call handleClick it will dispatch setIndex with a new id
The index will change to the same id
The useEffect will see the change in index and will call buildNewComponent.
One important thing is to wrap buildNewComponent with useCallback to avoid unexpected behavior.
Recently I saw code akin to the following contrived example:
const MyComp = props => {
const [prevProps, setPrevProps] = useState(props)
if (props !== prevProps) {
setPrevProps(props);
// do something else...
}
}
The component uses some kind of derived state to remember the previous props. If the memory location of props has changed (props !== prevProps), it will sync the new props to useState.
Yeah, it doesn't make that much sense, but my point is: Does React make any guarantees, that the object reference of props stays the same, given no contained property of props has changed?
At least, this seems to be the case for the tests I have done. If a parent component changes any of the props, there will be a new props object created.
I would appreciate any official documentation link proving this.
Does React make any guarantees, that the object reference of props stays the same, given no contained property of props has changed?
No, it doesn't.
If a parent component changes any of the props, there will be a new props object created.
If the parent re-renders, the child will always receive its props in a new object, even if nothing in it has changed.
If you want to prevent that, you will have to use
PureComponent or React.memo which will perform a shallow equal comparison of all the properties of the props object and prevent re-rendering if none of them changed.
const { memo, useState, useEffect } = React;
const Child = props => {
// only gets called when `props` changes
useEffect(() => console.log(props.name, ": props changed"), [props]);
return <p>{props.name}: {props.text}</p>;
};
const MemoChild = memo(Child);
const Parent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState("foo");
const handleTextChange = event => setText(event.target.value);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>
Click to force Re-Render
</button>
<input id="text" value={text} onChange={handleTextChange} />
<Child name="Child without memo()" text={text} />
<MemoChild name="Child with memo()" text={text} />
</div>
);
};
ReactDOM.render(<Parent />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Have a look at this example. You will see that when you press the button, the child not using memo will re-render, even if the props name or text have not changed. It will still receive a new props object.
But when you enter something into the input, both components re-render, because the text property of the props changed.
It should also be noted that this is only a performance optimization. You should not rely on memo preventing a re-render at all times.
This method only exists as a performance optimization. Do not rely on
it to “prevent” a render, as this can lead to bugs.
I am new to REACT so please correct me if I am wrong. When defining hooks, I can use the property all over my arrow function. Something like:
const CrawlJobs = () => {
const [crawlList, setCrawlList] = useState([]);
};
Everything OK here. As far as I understand the hooks in REACT is used because the render content know when data has changed and can update the DOM. But what if I have some simple properties/variables that is not used in the UI. Should I still define global variables as hooks? I tried defining a variable like this:
const statusId = 0;
However this could not be used globally in my arrow function component and doing below gave an error:
const this.statusId = 0;
So my question is should I always define all properties as hooks, or is there away to just define variables as standard variables in REACT? I dont want to define hooks if it is not needed.
You don't need to make a function to use useState so the correction:
const [crawlList, setCrawlList] = useState([]);
And functional component don't need this
You don't need to define your variables in the state as long as they are not affecting the application UI, component state usually used to notify the UI that there is a state it cares about changed, then the component itself should react to the change based on the state. the following is a silly example, just to demonstrate:
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
const label = 'You clicked';
return (
<div>
<p>{label} {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
So its totally fine to have variables without binding them to state.