React Context Api and State hooks mutation - javascript

I have a modern react application using the context api and many hooks, i use the context to store global values for my application, this values or the context itself should never directly re-render other components, the context itself has a its own getter/setter in form of the UseState hook/s which is what is called from the consumer components to be used, if any component is dependent on the context data a separate state in this component itself is created and state is then being properly handled.
My concrete question in my case how bad is it to directly mutate the object i have stored in the context?
for example from any random consumer component changing the context object as follows:
const handlerFunction = () => {contextObjData.value = "Something"};
Instead of the "intended" react way:
const handlerFunction = () => {setContextObjData(...contextObjData, value: "Something")};
To me it seems overkill to each time save the entire object again but maybe someone can give me another perspective and some insights.
Side question kind of nooby but i am not sure, is there a difference between these two:
const handlerFunction = () => {setContextObjData(...contextObjData, value: "Something")};
const handlerFunction = () => {setContextObjData(prevState => ({...contextObjData, value: "Something"}));

A state change will trigger a render. When you mutate something then React won't detect that the state has changed and will not re render.
The handlerFunction examples matter only if you want to optimize it using useCallback but the way you do it it is broken either way (syntax error and not using prevState in the second example).
//handlerFunction will be re created every render
const handlerFunction = () =>
setContextObjData({
...contextObjData,
value: 'Something',
});
//handler function will only be created on mount
const optimizedHandler = React.useCallback(
() =>
setContextObjData((prevState) => ({
...prevState,
value: 'Something',
})),
[] //empty dependency, only create optimizedHandler on mount
);
//broken handler, using stale closure will give you liner warning
const brokenHandler = React.useCallback(
() =>
//callback only used on mount so has contextObjData
// as it was when mounted (stale closure)
setContextObjData({
...contextObjData, //needs contextObjData in closure scope
value: 'Something',
}),
[] //empty dependency, but contextObjData will be a stale closure
);
Pure components will only re render when props change, state change or return value from useSelector or useContext change.
When you pass a callback as a prop to a child and your component re renders without the child needing to re render you can optimize a passed callback with useCallback so the child doesn't get needlessly re rendered:
//Child is a pure component
const Child = React.memo(function Increment({ increment }) {
const r = React.useRef(0);
r.current++;
return (
<button onClick={increment}>
rendered: {r.current} times, click to increment
</button>
);
});
const Parent = () => {
const [count, setCount] = React.useState(1);
const increment = React.useCallback(
() => setCount((c) => c + 1),
[]
);
React.useEffect(() => {
const t = setInterval(() => increment(), 1000);
return () => clearInterval(t);
}, [increment]);
return (
<div>
<h4>{count}</h4>
<Child increment={increment} />
</div>
);
};
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Here is an example how mutation will break re rendering:
const CounterContext = React.createContext();
const CounterProvider = ({ children }) => {
console.log('render counter provider');
const [c, setC] = React.useState({ count: 0 });
const increment = React.useCallback(
() =>
setC((c) => {
console.log('broken:', c.count);
c.count++;
return c;
}), //broken, context users never re render
[]
);
return (
<CounterContext.Provider value={[c, increment]}>
{children}
</CounterContext.Provider>
);
};
const App = () => {
console.log('render App');
const [count, increment] = React.useContext(
CounterContext
);
return (
<div>
<h4>count: {count.count}</h4>
<button onClick={increment}>+</button>
</div>
);
};
ReactDOM.render(
<CounterProvider>
<App />
</CounterProvider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

Access fresh REDUX state in function of React component

I'm using React and React-Redux-Toolkit and difficulties accessing the updated state from within a function.
I have a custom React Component which I pass a variable of my state:
const { useEffect, useState } = React;
const ExternalComponent = ({ func }) => {
func();
return (
<div></div>
);
};
const Component = ({ state }) => {
console.log("Component", state);
const someFunction = () => {
console.log("state", state);
};
return (
<ExternalComponent func={someFunction} />
);
};
const Page = () => {
const [state, setState] = useState();
useEffect(() => {
setState(true);
}, []);
return (
<Component state={state} />
);
};
ReactDOM.render(
<Page />,
document.getElementById('root')
);
<body>
<div id="root"></div>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script type="text/babel" src="main.js"></script>
</body>
On initial page render the component prints Component undefined, because state is yet undefined, which is understandable and not the problem.
When I update state by any means, the component re-renders and prints Component <the change>, which is also as expected.
However, someFunction() will not update and still print the old state, which is still undefined.
How do I get someFunction() to "update", i.e. print the updated state?
Edit: Typo
Edit2: I created a code snippet in which everything works as expected. However, in my actual code I'm using PaypalButtons, where I pass someFunction to createOrder. I'm not sure how PaypalButtons works and why someFunction would not get updated.
Edit3: I'm using paypal-react-js
This is happening because you have a stale value. Try using useCallback
import { useCallback } from 'react';
const Component = ({ statefulVariable }) => {
console.log("Component", statefulVariable);
const someFunction = useCallback(() => {
console.log(statefulVariable);
}), [statefulVariable]);
return (
<SomeThirdPartyComponent callback={someFunction}/>
);
};

Usage of useCallback and setting new object state using previous state as argument

Consider this basic form fields component with a custom form hook to handle input changes:
import React, { useState, useCallback } from 'react';
const useFormInputs = (initialState = {})=> {
const [values, setValues] = useState(initialState);
const handleChange = useCallback(({ target: { name, value } }) => {
setValues(prev => ({ ...prev, [name]: value }));
}, []);
const resetFields = useCallback(() =>
setValues(initialState), [initialState]);
return [values, handleChange, resetFields];
};
const formFields = [
{ name: 'text', placeholder: 'Enter text...', type: 'text', text: 'Text' },
{ name: 'amount', placeholder: 'Enter Amount...', type: 'number',
text: 'Amount (negative - expense, positive - income)' }
];
export const AddTransaction = () => {
const [values, handleChange, resetFields] = useFormInputs({
text: '', amount: ''
});
return <>
<h3>Add new transaction</h3>
<form>
{formFields.map(({ text, name, ...attributes }) => {
const inputProps = { ...attributes, name };
return <div key={name} className="form-control">
<label htmlFor={name}>{text}</label>
<input {...inputProps} value={values[name]}
onChange={handleChange} />
</div>;
})}
<button className="btn">Add transaction</button>
</form>
<button className="btn" onClick={resetFields}>Reset fields</button>
</>;
};
Is there really any reason / advantage for me to use useCallback to cache the function in my custom hook? I read the docs, but I just coudln't grasp the idea behind this usage of useCallback. How exactly it memoizes the function between renders? How exactly does ti work, and should I use it?
Inside the same custom hook, you can see the new values state being updated by spreading the previous state and creating a new object like so: setValues(prev => ({ ...prev, [name]: value }));
Would there be any difference if I did this instead? setValues({ ...prev, [name]: value })
as far as I can tell, doesn't look like it has any difference right? I am simply accessing the state directly.. Am I wrong?
Your first question:
In your case it doesn't matter because everything is rendered in the same component. If you have a list of things that get an event handler then useCallback can save you some renders.
In the example below the first 2 items are rendered with an onClick that is re created every time App re renders. This will not only cause the Items to re render it will also cause virtual DOM compare to fail and React will re create the Itms in the DOM (expensive operation).
The last 2 items get an onClick that is created when App mounts and not re created when App re renders so they will never re render.
const { useState, useCallback, useRef, memo } = React;
const Item = memo(function Item({ onClick, id }) {
const rendered = useRef(0);
rendered.current++;
return (
<button _id={id} onClick={onClick}>
{id} : rendered {rendered.current} times
</button>
);
});
const App = () => {
const [message, setMessage] = useState('');
const onClick = (e) =>
setMessage(
'last clicked' + e.target.getAttribute('_id')
);
const memOnClick = useCallback(onClick, []);
return (
<div>
<h3>{message}</h3>
{[1, 2].map((id) => (
<Item key={id} id={id} onClick={onClick} />
))}
{[1, 2].map((id) => (
<Item key={id} id={id} onClick={memOnClick} />
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Another example is when you want to call a function in an effect that also needs to be called outside of the effect so you can't put the function inside the effect. You only want to run the effect when a certain value changes so you can do something like this.
//fetchById is (re) created when ID changes
const fetchById = useCallback(
() => console.log('id is', ID),
[ID]
);
//effect is run when fetchById changes so basically
// when ID changes
useEffect(() => fetchById(), [fetchById]);
Your second question:
The setValues({ ...prev, [name]: value }) will give you an error because you never defined pref but if you meant: setValues({ ...values, [name]: value }) and wrap the handler in a useCallback then now your callback has a dependency on values and will be needlessly be re created whenever values change.
If you don't provide the dependency then the linter will warn you and you end up with a stale closure. Here is an example of the stale closure as counter.count will never go up because you never re create onClick after the first render thus the counter closure will always be {count:1}.
const { useState, useCallback, useRef } = React;
const App = () => {
const [counts, setCounts] = useState({ count: 1 });
const rendered = useRef(0);
rendered.current++;
const onClick = useCallback(
//this function is never re created so counts.count is always 1
// every time it'll do setCount(1+1) so after the first
// click this "stops working"
() => setCounts({ count: counts.count + 1 }),
[] //linter warns of missing dependency count
);
return (
<button onClick={onClick}>
count: {counts.count} rendered:{rendered.current}
</button>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Should renderItem of FlatList be wrapped with react useCallback hook?

const Component = React.memo(props => {
const { url } = props;
const keyExtractor = useCallback(item => item.id, []);
const handleClick = useCallback(() => {
Linking.openURL(url);
}, [url]);
const renderItem = useCallback(({ item }) => {
return (
<TouchableOpacity onPress={handleClick}>
<Text>Test</Text>
</TouchableOpacity>
);
}, [handleClick]);
return (
<FlatList
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
/>
);
});
Just like the code above.
If the renderItem function is wrapped with a useCallback hook. it must check if the handleClick function reference is changed because the handleClick function may be changed if url is different. and it looks a little strange.
I wonder if there is a significant difference in the performance of using useCallback to wrap renderItem.
What is the best practice of using hooks for renderItem functions? Thanks
Because you already use React.memo the Component will not be executed unless url changes, if url changes the useCallback will re create the functions anyway so you can leave them out provided that is the only prop that can change.
Here is some code demonstrating this, you can re render App as much as you want but it won't re render other components unless you change url.
const SubComponent = ({ onClick }) => {
console.log('sub component render');
return <button onClick={onClick}>log url</button>;
};
const PureComponent = React.memo(function PureComponent({
url,
}) {
console.log('pure component render', url);
//even if I do React.useCallback(fn,[url]) that would mean
// it creates onClick when url changes but it would already
// only create onClick when url changes because memo will
// memoize the component result and not execute PureComponent
// unless the url changes
const onClick = () => console.log('url is', url);
return <SubComponent onClick={onClick} />;
});
const App = () => {
const [, reRenderApp] = React.useState({});
const [url, setUrl] = React.useState(
new Date().toUTCString()
);
console.log('rendering App');
return (
<div>
<button onClick={() => reRenderApp({})}>
re render app
</button>
<button
onClick={() => setUrl(new Date().toUTCString())}
>
set url
</button>
<PureComponent url={url} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

useState always is default value in itself

I'm using useState to manage the state, it's working fine. But when i return state inside itseft, it always value of initial
import react, {useState} from 'react'
const MyComponent = () => {
const [myState, setMyState] = useState({
value: 'initial value',
setValue: (newValue) => {
setMyState({...myState, value: newValue})
console.log(myState.value) //<--always is 'initial value'
}
})
return(
<>
<p>{myState.value}</p> //<-- it's working fine
<input value={myState.value} onChange={(e) => myState.setValue(e.target.value)} /> //<--working fine too
</>
)
}
I expect the console.log is value of input, but the actual output always is initial value
const [myState, setMyState] = useState({
value: 'initial value',
setValue: (newValue) => {
setMyState({...myState, value: newValue})
console.log(myState.value) //<--always is 'initial value'
}
})
The first time your component function is run, the setValue function captures the initial value of myState. The second time it is run, you make a copy of the setValue function—but this is the function that has has captured the initial value of myState. It never updates.
Since the function never changes, you should not put it in useState() in the first place. You can define the function separately.
const [myState, setMyState] = useState({ value: 'initial value' })
const setValue = (newValue) => {
setMyState({ ...myState, value: newValue })
}
Now, a new setValue copy is created every time the component function is run. When capturing variables, you can use useCallback() for optimization; if the values didn't change, React will reuse old copies of the function.
const [myState, setMyState] = useState({ value: 'initial value' })
const setValue = useCallback((newValue) => {
setMyState({ ...myState, value: newValue })
}, [myState]) // ← this bit ensures that the value is always up to date inside the callback
As mentioned by Shubham Khatri, there is an even faster and better approach in this situation: using the functional form of setState.
const [myState, setMyState] = useState({ value: 'initial value' })
const setValue = useCallback((newValue) => {
setMyState((prev) => ({ ...prev, value: newValue }))
}, []) // ← now we use an empty array, so React will never update this callback
Any of these three methods are fine to use, though; they will all work and perform good enough for most use cases.
Per comments, you're attempting to create an object that is passed down via context. A way to do this is by creating the context object in a separate step, similarly to how we created the callback function. This time, we use useMemo, which is similar to useCallback but works for any type of object.
// Per T.J. Crowder's answer, you can use separate `useState` calls if you need multiple values.
const [myState, setMyState] = useState('initial value')
const ctx = useMemo(() => ({
value: myState,
setValue: (newValue) => {
setMyState(newValue)
}
}), [myState])
return (
<Provider value={ctx}>
{children}
</Provider>
)
goto-bus-stop's answer explains why you're getting the problem you're getting. But there's another thing to address here:
From the code you've provided, it looks like you're using an object as your state value. In particular, this line:
setMyState({...myState, value: newValue})
..suggests you intend myState to have multiple things in it, with value just being one of them.
That's not how you do it with hooks. It's fine to have an object as a state value, but generally you do that when you're going to update (that is, replace) the entire object when the state changes. If you're updating individual parts of the state (as suggested by the above), you use individual useState calls instead of an object. See comments:
const {useState} = React;
const MyComponent = () => {
// Separate `useState` calls for each state item
const [value, setValue] = useState('initial value');
const [anotherValue, setAnotherValue] = useState('another value');
// No need to create your own update function, that's what `setValue` and
// `setAnotherValue` are, just use them:
return(
<React.Fragment>
<p>{value}</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<p>{anotherValue}</p>
<input value={anotherValue} onChange={(e) => setAnotherValue(e.target.value)} />
</React.Fragment>
);
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
This separation is particularly useful if you have any side effects of state changes, because you can specify what state triggers the side effect. For instance, here's the component above which triggers a console.log when value changes but not when anotherValue changes, and another effect that occurs when either of them changes:
const {useState, useEffect} = React;
const MyComponent = () => {
// Separate `useState` calls for each state item
const [value, setValue] = useState('initial value');
const [anotherValue, setAnotherValue] = useState('another value');
// A counter for all changes; we use -1 because
// our effect runs on initial mount
const [changes, setChanges] = useState(-1);
// No need to create your own update function, that's what `setValue` and
// `setAnotherValue` are, just use them:
// A side-effect for when `value` changes:
useEffect(() => {
console.log(`value changed to ${value}`);
}, [value]); // <=== Notice that we declare what triggers this effect
// A side-effect for when *either* `value` or `anotherValue` changes:
useEffect(() => {
setChanges(changes + 1);
}, [value, anotherValue]);
return(
<React.Fragment>
<p>Total changes: {changes}</p>
<p>{value}</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<p>{anotherValue}</p>
<input value={anotherValue} onChange={(e) => setAnotherValue(e.target.value)} />
</React.Fragment>
);
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
A better way to do this will be
import react, {useState} from 'react'
const MyComponent = () => {
const [ value, setValue ] = useState('initial value');
const handleChange = (e) => {
setValue(e.target.value);
}
return(
<>
<p>{myState}</p>
<input value={myState.value} onChange={handleChange} />
</>
)
}
Firstly the function inside the useState argument isn't aware of the update since its only called once and has the value from its closure. Secondly the way you are using useState is not correct, you must instead only have the value in useState and have the handlers outside
Also you must use callback pattern
import react, {useState} from 'react'
const MyComponent = () => {
const [myState, setMyState] = useState('initial value');
const setValue = (newValue) => {
setMyState(newValue)
}
console.log(myState);
return(
<>
<p>{myState}</p>
<input value={myState} onChange={(e) => setValue(e.target.value)} />
</>
)
}
Also state update is asynchronous so the update is not reflected immediately instead after the next render

Wrong React hooks behaviour with event listener

I'm playing around with React Hooks and am facing a problem.
It shows the wrong state when I'm trying to console log it using a button handled by event listener.
CodeSandbox: https://codesandbox.io/s/lrxw1wr97m
Click on 'Add card' button 2 times
In first card, click on Button1 and see in console that there are 2 cards in state (correct behaviour)
In first card, click on Button2 (handled by event listener) and see in console that there is only 1 card in state (wrong behaviour)
Why does it show the wrong state?
In first card, Button2 should display 2 cards in the console. Any ideas?
const { useState, useContext, useRef, useEffect } = React;
const CardsContext = React.createContext();
const CardsProvider = props => {
const [cards, setCards] = useState([]);
const addCard = () => {
const id = cards.length;
setCards([...cards, { id: id, json: {} }]);
};
const handleCardClick = id => console.log(cards);
const handleButtonClick = id => console.log(cards);
return (
<CardsContext.Provider
value={{ cards, addCard, handleCardClick, handleButtonClick }}
>
{props.children}
</CardsContext.Provider>
);
};
function App() {
const { cards, addCard, handleCardClick, handleButtonClick } = useContext(
CardsContext
);
return (
<div className="App">
<button onClick={addCard}>Add card</button>
{cards.map((card, index) => (
<Card
key={card.id}
id={card.id}
handleCardClick={() => handleCardClick(card.id)}
handleButtonClick={() => handleButtonClick(card.id)}
/>
))}
</div>
);
}
function Card(props) {
const ref = useRef();
useEffect(() => {
ref.current.addEventListener("click", props.handleCardClick);
return () => {
ref.current.removeEventListener("click", props.handleCardClick);
};
}, []);
return (
<div className="card">
Card {props.id}
<div>
<button onClick={props.handleButtonClick}>Button1</button>
<button ref={node => (ref.current = node)}>Button2</button>
</div>
</div>
);
}
ReactDOM.render(
<CardsProvider>
<App />
</CardsProvider>,
document.getElementById("root")
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id='root'></div>
I am using React 16.7.0-alpha.0 and Chrome 70.0.3538.110
BTW, if I rewrite the CardsProvider using a сlass, the problem is gone.
CodeSandbox using class: https://codesandbox.io/s/w2nn3mq9vl
This is a common problem for functional components that use the useState hook. The same concerns are applicable to any callback functions where useState state is used, e.g. setTimeout or setInterval timer functions.
Event handlers are treated differently in CardsProvider and Card components.
handleCardClick and handleButtonClick used in the CardsProvider functional component are defined in its scope. There are new functions each time it runs, they refer to cards state that was obtained at the moment when they were defined. Event handlers are re-registered each time the CardsProvider component is rendered.
handleCardClick used in the Card functional component is received as a prop and registered once on component mount with useEffect. It's the same function during the entire component lifespan and refers to stale state that was fresh at the time when the handleCardClick function was defined the first time. handleButtonClick is received as a prop and re-registered on each Card render, it's a new function each time and refers to fresh state.
Mutable state
A common approach that addresses this problem is to use useRef instead of useState. A ref is basically a recipe that provides a mutable object that can be passed by reference:
const ref = useRef(0);
function eventListener() {
ref.current++;
}
In this case a component should be re-rendered on a state update like it's expected from useState, refs aren't applicable.
It's possible to keep state updates and mutable state separately but forceUpdate is considered an anti-pattern in both class and function components (listed for reference only):
const useForceUpdate = () => {
const [, setState] = useState();
return () => setState({});
}
const ref = useRef(0);
const forceUpdate = useForceUpdate();
function eventListener() {
ref.current++;
forceUpdate();
}
State updater function
One solution is to use a state updater function that receives fresh state instead of stale state from the enclosing scope:
function eventListener() {
// doesn't matter how often the listener is registered
setState(freshState => freshState + 1);
}
In this case a state is needed for synchronous side effects like console.log, a workaround is to return the same state to prevent an update.
function eventListener() {
setState(freshState => {
console.log(freshState);
return freshState;
});
}
useEffect(() => {
// register eventListener once
return () => {
// unregister eventListener once
};
}, []);
This doesn't work well with asynchronous side effects, notably async functions.
Manual event listener re-registration
Another solution is to re-register the event listener every time, so a callback always gets fresh state from the enclosing scope:
function eventListener() {
console.log(state);
}
useEffect(() => {
// register eventListener on each state update
return () => {
// unregister eventListener
};
}, [state]);
Built-in event handling
Unless the event listener is registered on document, window or other event targets that are outside of the scope of the current component, React's own DOM event handling has to be used where possible, this eliminates the need for useEffect:
<button onClick={eventListener} />
In the last case the event listener can be additionally memoized with useMemo or useCallback to prevent unnecessary re-renders when it's passed as a prop:
const eventListener = useCallback(() => {
console.log(state);
}, [state]);
Previous edition of this answer suggested to use mutable state that was applicable to initial useState hook implementation in React 16.7.0-alpha version but isn't workable in final React 16.8 implementation. useState currently supports only immutable state.*
A much cleaner way to work around this is to create a hook I call useStateRef
function useStateRef(initialValue) {
const [value, setValue] = useState(initialValue);
const ref = useRef(value);
useEffect(() => {
ref.current = value;
}, [value]);
return [value, setValue, ref];
}
You can now use the ref as a reference to the state value.
Short answer for me was that useState has a simple solution for this:
function Example() {
const [state, setState] = useState(initialState);
function update(updates) {
// this might be stale
setState({...state, ...updates});
// but you can pass setState a function instead
setState(currentState => ({...currentState, ...updates}));
}
//...
}
Short answer for me
this WILL NOT not trigger re-render ever time myvar changes.
const [myvar, setMyvar] = useState('')
useEffect(() => {
setMyvar('foo')
}, []);
This WILL trigger render -> putting myvar in []
const [myvar, setMyvar] = useState('')
useEffect(() => {
setMyvar('foo')
}, [myvar]);
Check the console and you'll get the answer:
React Hook useEffect has a missing dependency: 'props.handleCardClick'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)
Just add props.handleCardClick to the array of dependencies and it will work correctly.
This way your callback will have updated state values always ;)
// registers an event listener to component parent
React.useEffect(() => {
const parentNode = elementRef.current.parentNode
parentNode.addEventListener('mouseleave', handleAutoClose)
return () => {
parentNode.removeEventListener('mouseleave', handleAutoClose)
}
}, [handleAutoClose])
To build off of Moses Gitau's great answer, if you are developing in Typescript, to resolve type errors make the hook function generic:
function useStateRef<T>(initialValue: T | (() => T)):
[T, React.Dispatch<React.SetStateAction<T>>, React.MutableRefObject<T>] {
const [value, setValue] = React.useState(initialValue);
const ref = React.useRef(value);
React.useEffect(() => {
ref.current = value;
}, [value]);
return [value, setValue, ref];
}
Starting from the answer of #Moses Gitau, I'm using a sligthly different one that doesn't give access to a "delayed" version of the value (which is an issue for me) and is a bit more minimalist:
import { useState, useRef } from 'react';
function useStateRef(initialValue) {
const [, setValueState] = useState(initialValue);
const ref = useRef(initialValue);
const setValue = (val) => {
ref.current = val;
setValueState(val); // to trigger the refresh
};
const getValue = (val) => {
return ref.current;
};
return [getValue , setValue];
}
export default useStateRef;
This is what I'm using
Example of usage :
const [getValue , setValue] = useStateRef(0);
const listener = (event) => {
setValue(getValue() + 1);
};
useEffect(() => {
window.addEventListener('keyup', listener);
return () => {
window.removeEventListener('keyup', listener);
};
}, []);
Edit : It now gives getValue and not the reference itself. I find it better to keep things more encapsulated in that case.
after changing the following line in the index.js file the button2 works well:
useEffect(() => {
ref.current.addEventListener("click", props.handleCardClick);
return () => {
ref.current.removeEventListener("click", props.handleCardClick);
};
- }, []);
+ });
you should not use [] as 2nd argument useEffect unless you want it to run once.
more details: https://reactjs.org/docs/hooks-effect.html

Categories

Resources