I want to convert this function setState using hooks
this.setState(previousState => {
const newItemIdToSelectedMap = {
...previousState.itemIdToSelectedMap,
[itemId]: !previousState.itemIdToSelectedMap[itemId],
};
return {
itemIdToSelectedMap: newItemIdToSelectedMap,
};
});
Here is my initial state
const [itemIdToSelectedMap, setItemIdToSelectedMap] = useState({});
I want to convert to something like this
const toggleItem = itemId => {
setItemIdToSelectedMap(state => ({
...state,
[itemId]: !state.itemIdToSelectedMap[itemId]
}));
};
This is for the checkbox function. I want to make my specific checkbox checked but it's not working. Let me know what you think. Thanks
you dont need to use itemIdToSelectedMap again inside of your calback function that you are passing to your state setter, the state itself has the same value, your function should like below:
const toggleItem = itemId => {
setItemIdToSelectedMap(state => ({
...state,
[itemId]: !state[itemId]
}));
};
Related
I'm just a react beginner. I'm trying to create a custom hook, which will be triggered once an onClick event is triggered. By what I see, I need to use the useRef hook, to take into account if the component is rendered by first time, or if it's being re-rendered.
My code approach is the next:
const Clear = (value) => {
const useClearHook = () => {
const stateRef = useRef(value.value.state);
console.log(stateRef);
useEffect(() => {
console.log("useEffect: ");
stateRef.current = value.value.state;
stateRef.current.result = [""];
stateRef.current.secondNumber = [""];
stateRef.current.mathOp = "";
console.log(stateRef.current);
value.value.setState({
...stateRef.current,
result: value.value.state.result,
secondNumber: value.value.state.secondNumber,
mathOp: value.value.state.mathOp,
});
}, [stateRef.current]);
console.log(value.value.state);
};
return <button onClick={useClearHook}>Clear</button>;
};
Any suggestion? Maybe I might not call ...stateRef.current in setState. I'm not sure about my mistake.
Any help will be appreciated.
Thanks!
Your problem is useClearHook is not a component (the component always goes with the first capitalized letter like UseClearHook), so that's why when you call useRef in a non-component, it will throw that error. Similarly, for useEffect, you need to put it under a proper component.
The way you're using state is also not correct, you need to call useState instead
Here is a possible fix for you
const Clear = (value) => {
const [clearState, setClearState] = useState()
const useClearHook = () => {
setClearState((prevState) => ({
...prevState,
result: [""],
secondNumber: [""],
mathOp: "",
}));
};
return <button onClick={useClearHook}>Clear</button>;
};
If your states on the upper component (outside of Clear). You can try this way too
const Clear = ({value, setValue}) => {
const useClearHook = () => {
setValue((prevState) => ({
...prevState,
result: [""],
secondNumber: [""],
mathOp: "",
}));
};
return <button onClick={useClearHook}>Clear</button>;
};
Here is how we pass it
<Clear value={value} setValue={setValue} />
The declaration for setValue and value can be like this in the upper component
const [value, setValue] = useState()
I'm working on a hotel feature where the user can filter through and display the corresponding rooms available, however when I set the onClick to update the filters and display the filtered rooms, the rooms display correctly after the second click and there after.
const toggleSelection = (e) => {
setFilters((prevFilters) => ({
...prevFilters,
[e.name]: e.id,
}));
filterRooms();
};
const filterRooms = () => {
....
....
setRooms((prevRooms) => ({
...prevRooms,
filtered: filtered_rooms,
}));
};
useState() (and class component's this.setState()) are asynchronous, so your second state updater won't have an up to date value for filtered_rooms when it runs.
Rather than:
const [some_state, setSomeState] = useState(...);
const [some_other_state, setSomeOtherState] = useState(...);
const someHandler = e => {
setSomeState(...);
setSomeOtherState(() => {
// Uses `some_state` to calculate `some_other_state`'s value
});
};
You need to setSomeOtherState within a useEffect hook, and ensure to mark some_state as a dependency.
const [some_state, setSomeState] = useState(...);
const [some_other_state, setSomeOtherState] = useState(...);
useEffect(() => {
setSomeOtherState(() => {
// Uses `some_state` to calculate `some_other_state`'s value
});
}, [some_state]);
const someHandler = e => {
setSomeState(...);
};
It is hard to give an suggestion for your code since it is fairly edited, but it'd probably look like this:
const filterRooms = () => {
// ...
setRooms((prevRooms) => ({
...prevRooms,
filtered: filtered_rooms,
}));
};
useEffect(() => {
filterRooms();
}, [filtered_rooms]);
const toggleSelection = (e) => {
setFilters((prevFilters) => ({
...prevFilters,
[e.name]: e.id,
}));
};
See this codepen for a simple (albeit a bit contrived) example.
I'm converting a class component to functional component for practice. It has a ref object to contain some variables for the component, such as IntersectionObserver object to implement infinite scrolling.
The issue starts from here. The callback function of the IntersectionObserver calls a function(says update) defined in the component to load more data. Because the IntersectionObserver is defined inside the useRef, the update function is the function bound when the component gets initialized. So the value of the state that is used in the update function is also the value of the initial state.
How can I compose this functional component in a proper way?
Backbone demo
export default function A(props) {
const [state, setState] = useState({
pageNo: 1,
isLoading: false,
items: []
});
const update = useCallback(() => {
setState(state => ({...state, isLoading: true}));
someApi(state.pageNo);
setState(state => ({
...state,
pageNo: pageNo + 1
}));
setState(state => ({...state, isLoading: false}));
}, [isLoading, pageNo]);
const observerCallback = useCallback((entries, observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
observer.disconnect();
update();
}
}
}, [update]);
const observer = useRef(new IntersectionObserver(observerCallback)); // The callback is the function binding the update function that binds some of the initial state
const lastEl = useRef(null);
const preLastEl = useRef(null);
useEffect(() => {
update();
}, [props]);
if (lastEl.current && lastEl.current != preLastEl.current) {
preLastEl.current = lastEl.current;
observer.observe(lastEl.current);
}
return (
<SomeProgressBar style={{ display: state.isLoading ? "" : "none" }}/>
{
state.items.map((item) => <B ... ref={lastEl}/>)
}
);
}
I don't exactly why you're using the ref and why you can't do it differently. so in case you have to do it this way, your refs are dependent to state object and they need to be changed when the state are changed so you should use a useEffect to change the refs based on new state. try to implement one of these two steps:
1
const refs = useRef({
lastEl: undefined,
observer: new IntersectionObserver((entries, observer) => {
...
update(state.pageNo); // This is the update function bound when the instance of this component gets initialized
});
});
useEffect(() => {
update(state.pageNo);
}, [props]);
function update(pageNo = 1) {
setState(prev => ({...prev, isLoading: true}));
someApi(pageNo); // state.pageNo will be always 1
setState(prev => ({...prev, isLoading: false}));
}
2 in case above code didn't work try this
useEffect(() => {
if(state.pageNo){
refs.current = {
lastEl: undefined,
observer: new IntersectionObserver((entries, observer) => {
...
update(state.pageNo); // This is the update function bound when the instance of this component gets initialized
});
}
}
}, [state.pageNo])
I created a custom hook to store Objects in a useState hook and allow changing properties without loosing the other entries.
const useObject = initialValue => {
const [state, setState] = useState(initialValue);
return [
state,
newState => {
setState({
...state,
...newState
});
}
];
};
This hook works in my component but doesn't when I assign it to my context.
Here is what I did:
I created a context:
export const navigation = createContext();
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/store.js:40-83
I created a useObject variable and assigned it as value to my Context Provider
<navigation.Provider value={useObject()}>
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/Layout.js:234-284
I load the context via useContext and change its value
const [navigationState, setNavigationState] = useContext(navigation);
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/App.js:476-616
Result:
The context always stores the new entry and removes all existing entries.
Anyone knows why ?
Here is the Sandbox link. You can test it by clicking the filter button. I expected to see {search:true, icon: 'times'} as context value. Thx!
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/App.js
There is one important things to note here. useEffect in App.js is run once and hence the onClick function set with setNavigationState will use the values from its closure at the point at which it is defined i.e initial render.
Due to this, when you call the function within Header.js from context's the value along with the localState are being reset to the initial value.
SOLUTION 1:
One solution here is to use callback approach to state update. For that you need to modify your implementation on useObject a bit to provide the use the capability to use the callback value from setState
const useObject = initialValue => {
const [state, setState] = useState(initialValue);
return [
state,
newState => {
if(typeof newState === 'function') {
setState((prev) => ({ ...prev, ...newState(prev)}));
} else {
setState({
...state,
...newState
});
}
}
];
};
and then use it in onContextClick function like
const onContextClick = () => {
setState(prevState => {
setNavigationState(prev => ({ icon: ICON[prevState.isOpen ? 0 : 1] }));
return { isOpen: !prevState.isOpen };
});
};
Working DEMO
SOLUTION 2:
The other simpler approach to solving the problem is to use useCallback for onContextClick and update the navigation state with useEffect, everytime the closure state is updated like
const onContextClick = React.useCallback(() => {
setNavigationState({ icon: ICON[state.isOpen ? 0 : 1] });
setState({ isOpen: !state.isOpen });
}, [state]);
useEffect(() => {
setNavigationState({
search: true,
icon: ICON[0],
onClick: onContextClick
});
}, [onContextClick]);
Working demo
With class based components you could use the computed properties to in order to use one single handler for several inputs with the help of an id attribute like this:
function handleChange(evt) {
const value = evt.target.value;
setState({
...state,
[evt.target.name]: value
});
}
This is thebest I caome up with but im sure there must be a shorter way:
const handleChange = (e) => {
if (e.target.id === 'name') setName(e.target.value)
if (e.target.id === 'genre') setGenre(e.target.value)
if (e.target.id === 'description') setDescription(e.target.value)
}
Any ideas?
Thanks.
You could have create an object in the useState and update just like the first example.
// Declare the state with a object default value
const [state, setState] = useState({})
const handleChange = (e) => {
setState(prev => ({
...prev,
[e.target.id]: e.target.value
}))
}
If you can't change the useState and have a very strong reason to have these 3 separeted useStates, you could create an object that returns the 3 functions based on the e.target.id
const updater = {
name: setName,
genre: setGenre,
description: setDescription,
}
And when updating you do
const handleChange = (e) => {
const updateValue = updater[e.target.id]
if (updateValue) {
updateValue(e.target.value)
}
else {
// No function to update found
// Do something else
}
}