maybe you can use something like this?
const HanldeCheck = (index) => {
setcheckBoxState(prevState => prevState.map((item, idx) => idx === index ? !item : item))
};
Answer from Hovakimyan on Stack OverflowsetIsLoading is an async function and you cannot get the state value immediately after update.
setState actions are asynchronous and are batched for performance gains. setState() does not immediately mutate this. Thus the setState calls are asynchronous as well as batched for better UI experience and performance. This applies on both
functional/Classcomponents.
From React documentation
React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. You could read more about this here
If you want to get the updated state value then use useEffect hook with dependency array. React will execute this hook after each state update.
const {useEffect, useState } = React;
const App = (props) => {
const [isLoading, setIsLoading] = useState(false)
const buttonHandler = () => {
setIsLoading(current => !current)
}
useEffect( () => {
console.log(isLoading);
}, [isLoading]);
return (
<div>
<button onClick={buttonHandler} type="button">
Change
</button>
{isLoading? "Loading...": null}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root">
loading.....
</div>
This is the expected behavior. You may want to use useEffect to access the latest value.
Here is a thread discussing the same issue: useState set method not reflecting change immediately
Hope this helps!
reactjs - update boolean value in useState array - Stack Overflow
reactjs - React JS Typescript usestate with boolean - Stack Overflow
reactjs - useState Array boolean toggle - React Native - Stack Overflow
useState(Boolean) vs useState(false)
Videos
this is what i found to work
const updatePostCompletion = index => {
let newArr = [...myPostFID]; // copying the old posts array
newArr[index].completed = true; // replace false bool with true
setmyPostFID1(newArr);
}
this is a function that will create a new array with the old array data then will take the selected index in the state and change the completed bool to true.
after all that it will set the state to the new array that was just initialized
const initialState = {
userId: 1,
id: 1,
title: "delectus aut autem",
completed: false
};
export default function App() {
const [data, setData] = React.useState(initialState);
const handleChange = (event) =>
setData((prev) => {
return {
...prev,
completed: event.target.checked
};
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<input type="checkbox" value={data.completed} onChange={handleChange} />
<p>State looks currently like following:</p>
<p>{JSON.stringify(data)}</p>
</div>
);
}
https://codesandbox.io/s/cocky-neumann-tczrp5
One of the approaches is to store item names in an array or object and then check if the particular item were selected.
Here is another approach you could use:
const HomeScreen = () => {
const itemsData = [
{ name: 'Eggs', image: 'image require here', isSelected: false },
{ name: 'Pasta', image: '', isSelected: false },
{ name: 'Fish', image: '', isSelected: false },
];
const [items, setItems] = useState(itemsData);
const handleSelectItem = (selectedItemIndex) => {
const itemsToSelect = items.map((item, index) => {
if (selectedItemIndex === index) item.isSelected = !item.isSelected;
return item;
}, []);
setItems(itemsToSelect);
// your logic here
// AddToPanetry(item[selectedItemIndex].name)
};
const renderItem = (item, index) => {
const isSelected = items[index].isSelected;
return (
<TouchableOpacity
style={[styles.button, isSelected && styles.selectedButton]}
onPress={() => handleSelectItem(index)}>
{/* <Image source={item.image} /> */}
<Text>{item.name}</Text>
</TouchableOpacity>
);
};
return (
<View>
<ScrollView>
{itemsData.map((item, index) => renderItem(item, index))}
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
button: {
backgroundColor: 'white',
padding: 20,
},
selectedButton: {
backgroundColor: 'pink',
},
});
There are 2 options depending on your needs.
- You could keep your all of your data and
selectedstate in a single stateful array. On press, you need to find the item in the array that will update.
export default function Grocery({ navigation }) {
const [state, setState] = React.useState([ { label: 'pasta', pressed: false, }, { label: 'eggs', pressed: false, }, { label: 'fish', pressed: false, }, { label: 'salad', pressed: false, }, ]);
const handlePress = (i) => {
const newState = [...state];
newState[i].pressed = !state[i].pressed;
setState(newState);
};
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList
data={state}
ListHeaderComponent={() => (
<Button
title="home"
onPress={() => {console.log('press home')}}>
press
</Button>
)}
renderItem={({ item, index }) => (
<TouchableOpacity
key={index}
id={index}
onPress={() => handlePress(index)}
style={[
styles.flatListTouchable,
item.pressed && styles.flatListTouchablePressed,
]}>
<Text style={styles.flatListTouchableText}>{item.label}</Text>
</TouchableOpacity>
)}
/>
</SafeAreaView>
);
}
Snack
- You could keep your data and
selectedstate separately. Theselectedstate is managed in the child component.
const data = [ { label: 'pasta', }, { label: 'eggs', }, { label: 'fish', }, { label: 'salad', }, ];
export default function Grocery({ navigation }) {
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList
data={data}
ListHeaderComponent={() => (
<Button
title="home"
onPress={() => {
console.log('press home');
}}>
press
</Button>
)}
renderItem={({ item, index }) => (
<RenderItem item={item} index={index} />
)}
/>
</SafeAreaView>
);
}
const RenderItem = ({ item, index }) => {
const [pressed, setPressed] = React.useState(false);
const handlePress = () => {
setPressed(!pressed);
};
return (
<TouchableOpacity
key={index}
id={index}
onPress={handlePress}
style={[
styles.flatListTouchable,
pressed && styles.flatListTouchablePressed,
]}>
<Text style={styles.flatListTouchableText}>{item.label}</Text>
</TouchableOpacity>
);
};
Snack
Is it a thing to set default state values using primitive type constructors when applicable? e.g.
// Boolean (defaults to false)
const [active, setActive] = useState(Boolean);
const [active, setActive] = useState(false);
// String (defaults to "")
const [searchTerm, setSearchTerm] = useState(String);
const [searchTerm, setSearchTerm] = useState("");
// Number (defaults to 0)
...Another developer who was learning React at the same time as me said I should do it this way and I pretty much have been doing it this way for the last 2 years.
I figured it helped me write more declarative code and never gave it much more thought until this week. I've been trying to find any information discussing this but haven't found a thing.
Is there any explanation for or against this that people can think of? Do many other people declare state like this?
Your approach is right just one minor thing that you trying to achieve here is wrong.
setEmailNotifications({
...emailNotifications,
[event.target.id]: !event.target.id, //Here
});
when you are setting dynamic value to the state you are expecting it to be the Boolean value which is not
solution:
setEmailNotifications({
...emailNotifications,
[event.target.id]: !emailNotifications[event.target.id],
});
@kunal panchal's answer is totally valid Javascript, but it does cause a Typescript error because the type of event.target.id is string so Typescript does not know for sure that it's a valid key of emailNotifications. You have to assert that it is correct by using as.
!emailNotifications[event.target.id as keyof EmailNotifications]
One way to avoid this is to get the boolean value by looking at the checked property on the input rather than toggling the state.
As a sidenote, it's a good best practice to get the current state by using a setState callback so that you always get the correct value if multiple updates are batched together.
const _handleEmailNotificationsSettings = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setEmailNotifications(prevState => ({
...prevState,
[event.target.id]: event.target.checked,
}));
};
This is probably the best solution for a checkbox.
Another approach which is more flexible to other situations is to use a curried function. Instead of getting the property from the event.target.id, where it will always be string, we pass the property as an argument to create an individual handler for each property.
const handleEmailNotificationsSettings = (
property: keyof EmailNotifications
) => () => {
setEmailNotifications((prevState) => ({
...prevState,
[property]: !emailNotifications[property]
}));
};
or
const handleEmailNotificationsSettings = (
property: keyof EmailNotifications
) => (event: React.ChangeEvent<HTMLInputElement>) => {
setEmailNotifications((prevState) => ({
...prevState,
[property]: event.target.checked
}));
};
which you use like this:
<input
type="checkbox"
checked={emailNotifications.favourites}
onChange={handleEmailNotificationsSettings("favourites")}
/>
Those solutions avoid having to make as assertions in the event handler, but sometimes they are inevitable. I am looping through your state using (Object.keys(emailNotifications) and I need to make an assertion there because Object.keys always returns string[].
import React, { useState } from "react";
// I am defining this separately so that I can use typeof to extract the type
// you don't need to do this if you have the type defined elsewhere
const initialNotifications = {
rating: false,
favourites: false,
payments: false,
refunds: false,
sales: false
};
type EmailNotifications = typeof initialNotifications;
const MyComponent = () => {
// you don't really need to declare the type when you have an initial value
const [emailNotifications, setEmailNotifications] = useState(
initialNotifications
);
const handleEmailNotificationsSettings = (
property: keyof EmailNotifications
) => (event: React.ChangeEvent<HTMLInputElement>) => {
setEmailNotifications((prevState) => ({
...prevState,
[property]: event.target.checked
}));
};
return (
<div>
{(Object.keys(emailNotifications) as Array<keyof EmailNotifications>).map(
(property) => (
<div key={property}>
<label>
<input
type="checkbox"
id={property}
checked={emailNotifications[property]}
onChange={handleEmailNotificationsSettings(property)}
/>
{property}
</label>
</div>
)
)}
</div>
);
};
export default MyComponent;