react / functional component / props changed / getDerivedStateFromProps - javascript

Lets say I'm doing a simple CRUD app in react. My functional component is basically just the form.
In the CREATE case I pass in an empty object via props
In the UPDATE case I pass in an object with the values via props (I got the data in the parent component with an API call)
I looks like this:
const MyForm = (props) => {
const [myValues, setMyValues] = useState(props.myValues);
const [errors, setErrors] = useState(0);
(...)
}
In the UPDATE case, I run (of course) into the issue that props.myValues is still empty when the component is mounted, and not set again (updated) when the api call from the parent component has finished thus leaving the form values empty.
Using a class component, I'd solve that with getDerivedStateFromProps(). Is there anything like that in a functional component? Or am I doing this wrong from the beginning? Thanks for any advice!

Yes, you can of course use useEffect.
In your case the code should look like this:
const MyForm = (props) => {
const [myValues, setMyValues] = useState(props.myValues);
const [errors, setErrors] = useState(0);
useEffect(() => {
setMyValues(props.myValues);
}, [props.myValues]);
}

Another way to do this is the Fully uncontrolled component with a key strategy. If you go that route, your component stays the same:
const MyForm = (props) => {
const [myValues, setMyValues] = useState(props.myValues);
const [errors, setErrors] = useState(0);
(...)
}
But there is one extra piece to its usage:
<MyForm myValues={myValues} key={myValues} />
Using the key prop like this means a new instance of this component will be created if myValues changes, meaning the default value you have provided to your internal myValues state will be reset to whatever the new values are.
This avoids the double render you get if you useEffect, but comes at the cost of a new component instance. I don't find this to be a problem if you do it at the individual input level, but you may not want to rebuild your whole form.

When you write:
const [myValues, setMyValues] = useState(props.myValues);
then myValues props is only used for initialize the myValues state.
Dan Abramov relates to this point in his latest article.
I would suggest:
1. Rename myValues prop to initialValues
2. Call the api after submit, and change your state according to the result.
It will look like:
const MyForm = (props) => {
const { initialValues, onSubmit } = this.props
const [myValues, setMyValues] = useState(initialValues);
...
const handleSubmit = () => {
// Assume that onSubmit is a call to Fetch API
onSubmit(myValues).then(response => response.json())
.then(updatedValues => setMyValues(updatedValues))
}
}

Related

Is it correct to use two different useState hook to store an array and a filtered array for passing to components?

I am currently porting my Pokemon project to React as I just learned the basics of React just a couple of weeks ago and want to see how well I can adapt it. Right now, the way I have my code architectured is to use two different useState hooks, one for the original array fetched from the PokeAPI and is only set once. This original array is also passed into my form object, which filters it according to a few form elements such as a Pokemon type or the Pokemon's name. And another useState hook is used to keep track of the filteredList which is what gets rendered to the website using the Array.map() function.
const [pokemonList, setPokemonList] = useState([]);
const [filteredList, setPokemonFilteredList] = useState([]);
Here's the useEffect() hook where we fetch and set the states
useEffect(() => {
const getPokemon = async () => {
const list = await fetchPokemon();
setPokemonList(list);
setPokemonFilteredList(list);
};
And finally the pokemonList state variable and setPokemonFilteredList methods get passed into the <PokemonSearchForm>
<PokemonSearchForm pokemonList={pokemonList} setPokemonList={setPokemonFilteredList} />
So as my question title suggests, is the way I use two different useState() 'correct'? Maybe a different way is for the child component to access pokemonList variable? But I believe this may be an anti-pattern.
It is better practice to not maintain duplicate or, in this case, derived state because you may run into divergence. For example, if your original pokemon data got updated, how would you make sure your filtered data got updated and then the filters re-applied? It gets hairy very fast.
The preferred alternative is to maintain the original data and filters in state and then compute the derived state (in this case, filter the list) during render.
function App() {
const [pokemonList, setPokemonList] = useState([]);
// Some default filter state
const [filters, setFilters] = useState({
types: ["any"],
search: ""
});
const filteredList = pokemonList.filter((pokemon) => {
// Filter logic here
});
return <PokemonSearchForm setFilters={setFilters} />
}
I would refactor this in a few different ways:
Keep the filter state in your parent component, the child component will simply notify it when those change.
Ditch the useState for useMemo which computes a value every time its dependencies change.
function YourComponent() {
const [filters, setFilters] = useState({});
const [pokemons, setPokemons] = useState([]);
useEffect(
() => {
const list = await fetchPokemon();
setPokemons(list);
},
[]
);
// This will run each time `filters` or `pokemons` change.
const filteredPokemons = useMemo(
() => {
return pokemons.filter((pokemon) => {
// Perform any filtering logic you may have,
// based on the filters set by the child component.
if (filters.name) {
return pokemon.name.includes(filters.name);
}
// etc...
});
},
[filters, pokemons]
);
return (
<PokemonSearchForm
pokemons={filteredPokemons}
onChange={setFilters}
/>
);
}

Re-initialized state

I have a complex component with some "dynamic" imports.
I recreate my component tree on codesandbox: https://codesandbox.io/s/clever-rosalind-t3cfm?file=/src/App.js:298-302
I have an App.js file where I have a list of objects. Each object, render a RenderWidget component.
Inside RenderWidget, I need to get a "dynamic" comp. (I recreated that with a simple function, but i have more complexity in that part). So I wrapped the comp on useMemo to avoid component re-creation. (useMemo has a console.log to check if the Comp is re-initializated)
Inside the Comp, we have a useState declaration with the following initial value:
const getData = () => {
console.log("getData called");
return Math.random();
};
const [data, setData] = useState(getData());
If You click on H1, we force an update on App.js. If you check the console, You get "getData called" each time that you click on H1.
Why?
If parent component props have changed it will re-render all of its children, you can use React.memo to prevent unnecessary re-render.
I also suggest you to read this article, it's written by the author of the react core team,
in addition to memo, there are other ways to prevent re-rendering
export default memo(function RenderWidget() {
const LocalComp = useMemo(() => {
console.log("NO RE-INITIALIZED");
return getComp();
}, []);
return <LocalComp />;
});
I got the answer!
For the "lazy" initial value, we need to call the function like this:
// Replace that
const [data, setData] = useState(getData());
// With that
const [data, setData] = useState(() => getData());

How to manage multiple states in React Native

I have a Screen component called "EditProfile" which contains three forms:
The UserInformationForm, which contains several information of the user as states.
The ChangePasswordForm, which contains the password and repeat password of the user as states
The ChangeAvatarForm, which contains the avatar uri as state.
All these components have a common parent, the screen EditProfile, as I said. In this screen I have the logic to upload all the information to a DB.
I need to access each child information, so imagine that I have all the forms states in the parent. I think that having multiple states (more than 20) in the parent is insane and can affect performance... So I have thought to wrap each child with the hook useImperativeHandle, just to retrieve all this data without having to save all the states in the parent and avoid unnecesary re-renders... I don't know if this is a good practice as in the documentation says to avoid this type of behaviour and use the common data flow...
Is it normal to have all those states in the parent? Can I use the useImperativeHandle hook in this situation? Any patterns?
Thank you.
UPDATE
I mean, something like this:
ChangeAvatarForm.js
const ChangeAvatarForm = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
getAvatar: () => avatar,
}));
const [avatar, setAvatar] = useState(props.avatar);
...
EditProfile.js (The parent screen which have all the forms)
export default function EditProfile(props) {
const { information } = props; // Current user information
// References to each form
const avatarFormRef = useRef(null);
const userInformationFormRef = useRef(null);
const passwordFormRef = useRef(null);
const submitChanges = () => {
// Get the new avatar
const newAvatar = avatarFormRef.current.getAvatar();
...
}
...

React hooks useCallback not reflecting updated state

I have a simplified version of my problem as follows:
const Component = () => {
const [data, setData] = useState([]);
const fn = useCallback((num) => {
const newData = [...data];
newData.push(num);
setData(newData);
}, [data]);
return <button onClick={() => fn(Math.random())}>{data.join()}</button>;
};
My problem is that newData is always [], instead of reflecting the updated state values. Therefore, my button will only show the latest data value instead of an array with increasing values. Why is this the case as I've included it in the dependency array of the useCallback function?
I guess the problem is most probably happen in the "button" component which you have simplified in your question therefore we cannot see the real problem.
There could be multiple reasons why states in the "useCallback" is not updated:
you didn't add the state in the dependency of "useCallback". This is
not your case as you already add data in the dependency
you use this "fn" in another "useCallback" and you didn't add fn as a dependency of that "useCallback".
// what you probably write in the "button" component
const handleClick = useCallback(() => {
...
doSomethingWithState(someState);
props.onClick(); //<--- this is the fn you passed
}, [someState]);
// what you should write
const handleClick = useCallback(() => {
...
doSomethingWithState(someState);
props.onClick(); //<--- this is the fn you passed
}, [someState, props.onClick]); //<---- you need to add the method as dependency as well
please verify if this is your case.
Your code works just as you expect:
https://codesandbox.io/s/relaxed-ishizaka-n2eg6?file=/src/App.js
It does show the updated state values.

How can I force a component to re-render with hooks in React?

Considering below hooks example
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Basically we use this.forceUpdate() method to force the component to re-render immediately in React class components like below example
class Test extends Component{
constructor(props){
super(props);
this.state = {
count:0,
count2: 100
}
this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component
}
setCount(){
let count = this.state.count;
count = count+1;
let count2 = this.state.count2;
count2 = count2+1;
this.setState({count});
this.forceUpdate();
//before below setState the component will re-render immediately when this.forceUpdate() is called
this.setState({count2: count
}
render(){
return (<div>
<span>Count: {this.state.count}></span>.
<button onClick={this.setCount}></button>
</div>
}
}
But my query is How can I force above functional component to re-render immediately with hooks?
This is possible with useState or useReducer, since useState uses useReducer internally:
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
forceUpdate isn't intended to be used under normal circumstances, only in testing or other outstanding cases. This situation may be addressed in a more conventional way.
setCount is an example of improperly used forceUpdate, setState is asynchronous for performance reasons and shouldn't be forced to be synchronous just because state updates weren't performed correctly. If a state relies on previously set state, this should be done with updater function,
If you need to set the state based on the previous state, read about the updater argument below.
<...>
Both state and props received by the updater function are guaranteed
to be up-to-date. The output of the updater is shallowly merged with
state.
setCount may not be an illustrative example because its purpose is unclear but this is the case for updater function:
setCount(){
this.setState(({count}) => ({ count: count + 1 }));
this.setState(({count2}) => ({ count2: count + 1 }));
this.setState(({count}) => ({ count2: count + 1 }));
}
This is translated 1:1 to hooks, with the exception that functions that are used as callbacks should better be memoized:
const [state, setState] = useState({ count: 0, count2: 100 });
const setCount = useCallback(() => {
setState(({count}) => ({ count: count + 1 }));
setState(({count2}) => ({ count2: count + 1 }));
setState(({count}) => ({ count2: count + 1 }));
}, []);
React Hooks FAQ official solution for forceUpdate:
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick={forceUpdate}>Force update</button>
Working example
const App = () => {
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
return (
<div>
<button onClick={forceUpdate}>Force update</button>
<p>Forced update {_} times</p>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js" integrity="sha256-vMEjoeSlzpWvres5mDlxmSKxx6jAmDNY4zCt712YCI0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.1/umd/react-dom.production.min.js" integrity="sha256-QQt6MpTdAD0DiPLhqhzVyPs1flIdstR4/R7x4GqCvZ4=" crossorigin="anonymous"></script>
<script>var useReducer = React.useReducer</script>
<div id="root"></div>
Generally, you can use any state handling approach you want to trigger an update.
With TypeScript
codesandbox example
useState
const forceUpdate: () => void = React.useState({})[1].bind(null, {}) // see NOTE below
useReducer (recommended)
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void
as custom hook
Just wrap whatever approach you prefer like this
function useForceUpdate(): () => void {
return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}
How this works?
"To trigger an update" means to tell React engine that some value has changed and that it should rerender your component.
[, setState] from useState() requires a parameter. We get rid of it by binding a fresh object {}.
() => ({}) in useReducer is a dummy reducer that returns a fresh object each time an action is dispatched.
{} (fresh object) is required so that it triggers an update by changing a reference in the state.
PS: useState just wraps useReducer internally, so use reducer to reduce complexity. source
NOTE: Referential instability
Using .bind with useState causes a change in function reference between renders.
It is possible to wrap it inside useCallback as already explained in this answer here, but then it wouldn't be a sexy one-linerâ„¢. The Reducer version already keeps reference equality (stability) between renders. This is important if you want to pass the forceUpdate function in props to another component.
plain JS
const forceUpdate = React.useState({})[1].bind(null, {}) // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
As the others have mentioned, useState works - here is how mobx-react-lite implements updates - you could do something similar.
Define a new hook, useForceUpdate -
import { useState, useCallback } from 'react'
export function useForceUpdate() {
const [, setTick] = useState(0);
const update = useCallback(() => {
setTick(tick => tick + 1);
}, [])
return update;
}
and use it in a component -
const forceUpdate = useForceUpdate();
if (...) {
forceUpdate(); // force re-render
}
See https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.ts and https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver.ts
Alternative to #MinhKha's answer:
It can be much cleaner with useReducer:
const [, forceUpdate] = useReducer(x => x + 1, 0);
Usage:
forceUpdate() - cleaner without params
You can simply define the useState like that:
const [, forceUpdate] = React.useState(0);
And usage: forceUpdate(n => !n)
Hope this help !
You should preferably only have your component depend on state and props and it will work as expected, but if you really need a function to force the component to re-render, you could use the useState hook and call the function when needed.
Example
const { useState, useEffect } = React;
function Foo() {
const [, forceUpdate] = useState();
useEffect(() => {
setTimeout(forceUpdate, 2000);
}, []);
return <div>{Date.now()}</div>;
}
ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Simple code
const forceUpdate = React.useReducer(bool => !bool)[1];
Use:
forceUpdate();
Potential option is to force update only on specific component using key. Updating the key trigger a rendering of the component (which failed to update before)
For example:
const [tableKey, setTableKey] = useState(1);
...
useEffect(() => {
...
setTableKey(tableKey + 1);
}, [tableData]);
...
<DataTable
key={tableKey}
data={tableData}/>
You can (ab)use normal hooks to force a rerender by taking advantage of the fact that React doesn't print booleans in JSX code
// create a hook
const [forceRerender, setForceRerender] = React.useState(true);
// ...put this line where you want to force a rerender
setForceRerender(!forceRerender);
// ...make sure that {forceRerender} is "visible" in your js code
// ({forceRerender} will not actually be visible since booleans are
// not printed, but updating its value will nonetheless force a
// rerender)
return (
<div>{forceRerender}</div>
)
One line solution:
const useForceUpdate = () => useState()[1];
useState returns a pair of values: the current state and a function that updates it - state and setter, here we are using only the setter in order to force re-render.
react-tidy has a custom hook just for doing that called useRefresh:
import React from 'react'
import {useRefresh} from 'react-tidy'
function App() {
const refresh = useRefresh()
return (
<p>
The time is {new Date()} <button onClick={refresh}>Refresh</button>
</p>
)
}
Learn more about this hook
Disclaimer I am the writer of this library.
My variation of forceUpdate is not via a counter but rather via an object:
// Emulates `forceUpdate()`
const [unusedState, setUnusedState] = useState()
const forceUpdate = useCallback(() => setUnusedState({}), [])
Because {} !== {} every time.
Solution in one single line:
const [,forceRender] = useReducer((s) => s+1, 0)
You can learn about useReducer here.
https://reactjs.org/docs/hooks-reference.html#usereducer
This will render depending components 3 times (arrays with equal elements aren't equal):
const [msg, setMsg] = useState([""])
setMsg(["test"])
setMsg(["test"])
setMsg(["test"])
const useForceRender = () => {
const [, forceRender] = useReducer(x => !x, true)
return forceRender
}
Usage
function Component () {
const forceRender = useForceRender()
useEffect(() => {
// ...
forceRender()
}, [])
For regular React Class based components, refer to React Docs for the forceUpdate api at this URL. The docs mention that:
Normally you should try to avoid all uses of forceUpdate() and only
read from this.props and this.state in render()
However, it is also mentioned in the docs that:
If your render() method depends on some other data, you can tell React
that the component needs re-rendering by calling forceUpdate().
So, although use cases for using forceUpdate might be rare, and I have not used it ever, however I have seen it used by other developers in some legacy corporate projects that I have worked on.
So, for the equivalent functionality for Functional Components, refer to the React Docs for HOOKS at this URL. Per the above URL, one can use the "useReducer" hook to provide a forceUpdate functionality for Functional Components.
A working code sample that does not use state or props is provided below, which is also available on CodeSandbox at this URL
import React, { useReducer, useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
// Use the useRef hook to store a mutable value inside a functional component for the counter
let countref = useRef(0);
const [, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {
countref.current++;
console.log("Count = ", countref.current);
forceUpdate(); // If you comment this out, the date and count in the screen will not be updated
}
return (
<div className="App">
<h1> {new Date().toLocaleString()} </h1>
<h2>You clicked {countref.current} times</h2>
<button
onClick={() => {
handleClick();
}}
>
ClickToUpdateDateAndCount
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
NOTE: An alternate approach using the useState hook (instead of useReducer) is also available at this URL.
There are many ways to force re-render in Hook.
For me simple way with useState() and tip of reference object values.
const [, forceRender] = useState({});
// Anywhre
forceRender({});
Codesandbox Example
A bit late to the party but I notice that most (all) of the answers have missed the part where you can pass a callback to forceUpdate lifecycle method.
As per the react source code, this callback has the same behavior as the one in the setState method - it is executed after the update.
Hence, the most correct implementation would be like this:
/**
* Increments the state which causes a rerender and executes a callback
* #param {function} callback - callback to execute after state update
* #returns {function}
*/
export const useForceUpdate = (callback) => {
const [state, updater] = useReducer((x) => x + 1, 0);
useEffect(() => {
callback && callback();
}, [state]);
return useCallback(() => {
updater();
}, []);
};
I was working with an array and spotted this issue. However, instead of explicit forceUpdate I found another approach - to deconstruct an array and set a new value for it using this code:
setRoutes(arr => [...arr, newRoute]); // add new elements to the array
setRouteErrors(routeErrs => [...routeErrs]); // the array elements were changed
I found it very interesting that setting even a copy of an array will not trigger the hook. I assume React does the shallow comparison

Categories