I have a simple material-ui toggle in my react component. I want to use it to toggle state (false/true). If I start with useState(false) the first time I click the toggle it says false rather than true.
I'm wondering if another react hook would solve for this. useEffect, useCallback...
const Component = () => {
const [toggleValue, setToggleValue] = useState(false);
const handleToggleChange = () => {
setToggleValue(!toggleValue);
console.log("toggle value in handle: " + toggleValue);
};
return(
<FormControlLabel
control={
<Switch
checked={toggleValue}
onChange={handleToggleChange}
value="my toggle"
/>
}
/>
);
};
I would expect setPreseason(!preseason); to set the state opposite of what it currently is. True to false and false to true.
It probably is but when I log the state on the next line it logs the initial state and will always log the opposite of what the toggle is.
The state updater function returned by useState is asynchronous
If you need to react to state changes useEffect is the place for it
const Component = () => {
const [toggleValue, setToggleValue] = useState(false);
const handleToggleValue = () => {
setToggleValue(!toggleValue);
};
useEffect(() => {
console.log("toggleValue: " + toggleValue);
// second argument to useEffect is an array of dependencies
// this function is going to run every time one of the dependencies
// changes
}, [toggleValue])
return (
<FormControlLabel
control={
<Switch
checked={toggleValue}
onChange={handleToggleValue}
value="my toggle"
/>
}
/>
);
}
The issue is about which value toggleValue is inside the closure. Is not what you expect. Instead pass a callback to setToggleValue. You will get the current state back, that you can then change.
const handleToggleValue = () => {
setToggleValue((toggleValue) => !toggleValue);
}
You are doing it correctly, except that toggleValue is just a local variable and is not changed just by calling setToggleValue (the state will be changed, but that happens asynchronously).
You could do something like this instead:
const handleToggleValue = () => {
const newToggleValue = !toggleValue;
setToggleValue(newToggleValue);
console.log('toggleValue: ' + newToggleValue);
}
It depends what your intention is with the new toggle value. This simply fixes what you were doing with the console.log call. But you may run into further trouble after that, given that you are using the new toggle value before the state is updated. But that is for another SO question...
Related
There is a button that toggles dark and light mode, and the state of what mode the page is on is saved in localStorage. However, I cannot change the initial value of the state (dark) and I don't know why. This is done in a useEffect function but no matter what the value of dark is, it is always set to its initial value of false.
How do I set the value of the localStorage to the dark state?
function Mode() {
const [dark, setDark] = useState(false);
// localStorage.removeItem("dark");
const onClick = () => {
if (dark) {
setDark(false);
document.querySelector("body").classList.remove("dark");
} else {
setDark(true);
document.querySelector("body").classList.add("dark");
}
localStorage.setItem("dark", dark);
};
const localDark = JSON.parse(localStorage.getItem("dark"));
useEffect(() => {
if (localDark !== null) {
setDark(!JSON.parse(localStorage.getItem("dark"))); // this is what does not change the value of dark
onClick();
}
}, []);
return (
<div onClick={onClick} className="mode">
{dark ? <Light /> : <Dark />}
</div>
);
}
Directly use the value from localStorage in useState as the default. useEffect is unnecessary here.
const [dark, setDark] = useState(JSON.parse(localStorage.getItem("dark")));
document.body.classList.toggle('dark', dark);
The click event handler should set the localStorage dark value to the logical complement of the current value.
const onClick = () => {
localStorage.setItem("dark", !dark);
setDark(!dark);
};
Use a function to initialize the state from local storage. Update the storage and the body's class on init, and when dark state changes:
const getLocalDark = () => !!JSON.parse(localStorage.getItem("dark"));
function Mode() {
const [dark, setDark] = useState(getLocalDark);
const onClick = () => {
setDark(d => !d);
};
useEffect(() => {
const classList = document.querySelector("body").classList;
if (dark) classList.add("dark");
else classList.remove("dark");
localStorage.setItem("dark", dark);
}, [dark]);
return (
<div onClick={onClick} className="mode">
{dark ? <Light /> : <Dark />}
</div>
);
}
Perhaps you'd be interested in a useLocalStorage hook. Here's how that can be implemented:
export const useLocalStorage = (key, initialState) => {
const [value, setValue] = useState(() => {
// Initialize with the value in localStorage if it
// already exists there.
const data = localStorage.getItem(key);
// Otherwise, initialize using the provided initial state.
return data ? JSON.parse(data) : initialState;
});
// Each time "value" is changed using "setValue", update the
// value in localStorage to reflect these changes.
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [value]);
return [value, setValue];
};
This hook syncs the value seen in localStorage with the value stored in memory under the value variable.
The usage looks like this (almost identical to regular useState):
export const Counter = () => {
const [count, setCount] = useLocalStorage('count', 0);
return (
<div>
<p>{count}</p>
<button
onClick={() => {
setCount((prev) => prev + 1);
}}>
Increase count
</button>
</div>
);
};
However, the main caveat of this hook is that it's only really meant to be used in one component. That means, if you change the value from light to dark in one component using this hook, any other components using it won't be updated. So instead, you should look into using a similar implementation of what is demonstrated above, but using the React Context API. That way, you'll ensure your in-memory values are in sync with those stored in localStorage. Theming is one of the main uses of the Context API.
Good luck! :)
i do have a functional parent component and it do have a state value , which have default value as false and it is calling another component which is visible only when the state value changes to true and the child component do have 2 function in it.
Illustrating with code
export const MainSearch = (props) => {
const [search, setSearch] = useState(false);
const closeSearch = () => {
setSearch(false);
ANALYTICS.trackEvent('popup_collpsed');
}
const toggleSearch = async () => {
await setSearch(true);
ANALYTICS.trackEvent('popup_expanded');
}
};
return (
<React.Fragment>
<searchBar toggleSearch={toggleSearch} />
{search &&
<childSearch
toggleSearch={toggleSearch}
closeSearch={closeSearch}
/>}
</React.Fragment>
)
}
And its test file with one test case
describe('MainSearch',()=>{
it('AdvancedSearch - Toggle popup_expanded and popup_collapsed ', async () => {
const component = shallow(<MainSearch {...props} />);
const searchBar = component.find('searchBar');
await searchBar.props().toggleSearch(); // calling function
expect(ANALYTICS.trackEvent).toHaveBeenCalledWith('popup_expanded');
const childSearchComponent = component.find('childSearch'); // not working ,since state value hides
expect(childSearchComponent).toBeDefined();
await advancedSearchComponent.props().closeSearch();// here getting null for .props()
expect(ANALYTICS.page.trackEvent).toHaveBeenCalledWith('popup_collapsed');
});
});
i know its possible with component.update for CLASS COMPONENTS, but here am using functional components and am getting error
if i remove the state value search , am getting my test case PASS, but cant remove that , its needed. so my test case need to make the state value true and call the function closeSearch and then check the analytics.
BUT am getting error Method βpropsβ is meant to be run on 1 node. 0 found instead.
I guess state value if false and its not getting that particular node .
Can you guys please help me on same , since am stuck with it and can give more info if needed
Take a look at your toggleSearch function. You're awaiting setSearch, which isn't a promise. Remove the await Keyword and you should be fine!
If you would want to trigger your Analytics call only after the State has been set, you would need to hook in to Reacts useEffect Hook.
Then you could do something like this:
useEffect(() => {
if(search){
ANALYTICS.trackEvent('popup_expanded');
}
}, [search])
https://reactjs.org/docs/hooks-effect.html
I needed a hook to get the previous distinct value of a specific state. It looks like this and it seems to work:
function usePreviousDistinct(state) {
const prevRef = useRef();
useEffect(() => {
prevRef.current = state;
}, [state]);
return prevRef.current;
}
I've also seen there is a usePreviousDistinct hook in the react-use package but the approach is different than mine.
import { useRef } from 'react';
import { useFirstMountState } from './useFirstMountState';
export type Predicate<T> = (prev: T | undefined, next: T) => boolean;
const strictEquals = <T>(prev: T | undefined, next: T) => prev === next;
export default function usePreviousDistinct<T>(value: T, compare: Predicate<T> = strictEquals): T | undefined {
const prevRef = useRef<T>();
const curRef = useRef<T>(value);
const isFirstMount = useFirstMountState();
if (!isFirstMount && !compare(curRef.current, value)) {
prevRef.current = curRef.current;
curRef.current = value;
}
return prevRef.current;
}
I wonder if I have not understood something or am missing something. Is my version also correct?
In my test I could not find a difference:
https://codesandbox.io/s/distracted-mayer-zpym8?file=/src/App.js
useEffect() together with useRef() (your version) does not show the latest value.
useRef() without useEffect() gives you the correct value, because it runs synchronously, but doesn't have the advantages that come with asynchronicity.
useEffect() together with useState() gives you the correct value, but might trigger unnecessary renders (adds potentially unnecessary overhead).
Your version looks like it works as expected, because the old value that is shown is the one that you expect to see as the new value. But the actual new value is not the one you want.
Example:
import React, { useState, useEffect, useRef } from 'react';
export const MyComponent = function(props){
const [state, setState ] = useState(0);
return <React.Fragment>
state: { state },<br />
with useEffect: { usePreviousDistinctUE( state ) },<br />
w/o useEffect: { usePreviousDistinctR( state ) },<br />
<button onClick={ function(){
setState( state + 1 );
} }>
increment
</button>
</React.Fragment>;
};
const usePreviousDistinctUE = function( value ){
const prevRef = useRef();
useEffect(() => {
prevRef.current = value;
console.log('with useEffect, prev:', prevRef.current, ', current:', value);
}, [value]);
return prevRef.current;
};
const usePreviousDistinctR = function( value ){
const prevRef = useRef();
const curRef = useRef( value );
if( curRef.current !== value ){
prevRef.current = curRef.current;
curRef.current = value;
}
console.log('w/o useEffect, prev:', prevRef.current, ', current:', curRef.current);
return prevRef.current;
};
The values shown on the page are the same, but in the console they are different. That means the value in the useEffect() version is changed, it is only not yet shown on the page.
If you just add another hook that updates anything unrelated (leaving everything else unchanged), then the page (might*) magically show the updated value again, because the page is re-rendered and the previously already changed value is shown. The value is now wrong in your eyes, but it is not changed, only shown:
// ...
with useEffect: { usePreviousDistinctUE( state ) },<br />
w/o useEffect: { usePreviousDistinctR( state ) },<br />
anything updated: { useAnythingUpdating( state ) },<br />
// ...
const useAnythingUpdating = function(state){
const [result, setResult ] = useState(0);
useEffect(() => {
setResult( state );
console.log('anything updated');
});
return result;
};
*But you shouldn't rely on something else triggering a re-render. I'm not even sure this would update as expected under all circumstances.
more Details:
useEffect() is triggered at some time when react decides that the prop must have been changed. The ref is changed then, but react will not 'get informed' about a ref change, so it doesn't find it necessary to re-render the page to show the changed value.
In the example without useEffect() the change happens synchronously.
React doesn't 'know' about this change either, but (if everything else runs as expected) there will be always a re-render when necessary anyway (you will have called that function from another function that is rendered at the end).
(Not informing react about the change is basically the point in using useRef(): sometimes you want just a value, under your own control, without react doing magic things with it.)
I have a child component "Text Input" and passes the value to as a prop like this
export default function MobileInput(props) {
const [mobileNumber, setMobileNumber] = React.useState('');
return (
<View style={styles.inputBox}>
<TextInput
value={mobileNumber}
onChangeText={(number) => setMobileNumber(number)}
onEndEditing={props.saveMobileNumber(mobileNumber)} // here
/>
</View>
);
}
In Parent, I got the value from child
const [mobile, setMobile] = useState('');
const getMobile = (number) => {
number ? setMobile(number) : null; // here's I got this warnning
console.log('getMobile-number-from-child', number);
};
const reSendMobile = () => { // other function I want to call passed on mobile number I got from child component
if (mobile?.length) {
alert('Done');
setReSend(false);
setSeconds(10);
} else {
alert('Please write your number before press send!');
}
};
<MobileInput saveMobileNumber={getMobile} />
I see this issue But I'm already using React 16.13.1
TextInputs property onEndEditing accepts a function that is called when text input ends.. Instead of a function, you are passing the result of your props.saveMobileNumber function that is called when the component renders. Try passing a function that calls saveMobileNumber instead:
onEndEditing={() => props.saveMobileNumber(mobileNumber)}
Your code will be much easier to read/debug if you avoid keeping the same state in multiple components. You can pass mobile and setMobile to the child through props and avoid having to create separate state for the same data.
Try this:
<View style={styles.inputBox}>
<TextInput
value={mobileNumber}
onChangeText={(number) => setMobileNumber(number)}
onEndEditing={() => props.saveMobileNumber(mobileNumber)} // change here
/>
</View>
The event onEndEditing accepts a function call
Just update to call a arrow function :
onEndEditing={() => props.saveMobileNumber(mobileNumber)}
For me, i was updating activity title outside the useEffect hook. When i moved the code
into useEffect hook, the error just gone.
I have a function component, and I want to force it to re-render.
How can I do so?
Since there's no instance this, I cannot call this.forceUpdate().
π You can now, using React hooks
Using react hooks, you can now call useState() in your function component.
useState() will return an array of 2 things:
A value, representing the current state.
Its setter. Use it to update the value.
Updating the value by its setter will force your function component to re-render,
just like forceUpdate does:
import React, { useState } from 'react';
//create your forceUpdate hook
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => value + 1); // update state to force render
// A function that increment ππ» the previous state like here
// is better than directly setting `setValue(value + 1)`
}
function MyComponent() {
// call your hook here
const forceUpdate = useForceUpdate();
return (
<div>
{/*Clicking on the button will force to re-render like force update does */}
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
You can find a demo here.
The component above uses a custom hook function (useForceUpdate) which uses the react state hook useState. It increments the component's state's value and thus tells React to re-render the component.
EDIT
In an old version of this answer, the snippet used a boolean value, and toggled it in forceUpdate(). Now that I've edited my answer, the snippet use a number rather than a boolean.
Why ? (you would ask me)
Because once it happened to me that my forceUpdate() was called twice subsequently from 2 different events, and thus it was reseting the boolean value at its original state, and the component never rendered.
This is because in the useState's setter (setValue here), React compare the previous state with the new one, and render only if the state is different.
Update react v16.8 (16 Feb 2019 realease)
Since react 16.8 released with hooks, function components have the ability to hold persistent state. With that ability you can now mimic a forceUpdate:
function App() {
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
console.log("render");
return (
<div>
<button onClick={forceUpdate}>Force Render</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Note that this approach should be re-considered and in most cases when you need to force an update you probably doing something wrong.
Before react 16.8.0
No you can't, State-Less function components are just normal functions that returns jsx, you don't have any access to the React life cycle methods as you are not extending from the React.Component.
Think of function-component as the render method part of the class components.
Official FAQ now recommends this way if you really need to do it:
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {
forceUpdate();
}
Simplest way π
if you want to force a re-render, add a dummy state you can change to initiate a re-render.
const [rerender, setRerender] = useState(false);
...
setRerender(!rerender); //whenever you want to re-render
And this will ensure a re-render, And you can call setRerender(!rerender) anywhere, whenever you want :)
I used a third party library called
use-force-update
to force render my react functional components. Worked like charm.
Just use import the package in your project and use like this.
import useForceUpdate from 'use-force-update';
const MyButton = () => {
const forceUpdate = useForceUpdate();
const handleClick = () => {
alert('I will re-render now.');
forceUpdate();
};
return <button onClick={handleClick} />;
};
Best approach - no excess variables re-created on each render:
const forceUpdateReducer = (i) => i + 1
export const useForceUpdate = () => {
const [, forceUpdate] = useReducer(forceUpdateReducer, 0)
return forceUpdate
}
Usage:
const forceUpdate = useForceUpdate()
forceUpdate()
If you already have a state inside the function component and you don't want to alter it and requires a re-render you could fake a state update which will, in turn, re-render the component
const [items,setItems] = useState({
name:'Your Name',
status: 'Idle'
})
const reRender = () =>{
setItems((state) => [...state])
}
this will keep the state as it was and will make react into thinking the state has been updated
This can be done without explicitly using hooks provided you add a prop to your component and a state to the stateless component's parent component:
const ParentComponent = props => {
const [updateNow, setUpdateNow] = useState(true)
const updateFunc = () => {
setUpdateNow(!updateNow)
}
const MyComponent = props => {
return (<div> .... </div>)
}
const MyButtonComponent = props => {
return (<div> <input type="button" onClick={props.updateFunc} />.... </div>)
}
return (
<div>
<MyComponent updateMe={updateNow} />
<MyButtonComponent updateFunc={updateFunc}/>
</div>
)
}
The accepted answer is good.
Just to make it easier to understand.
Example component:
export default function MyComponent(props) {
const [updateView, setUpdateView] = useState(0);
return (
<>
<span style={{ display: "none" }}>{updateView}</span>
</>
);
}
To force re-rendering call the code below:
setUpdateView((updateView) => ++updateView);
None of these gave me a satisfactory answer so in the end I got what I wanted with the key prop, useRef and some random id generator like shortid.
Basically, I wanted some chat application to play itself out the first time someone opens the app. So, I needed full control over when and what the answers are updated with the ease of async await.
Example code:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ... your JSX functional component, import shortid somewhere
const [render, rerender] = useState(shortid.generate())
const messageList = useRef([
new Message({id: 1, message: "Hi, let's get started!"})
])
useEffect(()=>{
async function _ () {
await sleep(500)
messageList.current.push(new Message({id: 1, message: "What's your name?"}))
// ... more stuff
// now trigger the update
rerender(shortid.generate())
}
_()
}, [])
// only the component with the right render key will update itself, the others will stay as is and won't rerender.
return <div key={render}>{messageList.current}</div>
In fact this also allowed me to roll something like a chat message with a rolling .
const waitChat = async (ms) => {
let text = "."
for (let i = 0; i < ms; i += 200) {
if (messageList.current[messageList.current.length - 1].id === 100) {
messageList.current = messageList.current.filter(({id}) => id !== 100)
}
messageList.current.push(new Message({
id: 100,
message: text
}))
if (text.length === 3) {
text = "."
} else {
text += "."
}
rerender(shortid.generate())
await sleep(200)
}
if (messageList.current[messageList.current.length - 1].id === 100) {
messageList.current = messageList.current.filter(({id}) => id !== 100)
}
}
If you are using functional components with version < 16.8. One workaround would be to directly call the same function like
import React from 'react';
function MyComponent() {
const forceUpdate = MyComponent();
return (
<div>
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
But this will break if you were passing some prop to it. In my case i just passed the same props which I received to rerender function.
For me just updating the state didn't work. I am using a library with components and it looks like I can't force the component to update.
My approach is extending the ones above with conditional rendering. In my case, I want to resize my component when a value is changed.
//hook to force updating the component on specific change
const useUpdateOnChange = (change: unknown): boolean => {
const [update, setUpdate] = useState(false);
useEffect(() => {
setUpdate(!update);
}, [change]);
useEffect(() => {
if (!update) setUpdate(true);
}, [update]);
return update;
};
const MyComponent = () => {
const [myState, setMyState] = useState();
const update = useUpdateOnChange(myState);
...
return (
<div>
... ...
{update && <LibraryComponent />}
</div>
);
};
You need to pass the value you want to track for change. The hook returns boolean which should be used for conditional rendering.
When the change value triggers the useEffect update goes to false which hides the component. After that the second useEffect is triggered and update goes true which makes the component visible again and this results in updating (resizing in my case).