simple toggle hook in react - javascript

I'm having problem abstracting my toggle function out to a hook. I can make the toggle right but something is wrong in this hook code:
import { useState, useCallback } from "react";
const useToggle = (initialValue = false) => {
const [value, setValue] = useState(initialValue);
const toggle = useCallback((defaultValue) => {
defaultValue !== undefined
? setValue(defaultValue) //set true or false
: setValue((value) => !value); //if param is not supplied, toggle the value
}, []);
return [value, toggle];
};
export default useToggle;
https://codesandbox.io/s/goofy-swartz-ztdfb?file=/src/App.js
what's wrong?

On writing this code:
<button onClick={toggle}>toggle</button>
You actually are passing the event object to toggle function.
onClick={(event) => toggle(event)}
// Same
onClick={toggle}
And in your custom hook, you have the condition defaultValue !== undefined which will result in a truthy value.
Therefore you should do:
<button onClick={() => toggle()}>toggle</button>
And for your notice you can just use useReducer instead of custom hook:
const [value,toggle] = useReducer(p=>!p, false);
Example of useToggle
const useToggle = (initialValue = false) => {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue((value) => !value), []);
return [value, toggle];
};

The difference in your code and the article is that article has this code:
React.useCallback(()
Notice (). It doesn't take any parameter. So even when onClick passes the event it is being ignored in the article code.
But in your code you are using like this:
useCallback((defaultValue)
Here defaultValue becomes event object and that's why you see status: [object Object] in your output when you click on toggle button because event object is being converted to string in this call:
status: {open.toString()}
Hope this clarifies!

Maybe I don't understand what you're trying to achieve but something like this would toggle between true/false along with being much more simple?
const [toggle, setToggle] = useState(false);
const toggleButtonHandler = () => {
setToggle(!toggle);
};
return (
<div className="App">
<p>Status: {toggle.toString()}</p>
<button onClick={setToggle(false)}>false</button>
<button onClick={toggleButtonHandler}>toggle</button>
</div>
);

Related

How to avoid a function to be called twice when the state changes?

I have this react component
const [filter, setFilter] = useState(valueFromProps);
const [value, setValue] = useState(valueFromProps);
const initialRender = useRef(true);
useEffect(() => {
if (initialRender.current) {
initialRender.current = false;
} else {
console.log("set new value");
const newValue = calculateNewValue(filter);
setvalue(() => newValue);
}
}, [filter]);
const getReports = (value) => {
console.log(value);
//generate some html
};
return (
<>
<div>
{getReports(value)}
</div>
</>
);
pretty standard. It works as expected, the only problem is that getReports is executed twice every time the state filter changes. The first time with the old value and the second time with the new value.
I put some console.log and I can see the function is called twice despite the log in useEffect is printed only once.
What can I do to make it run only once please?
not sure but maybe because of setvalue(() => newValue);, the callback here is used to make updates after the state is changed so setFilter ran and after that setvalue(() => newValue) <-- this
try: setvalue(calculateNewValue(filter));
React runs return statement after filter state update and only then runs useEffect, which itself updates state and triggers re-render.
I would suggest update your state in same place you update your filter or use useMemo for value if it only depends on filter state.
const [filter, setFilter] = useState(valueFromProps);
const [value, setValue] = useState(valueFromProps);
const getReports = (value) => {
console.log(value);
//generate some html
};
cons updateFilter = (filter) => {
setFilter(filter);
setValue(calculateNewValue(filter);
}
return (
<>
<div>
{getReports(value)}
</div>
</>
);
Despite the solution proposed by #Andyally doesn't work, thanks to his suggestion I came up with a solution using useMemo
let value = props.value;
const [filter, setFilter] = useState(valueFromProps);
value = useMemo(() => calculateNewValue(filter), [filter]);
const getReports = (value) => {
console.log(value);
//generate some html
};
return (
<>
<div>
{getReports(value)}
</div>
</>
);

useState variable is one step behind its assignment

Sorry in advance if the question is a bit vague, still quite new to JS and react. Anyways, my problem is that in the following code the newFilter state hook is one step behind the event.target.value, which should have been assigned to newFilter at onChange, could anyone enlighten me why the newFilter gets updated one step later?
Output in console from console.log, when input change happens:
The code:
function App() {
const [countries, setCountries] = useState([]);
const [newFilter, setNewFilter] = useState('');
const [allCountries, setAllCountries] = useState([]);
useEffect(() => {
axios.get("https://restcountries.com/v3.1/all").then((response) => {
setAllCountries(response.data);
});
}, []);
const handleFilterChange = (event) => {
setNewFilter(event.target.value);
console.log("this is event.target.value", event.target.value)
console.log("this is the newFilter", newFilter)
if (event.target.value) {
let countriesToShow = allCountries.filter((country) =>
country.name.common.toLowerCase().match(event.target.value.toLowerCase())
);
setCountries(countriesToShow);
}
};
return (
<div>
<strong>
<p>Find countries</p>
</strong>{" "}
<input value={newFilter} onChange={handleFilterChange} />
</div>
);
}
export default App;
React state updates are asynchronous & are not run immediately (kind of like setTimeout(func , 0).
See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous.
Hence when you update a state property using its previous value, you should use the callback argument for the state setter.
handleFilterChange = (event) => {
this.setState((state)=> {
newFilter: event.target.value,
countries: (event.target.value)?allCountries.filter(...):allCountries
});
}

is it possible to change the useState value without rendering? React

I need to change the useState without rendering the page.
First is it possible?
const UsersComponent = ({valueProp}) => {
const [users, setUsers] = useState(valueProp);
const [oldUsers, setoldUsers] = useState(value);
const allUsers = useSelector((state) =>
state.users
);
useEffect(() => {
dispatch(getUsersData());
}, [dispatch]);
useEffect(() => {
// assign users to state oldUsers
}, [dispatch]);
const onClickMergeTwoArrayOfUsers = () => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
console.log("filteredUsers", filteredUsers); // not changed
};
I tried everything nothing helps me.
useEffect(() => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
}, [users]); // RETURN INFINITIVE LOOP
I am also try ->
useEffect(() => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
}, []);
Load only one and that doesn't mean anything to me..
I am try with useRef ,but that doesn't help me in this case.
I will try to explain the basis of the problem.
I need to get one get data. After that get on the click of a button, I need to merge oldUsers and users without rendering, change the state. That is problem.
If there is no solution to this problem, tell me what I could do to solve the problem?
I am googling but without succes ... I am also try this solution from interent ->
const [state, setState] = useState({});
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
no work.
I am also try with ->
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
Here is problem because I need to asynchronous get only after that can I looping.
Using a ref is probably a better option for whatever it is you're ultimately trying to do.
Yes, it is possible, but it violates one of the core rules of React state: Do Not Modify State Directly.
React compares state values using Object.is equality, so if you simply mutate an object in state instead of replacing it with a new value that is not object-equal, then the state "update" will not cause a re-render (but this is considered a bug in your program!). Anyway, this is how you'd do it:
<div id="root"></div><script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.17.1/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const {useCallback, useState} = React;
function Example () {
const [state, setState] = useState([1]);
const logState = useCallback(() => console.log(state.join(', ')), [state]);
// Don't actually do this!!!
const mutateState = () => {
setState(arr => {
arr.push(arr.at(-1) + 1);
return arr;
});
};
return (
<>
<div>{state.join(', ')}</div>
<button onClick={mutateState}>Mutate state</button>
<button onClick={logState}>Log state</button>
</>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>

React handleClick button throwing error while using hooks

import React, {useState} from "react"
const App = () => {
const {loggedIn, setLoggedIn} = useState(false)
const handleClick = () => {
setLoggedIn(!loggedIn)
}
return (
<div>
<h1>You are {loggedIn ? 'logged in' : 'logged out'}</h1>
<button onClick={handleClick}>{loggedIn ? 'Log Out' : 'Log In'}</button>
</div>
)
}
export default App
I was writing some code using hooks, and when I click on the button, nothing happens and console shows unknown error message.
I tried changing it to:
() => handleClick
handleClick()
but they all don't work.
What is wrong with the code?
The problem is useState is returning with [] instead of {}.
You should have the following instead:
const [loggedIn, setLoggedIn] = useState(false);
+1 suggestion:
Also it is better to use the callback option when using setLoggedIn in order to capture the previous version of the state as the following:
const handleClick = () => {
setLoggedIn(prev => !prev);
}
I hope this helps!
change const {loggedIn, setLoggedIn} = useState(false)
To : const [loggedIn, setLoggedIn] = useState(false)
Dont use {} to declare useState variable and its setter function use [] these instead.
You are destructuring the state value and change handler incorrectly. It returns a tuple so you need to get the values like this:
const [loggedIn, setLoggedIn] = useState(false)

How use async return value from useEffect as default value in useState?

I've created a simple example https://codesandbox.io/s/4zq852m7j0.
As you can see I'm fetching some data from a remote source. I'd like to use the return value as the value inside my textfield.
const useFetch = () => {
const [value, setValue] = useState("");
useEffect(
async () => {
const response = await fetch("https://httpbin.org/get?foo=bar");
const data = await response.json();
setValue(data.args.foo);
},
[value]
);
return value;
};
However using the value inside the useState function does not work. I think useState uses the default value only on first render. When first rendering the value is obviously not set since it's async. The textfield should have the value bar but it is empty.
function App() {
const remoteName = useFetch();
// i want to see the remote value inside my textfield
const [name, setName] = useState(remoteName);
const onChange = event => {
setName(event.target.value);
};
return (
<div className="App">
<p>remote name: {remoteName}</p>
<p>local name: {name}</p>
<input onChange={onChange} value={name} />
</div>
);
}
After fetching the value from remote I'd like to be able to change it locally.
Any ideas?
Now that useFetch returns a value that is available asynchronously, what you need is to update localState when the remoteValue is available, for that you can write an effect
const remoteName = useFetch();
// i want to see the remote value inside my textfield
const [name, setName] = useState(remoteName);
useEffect(
() => {
console.log("inside effect");
setName(remoteName);
},
[remoteName] // run when remoteName changes
);
const onChange = event => {
setName(event.target.value);
};
Working demo
This is exactly same case as setting initial state asynchronously in class component:
state = {};
async componentDidMount() {
const response = await fetch(...);
...
this.setState(...);
}
Asynchronously retrieved state cannot be available during initial render. Function component should use same technique as class component, i.e. conditionally render children that depend on a state:
return name && <div className="App">...</div>;
This way there's no reason for useFetch to have its own state, it can maintain common state with the component (an example):
const useFetch = () => {
const [value, setValue] = useState("");
useEffect(
async () => {
const response = await fetch("https://httpbin.org/get?foo=bar");
const data = await response.json();
setValue(data.args.foo);
},
[] // executed on component mount
);
return [value, setValue];
};
function App() {
const [name, setName] = useFetch();
const onChange = event => {
setName(event.target.value);
};
return name && (
<div className="App">
<p>local name: {name}</p>
<input onChange={onChange} value={name} />
</div>
);
}
Is it not possible to pass the initial state as an async function?
i.e
const [value, setValue] = useState(async () => fetch("https://httpbin.org/get?foo=bar").json().args.foo);

Categories

Resources