it would be best to first look at my code:
import React, { Component } from 'react';
import _ from 'lodash';
import Services from 'Services'; // Webservice calls
export default class componentName extends Component {
constructor(props) {
super(props);
this.state = {
value: this.props.value || null
}
}
onChange(value) {
this.setState({ value });
// This doesn't call Services.setValue at all
_.debounce(() => Services.setValue(value), 1000);
}
render() {
return (
<div>
<input
onChange={(event, value) => this.onChange(value)}
value={this.state.value}
/>
</div>
)
}
}
Just a simple input. In the contructor it grabs value from the props (if available) at sets a local state for the component.
Then in the onChange function of the input I update the state and then try to call the webservice endpoint to set the new value with Services.setValue().
What does work is if I set the debounce directly by the onChange of the input like so:
<input
value={this.state.value}
onChange={_.debounce((event, value) => this.onChange(value), 1000)}
/>
But then this.setState gets called only every 1000 milliseconds and update the view. So typing in a textfield ends up looking weird since what you typed only shows a second later.
What do I do in a situation like this?
The problem occurs because you aren't calling the debounce function, you could do in the following manner
export default class componentName extends Component {
constructor(props) {
super(props);
this.state = {
value: this.props.value || null
}
this.servicesValue = _.debounce(this.servicesValue, 1000);
}
onChange(value) {
this.setState({ value });
this.servicesValue(value);
}
servicesValue = (value) => {
Services.setValue(value)
}
render() {
return (
<div>
<input
onChange={(event, value) => this.onChange(value)}
value={this.state.value}
/>
</div>
)
}
}
Solution for those who came here because throttle / debounce doesn't work with FunctionComponent - you need to store debounced function via useRef():
export const ComponentName = (value = null) => {
const [inputValue, setInputValue] = useState(value);
const setServicesValue = value => Services.setValue(value);
const setServicesValueDebounced = useRef(_.debounce(setServicesValue, 1000));
const handleChange = ({ currentTarget: { value } }) => {
setInputValue(value);
setServicesValueDebounced.current(value);
};
return <input onChange={handleChange} value={inputValue} />;
};
This medium article perfectly explains what happens:
Local variables inside a function expires after every call. Every time
the component is re-evaluated, the local variables gets initialized
again. Throttle and debounce works using window.setTimeout() behind
the scenes. Every time the function component is evaluated, you are
registering a fresh setTimeout callback.
So we will use useRef() hook as value returned by useRef() does not get re-evaluated every time the functional component is executed. The only inconvenience is that you have to access your stored value via the .current property.
I've created sandbox with tiny lodash.throttle and lodash.debounce packages so you can experiment with both and choose suitable behavior
For a React functional component, debounce does not work by default. You will have to do the following for it to work:
const debouncedFunction= React.useCallback(debounce(functionToCall, 400), []);
useCallback makes use of the function returned by debounce and works as expected.
Although, this is a bit more complicated when you want to use state variables inside the debounced function (Which is usually the case).
React.useCallback(debounce(fn, timeInMs), [])
The second argument for React.useCallback is for dependencies. If you would like to use a state or prop variable in the debounced function, by default, it uses an an old version of the state variable which will cause your function to use the historical value of the variable which is not what you need.
To solve this issue, you will have to include the state variable like you do in React.useEffect like this:
React.useCallback(debounce(fn, timeInMs), [stateVariable1, stateVariable2])
This implementation might solve your purpose. But you will notice that the debounced function is called every time the state variables (stateVariable1, stateVariable2) passed as dependencies change. Which might not be what you need especially if using a controlled component like an input field.
The best solution I realized is to put some time to change the functional component to a class based component and use the following implementation:
constructor(props)
{
super();
this.state = {...};
this.functionToCall= debounce(this.functionToCall.bind(this), 400, {'leading': true});
}
I wrote a hook for those who are using react functional components.
It's typescript, but you can ignore type annotations to use on your javascript application.
| use-debounce.ts |
import { debounce, DebounceSettings } from 'lodash'
import { useRef } from 'react'
interface DebouncedArgs<T> {
delay?: number
callback?: (value: T) => void
debounceSettings?: DebounceSettings
}
export const useDebounce = <T = unknown>({ callback, debounceSettings, delay = 700 }: DebouncedArgs<T>) => {
const dispatchValue = (value: T) => callback?.(value)
const setValueDebounced = useRef(debounce(dispatchValue, delay, { ...debounceSettings, maxWait: debounceSettings?.maxWait || 1400 }))
return (value: T) => setValueDebounced.current(value)
}
| usage: |
export const MyInput: FC = () => {
const [value, setValue] = useState<string>('')
const debounce = useDebounce({ callback: onChange })
const handleOnInput = (evt: FormEvent<HTMLInputElement>) => {
const { value } = evt.currentTarget
setValue(value)
debounce(value)
}
function onChange(value: string) {
// send request to the server for example
console.log(value)
}
return <input value={value} onInput={handleOnInput} />
}
Solution for functional component - use useCallback
export const ComponentName = (value = null) => {
const [inputValue, setInputValue] = useState(value);
const setServicesValue = value => Services.setValue(value);
const setServicesValueDebounced = useCallback(_.debounce(setServicesValue, 500), []);
const handleChange = ({ currentTarget: { value } }) => {
setInputValue(value);
setServicesValueDebounced(value);
};
return <input onChange={handleChange} value={inputValue} />;
};
Related
I'm having serious issues with the "new" React Context ( https://reactjs.org/docs/context.html ) to work like I want/expect from the documentation. I'm using React v.16.8.6 (upgrading will probably take ages, it's a big app). I know there is a bit of a mix between old and new stuff but plz don't get stuck on that..
I did it like this to be as flexible as possible but it doesn't work.
The issue is, when it comes to contextAddToCart(..) it only executes the empty function instead of the one I defined in state as the documentation this.addToCart. I have consumers in other places as well. It seems like perhaps it's executing this in the wrong order. Or every time a Compontent imports MinicartContext it's reset to empty fn.. I don't know how to get around this..
I'll just post the relevant code I think will explain it best:
webpack.config.js:
const APP_DIR = path.resolve(__dirname, 'src/');
module.exports = function config(env, argv = {}) {
return {
resolve: {
extensions: ['.js', '.jsx'],
modules: [
path.resolve(__dirname, 'src/'),
'node_modules',
],
alias: {
contexts: path.resolve(__dirname, './src/contexts.js'),
},
contexts.js
import React from 'react';
export const MinicartContext = React.createContext({
addToCart: () => {},
getState: () => {},
});
MinicartContainer.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
MinicartContext,
} from 'contexts';
export default class MinicartContainer extends Component {
constructor(props) {
super(props);
this.addToCart = (product, qty) => {
const { prices } = product;
const { grandTotal, qtyTotal } = this.state;
this.setState({
grandTotal: grandTotal + prices.price,
qtyTotal: qtyTotal + qty,
});
};
this.state = {
grandTotal: -1,
qtyTotal: -1,
currencyCode: '',
addToCart: this.addToCart,
};
}
render() {
const { children } = this.props;
return (
<MinicartContext.Provider value={this.state}>
{children}
</MinicartContext.Provider>
);
}
Header.jsx:
import React, { Component } from 'react';
import {
MinicartContext,
} from 'contexts';
class Header extends Component {
render() {
return (
<div>
<MinicartContainer MinicartContext={MinicartContext}>
<Minicart MinicartContext={MinicartContext} />
</MinicartContainer MinicartContext={MinicartContext}>
{/* stuff */}
<MinicartContainer MinicartContext={MinicartContext}>
<Minicart MinicartContext={MinicartContext} />
</MinicartContainer MinicartContext={MinicartContext}>
</div>
)
}
}
export default Header;
AddToCartButton.jsx
import {
MinicartContext,
} from 'contexts';
export default class AddToCartButton extends Component {
addToCart(e, contextAddToCart) {
e.preventDefault();
const QTY = 1;
const { product, active } = this.props;
// doing stuff ...
contextAddToCart(product, QTY);
}
render() {
return (
<React.Fragment>
<MinicartContext.Consumer>
{({context, addToCart}) => (
<div
onClick={(e) => { this.addToCart(e, addToCart); }}
Seems to me that you don't have fully understand how the context API words.
Here's my HOC implementation of contexts, maybe it can help you to understand better how things work.
export const MinicartContext = React.createContext({}) // Export the Context so we can use the Consumer in class and functional components (above). Don't use the Provider from here.
// Wrap the provider to add some custom values.
export const MinicartProvider = props => {
const addToCart = () => {
//Add a default version here
};
const getState = () => {
//Add a default version here
};
// Get the custom values and override with instance ones.
const value = {addToCart, getState, ...props.value}
return <MinicartContext.Provider value={value}>
{props.children}
</MinicartContext.Provider>
}
Then when using the provider:
const SomeComponent = props => {
const addToCart = () => {
//A custom version used only in this component, that need to override the default one
};
//Use the Wrapper, forget the MinicartContext.Provider
return <MinicartProvider value={{addToCart}}>
/* Stuff */
</MinicartProvider>
}
And when using the consumer you have three options:
Class Components with single context
export default class AddToCartButton extends Component {
static contextType = MinicartContext;
render (){
const {addToCart, getState} = this.context;
return (/*Something*/)
}
}
Class Components with multiple contexts
export default class AddToCartButton extends Component {
render (){
return (
<MinicartContext.Consumer>{value => {
const {addToCart, getState} = value
return (/*Something*/)
}}</MinicartContext.Consumer>
)
}
}
Functional Components
const AddToCartButton = props => {
const {addToCart, getState} = useContext(MinicartContext);
}
You can create the Wrapper Provider as a class component too, and pass the full state as value, but it's unnecessary complexity.
I Recommend you take a look at this guide about contexts, and also, avoid using the same name on the same scope... Your AddToCartButton.jsx file was reeeeally confusing :P
The issue I had was that I was using <MinicartContainer> in multiple places but all should act as one and the same. Changing it so it wrapped all elements made other elements reset their state when the context updated.
So the only solution I found was to make everything static (including state) inside MinicartContainer, and keep track of all the instances and then use forceUpdate() on all (needed) instances. (Since I am never doing this.setState nothing ever updates otherwise)
I though the new React context would be a clean replacement for things like Redux but as it stands today it's more a really vague specification which can replace Redux in a (sometimes) non standard way.
If you can just wrap all child Consumers with a single Provider component without any side-effects then you can make it a more clean implementation. That said I don't think what I have done is bad in any way but not what people expect a clean implementation should look like. Also this approach isn't mentioned in the docs at all either.
In addition to Toug's answer, I would memoize the exposed value prop of the provider. Otherwise it will re-render it's subscribers every time even if the state doesn't change.
export const MinicartContext = React.createContext({}) // Export the Context so we can use the Consumer in class and functional components (above). Don't use the Provider from here.
// Wrap the provider to add some custom values.
export const MinicartProvider = props => {
const addToCart = () => {
//Add a default version here
};
const getState = () => {
//Add a default version here
};
// Get the custom values and override with instance ones.
const value = useMemo(
() => ({addToCart, getState, ...props.value}),
[addToCart, getState, props.value]
);
return <MinicartContext.Provider value={value}>
{props.children}
</MinicartContext.Provider>
}
I am trying to convert Class Component to Stateless Functional Component using React Hooks concept
I am working with react-jsonschema-form - Custom field components reference link
const schema = {
type: "object",
required: ["lat", "lon"],
properties: {
lat: {type: "number"},
lon: {type: "number"}
}
};
// Define a custom component for handling the root position object
class GeoPosition extends React.Component {
constructor(props) {
super(props);
this.state = {...props.formData};
}
onChange(name) {
return (event) => {
this.setState({
[name]: parseFloat(event.target.value)
}, () => this.props.onChange(this.state));
};
}
render() {
const {lat, lon} = this.state;
return (
<div>
<input type="number" value={lat} onChange={this.onChange("lat")} />
<input type="number" value={lon} onChange={this.onChange("lon")} />
</div>
);
}
}
// Define the custom field component to use for the root object
const uiSchema = {"ui:field": "geo"};
// Define the custom field components to register; here our "geo"
// custom field component
const fields = {geo: GeoPosition};
// Render the form with all the properties we just defined passed
// as props
render((
<Form
schema={schema}
uiSchema={uiSchema}
fields={fields} />
), document.getElementById("app"));
I am converting the above code like this.
function GeoPosition(props) {
const [state, setState] = React.useState({ ...props.formData });
const onChange = name => {
return event => {
setState(
{
[name]: parseFloat(event.target.value)
},
() => props.onChange(state) // GETTING ERROR - UNABLE TO USE CALLBACK
);
};
};
const { lat, lon } = state;
return (
<div>
<input type="number" value={lat} onChange={onChange("lat")} />
<input type="number" value={lon} onChange={onChange("lon")} />
</div>
);
}
It throws an error, I think, I need to use React.useEffect(), but don't how to implement it. kindly any react experts support.
index.js:1375 Warning: State updates from the useState() and
useReducer() Hooks don't support the second callback argument. To
execute a side effect after rendering, declare it in the component
body with useEffect().
The setter function from useState does not accept a second argument: [hooks] useState - "setState" callback. I'm not sure you need to use useEffect here, you can just call props.onChange(state) after setting the state values. Also note that you need to concat the existing state with the new state values because setState will overwrite existing state.
const onChange = name => {
return event => {
setState(state => {
...state,
[name]: parseFloat(event.target.value)
})
props.onChange(state);
};
};
If you really need to make sure props.onChange is called only after the new value has been set on the current component's state, you can track the state in useEffect, altho you'll need to use a custom function for deep comparison: react useEffect comparing objects
useEffect(() => {
props.onChange(state);
}, [deepCompare(state)])
I have a component that I can use multiple times on a page. What it does is make an external call and save a value from that external call to redux store in a key object. I only want the component to do this once so I was using the componentDidMount. Now if the same component gets used again on the page I don't want it to do the external call again. This works correctly using Classes but when I try to use function hooks this no longer works.
Let me start with showing you the Class based code.
class MyComponent extends Component {
componentDidMount() {
setTimeout(() => this.wait(), 0);
}
wait() {
const { key, map } = this.props;
if (map[key] === undefined) {
saveKey(key);
console.log('LOAD EXTERNAL ID ONLY ONCE');
externalAPI(this.externalHandler.bind(this));
}
}
externalHandler(value) {
const { key, setValue } = this.props;
setValue(key, value);
}
render() {
const { key, map children } = this.props;
return (
<>
{children}
</>
);
}
}
mapStateToProps .....
export default connect(mapStateToProps, { saveKey, setValue })(MyComponent);
Reducer.js
export default (state = {}, action = null) => {
switch (action.type) {
case SAVE_KEY: {
return {
...state,
[action.id]: 'default',
};
}
case SET_VALUE: {
const { id, value } = action;
return {
...state,
[id]: value,
};
}
default: return state;
}
};
Page.js
Calls each component like below.
import React from 'react';
const Page = () => {
return (
<>
<MyComponent key='xxxxx'>First Component</MyComponent>
<MyComponent key='xxxxx'>Second Component</MyComponent>
</>
);
};
export default Page;
The above all works. So when the first component mounts i delay a call to redux, not sure why this works but it does. Can someone tell me why using the setTimeout works??? and not using the setTimeout does not. By works I mean with the timeout, first component mounts sets key because map[key] === undefined. Second component mounts map[key] is no longer undefined. But without the timeout map[key] is always === undefined ?
It stores the passed key prop in redux. The Second component mounts and sees the same key is stored so it doesn't need to call the external API getExternalID again. If a third component mounted with a different key then it should run the external API call again and so on.
As I said the above all works except I'm not sure why I needed to do a setTimout to get it to work.
Second question turning this into a function and hooks instead of a Class. This does not work for some reason.
import React, { useEffect } from 'react';
const MyComponent = ({ children, key, map, saveKey, setValue }) => {
useEffect(() => {
setTimeout(() => delay(), 0);
}, [map[key]]);
const delay = () => {
if (map[key] === undefined) {
saveKey(key);
console.log('LOAD VARIANT ONLY ONCE');
externalAPI(externalHandler);
}
};
const externalHandler = (value) => {
setValue(key, value);
};
return (
<>
{children}
</>
);
};
export default MyComponent;
First question:
Javascript works with a single thread, so even when you use delay 0 ms, the method is called after React's render method exit. Here is an experiment that I believe explains that for that :
function log(msg){
$("#output").append("<br/>"+msg);
}
function render(){
log("In render");
log("Delayed by 0 ms without setTimeout...")
setTimeout(() =>log("Delayed by 0 ms with setTimeout..."), 0);
for(var i = 0;i<10;i++) log("Delay inside render "+i);
log("Render finish");
}
render();
https://jsfiddle.net/hqbj5xdr/
So actually all the components render once before they start checking if map[key] is undefined.
Second question:
In your example, it is not really clear where map and saveKey come from. And how map is shared between components...
Anyway, a naive solution is to directly update the map. And make sure that all component
refers to this map instance (not a copy).
if (map[key] === undefined) {
map[key]=true;
saveKey(key);
console.log('LOAD VARIANT ONLY ONCE');
externalAPI(externalHandler);
}
But that is a little bad (sharing reference). So a better design may be to use a cache. Don't pass the map, but a getter and a setter. getKey and saveKey. Underlying those methods may use a map to persist which key has been set.
I was experimenting with the new Hook feature in React. Considering I have the following two components (using React Hooks) -
const HookComponent = () => {
const [username, setUsername] = useState('Abrar');
const [count, setState] = useState();
const handleChange = (e) => {
setUsername(e.target.value);
}
return (
<div>
<input name="userName" value={username} onChange={handleChange}/>
<p>{username}</p>
<p>From HookComponent: {count}</p>
</div>
)
}
const HookComponent2 = () => {
const [count, setCount] = useState(999);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Hooks claim to solve the problem of sharing stateful logic between components but I found that the states between HookComponent and HookComponent2 are not sharable. For example the change of count in HookComponent2 does not render a change in the HookComponent.
Is it possible to share states between components using the useState() hook?
If you are referring to component state, then hooks will not help you share it between components. Component state is local to the component. If your state lives in context, then useContext hook would be helpful.
Fundamentally, I think you misunderstood the line "sharing stateful logic between components". Stateful logic is different from state. Stateful logic is stuff that you do that modifies state. For e.g., a component subscribing to a store in componentDidMount() and unsubscribing in componentWillUnmount(). This subscribing/unsubscribing behavior can be implemented in a hook and components which need this behavior can just use the hook.
If you want to share state between components, there are various ways to do so, each with its own merits:
1. Lift State Up
Lift state up to a common ancestor component of the two components.
function Ancestor() {
const [count, setCount] = useState(999);
return <>
<DescendantA count={count} onCountChange={setCount} />
<DescendantB count={count} onCountChange={setCount} />
</>;
}
This state sharing approach is not fundamentally different from the traditional way of using state, hooks just give us a different way to declare component state.
2. Context
If the descendants are too deep down in the component hierarchy and you don't want to pass the state down too many layers, you could use the Context API.
There's a useContext hook which you can leverage on within the child components.
3. External State Management Solution
State management libraries like Redux or Mobx. Your state will then live in a store outside of React and components can connect/subscribe to the store to receive updates.
It is possible without any external state management library. Just use a simple observable implementation:
function makeObservable(target) {
let listeners = []; // initial listeners can be passed an an argument aswell
let value = target;
function get() {
return value;
}
function set(newValue) {
if (value === newValue) return;
value = newValue;
listeners.forEach((l) => l(value));
}
function subscribe(listenerFunc) {
listeners.push(listenerFunc);
return () => unsubscribe(listenerFunc); // will be used inside React.useEffect
}
function unsubscribe(listenerFunc) {
listeners = listeners.filter((l) => l !== listenerFunc);
}
return {
get,
set,
subscribe,
};
}
And then create a store and hook it to react by using subscribe in useEffect:
const userStore = makeObservable({ name: "user", count: 0 });
const useUser = () => {
const [user, setUser] = React.useState(userStore.get());
React.useEffect(() => {
return userStore.subscribe(setUser);
}, []);
const actions = React.useMemo(() => {
return {
setName: (name) => userStore.set({ ...user, name }),
incrementCount: () => userStore.set({ ...user, count: user.count + 1 }),
decrementCount: () => userStore.set({ ...user, count: user.count - 1 }),
}
}, [user])
return {
state: user,
actions
}
}
And that should work. No need for React.Context or lifting state up
This is possible using the useBetween hook.
See in codesandbox
import React, { useState } from 'react';
import { useBetween } from 'use-between';
const useShareableState = () => {
const [username, setUsername] = useState('Abrar');
const [count, setCount] = useState(0);
return {
username,
setUsername,
count,
setCount
}
}
const HookComponent = () => {
const { username, setUsername, count } = useBetween(useShareableState);
const handleChange = (e) => {
setUsername(e.target.value);
}
return (
<div>
<input name="userName" value={username} onChange={handleChange}/>
<p>{username}</p>
<p>From HookComponent: {count}</p>
</div>
)
}
const HookComponent2 = () => {
const { count, setCount } = useBetween(useShareableState);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
We move React hooks stateful logic from HookComponent to useShareableState.
We call useShareableState using useBetween in each component.
useBetween is a way to call any hook. But so that the state will not be stored in the React component.
For the same hook, the result of the call will be the same. So we can call one hook in different components and work together on one state. When updating the shared state, each component using it will be updated too.
Disclaimer: I'm the author of the use-between package.
the doc states:
We import the useState Hook from React. It lets us keep local state in a function component.
it is not mentioned that the state could be shared across components, useState hook just give you a quicker way to declare a state field and its correspondent setter in one single instruction.
I've created hooksy that allows you to do exactly this - https://github.com/pie6k/hooksy
import { createStore } from 'hooksy';
interface UserData {
username: string;
}
const defaultUser: UserData = { username: 'Foo' };
export const [useUserStore] = createStore(defaultUser); // we've created store with initial value.
// useUserStore has the same signature like react useState hook, but the state will be shared across all components using it
And later in any component
import React from 'react';
import { useUserStore } from './userStore';
export function UserInfo() {
const [user, setUser] = useUserStore(); // use it the same way like useState, but have state shared across any component using it (eg. if any of them will call setUser - all other components using it will get re-rendered with new state)
function login() {
setUser({ username: 'Foo' })
}
return (
<div>
{!user && <strong>You're logged out<button onPress={login}>Login</button></strong>}
{user && <strong>Logged as <strong>{user.username}</strong></strong>}
</div>
);
}
With hooks its not directly possible.
I recommend you to take a look at react-easy-state.
https://github.com/solkimicreb/react-easy-state
I use it in big Apps and it works like a charm.
I'm going to hell for this:
// src/hooks/useMessagePipe.ts
import { useReducer } from 'react'
let message = undefined
export default function useMessagePipe() {
const triggerRender = useReducer((bool) => !bool, true)[1]
function update(term: string) {
message = term.length > 0 ? term : undefined
triggerRender()
}
return {message: message, sendMessage: update}
}
Full explanation over at: https://stackoverflow.com/a/72917627/1246547
Yes, this is the dirtiest and most concise way i could come up with for solving that specific use case. And yes, for a clean way, you probably want to learn how to useContext, or alternatively take a look at react-easy-state or useBetween for low-footprint solutions, and flux or redux for the real thing.
You will still need to lift your state up to an ancestor component of HookComponent1 and HookComponent2. That's how you share state before and the latest hook api doesnt change anything about it.
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