I have list of products that are being generated each product has a checkbox associated with it. Right now all of my checkboxes are sharing the same state so when you check one, all of them end up being checked. The product data including the checkbox are in a loop , how can I generate checkboxes with different states? I am using React Native and the checkbox component
is using react-native-elements
<View>
{products.map((value, key) => {
return (
<View
key={value.serialNumber + key}
style={localStyles.productCheckbox}
>
<Text key={value._id + key} style={localStyles.modalText}>
{value.pModel}
</Text>
<Text
key={value.pModel + key}
style={localStyles.modalText}
>
{value.serialNumber}
</Text>
<Text
key={value.pVersion + key}
style={localStyles.modalText}
>
{value.versionNumber}
</Text>
<CheckBox
checked={this.state.checked}
onPress={() => {
this.setState({ checked: !this.state.checked });
}}
key={value._id}
/>
</View>
Instead of using a bool value, use an array that will store the ids of all the checked items.
Like this:
this.state = {
....
checked: [],
....
}
Now for each checkbox check whether its id exists inside array of not:
<CheckBox
checked={this.state.checked.includes(value._id)}
Update the state array inside onPress event handler:
onPress={(e) => this.handleCheckbox(value._id, e)}
handleCheckbox(id, e) {
// check whether item is checked or unchecked on the basis of add and remove
if (/* checked */) {
this.setState((prevstate) => ({
checked: [...prevState.checked, id]
}))
} else {
this.setState((prevstate) => ({
checked: prevState.checked.filter(el => el != id)
}))
}
}
You need to make a dictionary in your state that uses the unique id of the checkbox as a key, and the value will be the checked state.
// setting up your state in the ctor
this.state = { checkboxValues : {} }
<View>
{products.map((value, key) => {
const uniqueKey = value.serialNumber + key; // Unique identifier could probably be just the serial number i presume..
return (
<View
key={uniqueKey}
style={localStyles.productCheckbox}
>
<Text key={value._id + key} style={localStyles.modalText}>
{value.pModel}
</Text>
<Text
key={value.pModel + key}
style={localStyles.modalText}
>
{value.serialNumber}
</Text>
<Text
key={value.pVersion + key}
style={localStyles.modalText}
>
{value.versionNumber}
</Text>
<CheckBox
checked={this.state.checkboxValues[uniqueKey].checked} // read from the state dictionary based on the id
onPress={() => {
// assign a new checked state based on the unique id. You'll also need to do some error/undefined checking here.
const newCheckboxValues = this.state.checkboxValues;
newCheckboxValues[uniqueKey].checked = !newCheckboxValues[uniqueKey].checked
this.setState({ checkboxValues: newCheckboxValues });
}}
key={value._id}
/>
</View>
hi recently i also stumbled upon this issue, it seems easy to solve but i took a lot of time to solve it. The below code creates dynamic key-value pairs in state. if you console.log the state in your render you'll see (check0:true check1:true check2: false ....) Hope my solution helps someone.
<Switch
value={this.state[`check${key}`]}
onValueChange={value => this.setState({ [`check${key}`]: value })}
/>
Related
I've got a useState variable that has an array of JSON objects, and I'm trying to get the text fields to dynamically render and update with onChangeText, but I'm having a bit of an issue with this.
When I add console.log(index) to updateHashtags, it says it is undefined, and I can't understand what I'm doing wrong or can do to make this work. In theory, the index should be a static number for each text field. So the first text field would have hashtags[0] as its value and use '0' as the index for updating the state of hashtags.
When I use:
console.log('index',index);
inside of updateHashtags, I get:
index undefined
Here's the code:
const updateHashtags = (text, index) => {
let ht = hashtags;
ht[index].name = text;
setHashtags(ht);
}
const hashtagElement = (
<>
<Text style={styles.plainText} >Set Your Values:</Text>
<Text style={styles.instructionsText} >This is what other users use to search for you.</Text>
{hashtags.map((e,index) =>
<TextInput
placeholder='value'
key={e.name + index}
value={hashtags[index].name}
onChangeText={(text,index) => updateHashtags(text,index)}
style={styles.textInput}
/>
)}
<TouchableOpacity
onPress={() => {
let ht = hashtags;
let newht = {
name: '',
weight: 0,
};
ht[ht.length] = newht;
setHashtags(ht);
}}
>
<Ionicons
name="add-circle-outline"
size={40}
color={'black'}
/>
</TouchableOpacity>
</>
);
Maybe the onChangeText functions doens't get index param.
Try it like this:
// we are getting index from here
{hashtags.map((e,index) =>
<TextInput
placeholder='value'
key={e.name + index}
value={hashtags[index].name}
// so no need of taking index from param here
onChangeText={(text) => updateHashtags(text,index)}
style={styles.textInput}
/>
)}
Also for your updateHashTags functions consider this insted:
const updateHashtags = (text, index) => {
// doing it this way ensures your are editing updated version of state
setHashtags((state) => {
let ht = state;
ht[index].name = text;
return ht;
});
}
I am new to react native and have a situation where I am trying to set a state to a filtered version of another state(array). With the current code, each item in the unfiltered array is being changed to the filtering condition. How can I setFilteredJobs to only contain 'jobs', where status equals the status that the user has chosen in the AppPicker?
Here is my code:
const [jobs, setJobs] = useState()
const [filteredJobs, setFilteredJobs] = useState()
const [status, setStatus] = useState()
const handleStatusChange = (item) => {
setFilteredJobs(
jobs.filter( job => job.status = item.label )
)
setStatus(item)
}
return (
<View style={defaultStyles.screenNoPadding}>
<AppTextInput placeholder='Search for a Job' icon='magnify' />
<View style={styles.filterContainer}>
<AppPicker
color='white'
selectedItem={category}
onSelectItem={item => handleCategoryChange(item)}
items={categories}
placeholder='Filter'
icon='apps' />
<AppPicker
color='white'
selectedItem={status}
onSelectItem={item => handleStatusChange(item)}
items={statuses}
placeholder='Status'
icon='apps' />
</View>
<FlatList
style={styles.list}
data={filteredJobs ? filteredJobs : jobs}
keyExtractor={job => job.id.toString()}
renderItem={({ item }) => (
<ListItem
company={item.company}
position={item.position}
status={item.status}
onPress={() => navigation.navigate('Details', { item })}
/>
)}
ItemSeparatorComponent={ListItemSeparator}
/>
</View>
);
Thanks in advance! Keep in mind jobs is fetched in a useEffect on loading the component.
It is because you should use a comparison, and not an attribuition.
const handleStatusChange = (item) => {
setFilteredJobs(
// FIX HERE, USE == INSTEAD OF =
jobs.filter( job => job.status == item.label )
)
setStatus(item)
}
#guilherme is right, it's a simple mistake, you assigned instead of comparing. Get in the habit of using === to compare strings.
Also the way to tackle these problems in the future: console.log before and after the thing you are doing that isn't working. It would have jumped out at you pretty quick if you had. Use JSON.stringify for logging of objects if you are getting [Object object] in the logging output.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I keep getting this warning "each child in a list should have unique 'key' prop" even though I have unique items with different keys.
Whenever I create a new 'plant' object I give it a new uuid
setPlants(prevItems => {
return [
{name: newPlantName, key: uuid.v4(), timeInterval: null},
...prevItems,
];
And my listItem component is set up with a key
<ListItem
key={plant.key}
Whenever I print my list all the 'keys' have a different uuid. The warning occurs every time I refresh the app so it might be somehow because i'm using a database to access the data? I'm not really sure but I am using mmkv to store the data from my state and then I show that data when the app first opens.
This is the full mapping:
{plants &&
plants.map(plant =>
plant ? (
<PlantItem
plant={plant}
deletion={openDeleteOrCancel}
setPlants={setPlants}
/>
) : null,
)}
PlantItem component:
return (
<>
<ActionSheet
visible={actionSheetVisible}
closeOverlay={() => {
setActionSheetVisible(false);
}}
actions={actions}
/>
<ListItem
key={plant.key}
onPress={() => {
setActionSheetVisible(true);
}}
bottomDivider>
<ListItem.Content key={plant.key} style={styles.listItemContainer}>
<ListItem.Title>{plant.name}</ListItem.Title>
{/* <Icon name="check" size={20} /> */}
</ListItem.Content>
</ListItem>
{showAddTimeInterval && (
<AddTimeInterval
createTimeInterval={createTimeInterval}
closeModal={toggleShowAddTimeInterval}
plantName={plant.name}
/>
)}
</>
);
This is how my states are initiated
const [plantsStorage, setPlantsStorage] = useStorage('plantss');
const [plants, setPlants] = useState(plantsStorage ? plantsStorage : []);
useEffect(() => {
setPlantsStorage(plants);
});
The warning is just really annoying, if there is no way to change my code to fix it is there a way to mute it somehow? just for this specific warning not all warnings.
The React key should be used on the outermost mapped element.
React Lists and Keys
{plants.map(plant =>
plant ? (
<PlantItem
key={plant.key} // <-- use key here!
plant={plant}
deletion={openDeleteOrCancel}
setPlants={setPlants}
/>
) : null,
)}
Suggestion, filter the plants array to remove the null "holes".
{plants
.filter(Boolean) // include only truthy plant objects
.map(plant => (
<PlantItem
key={plant.key} // <-- use key here!
plant={plant}
deletion={openDeleteOrCancel}
setPlants={setPlants}
/>)
)}
I first make an Ajax call (to an API) which provides me some data, a list of achievements (array of objects). I would like to loop through this array, show the first achievement as a Modal and on click of a button close the modal then show the next one (next achievement) and so on.
Ajax call providing the data:
getAchievements = () => {
fetch(url + '/achievements', {
method: 'get',
headers: {
Accept: 'application/json',
'Content-type': 'application/json'
}
})
.then((data) => data.json())
.then((data) => {
this.props.addData({
achievements: data.achievements
})
if(this.props.store.achievements.length > 0) {
this.setState({
showAchievementModal: true
})
}
})
.catch((error) => {
console.error(error)
})
}
Here I show the modals:
render() {
return (
{this.state.showAchievementModal &&
<Modal
animationType={'fade'}
visible={this.props.store.isModalAchievementVisible}
>
{this.props.store.achievements.map((data,index)=>{
return(
<View key={index}>
<View style={styles.container}>
<Text>{data.title}</Text>
<Text>{data.description}</Text>
<TouchableOpacity onPress={this.closeModal}>
<Text>Collect</Text>
</TouchableOpacity>
</View>
</View>
)
})}
</Modal>
}
)
}
At the moment all the Modals open at the same time. How could I open them one after the other after clicking the Collect button?
This is the updated version of my code that works:
Initialising activeModalIndex in the constructor:
constructor(props) {
super(props)
this.state = {
activeModalIndex: 0
}
}
Get achievements:
getAchievements = () => {
if(this.props.store.achievements.length > 0) {
this.setState({
showAchievementModal: true,
activeModalIndex: 0,
})
}
}
Render function:
render() {
return this.props.store.achievements.map((data,index) => this.state.activeModalIndex === index &&
<Modal>
<View key={index}>
<View style={styles.container}>
<Text>{data.title}</Text>
<Text>{data.description}</Text>
<TouchableOpacity onPress={this.closeModal}>
<Text>Collect</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
)
}
Close Modal:
closeModal = () => {
const maxIndex = this.props.store.achievements.length - 1
const currentIndex = this.state.activeModalIndex
const isLastModal = currentIndex === maxIndex
const newIndex = isLastModal? -1: currentIndex +1
this.setState({
activeModalIndex: newIndex
})
}
The problem is that you have multiple Modals on your page and they all use the same boolean to check if they should be rendered. Initially, showAchievementModal is set to true, so all modals are rendered. Furthermore, after you set showAchievementModal to false in closeModal, it will permanently stay false, so no additional modals will get rendered.
render() {
return (
{this.state.showAchievementModal &&
<Modal
...
</Modal>
}
)
}
Instead of showAchievementModal you should be keeping track of index of active modal. So, after you fetch the list of achievements from your API, set the activeModalIndex to 0. After the user dismisses this first modal, set the activeModalIndex to 1 inside the closeModal method, then set it to 2 after the second modal is closed and so on.
Now, for every modal to correspond to a single achievement, we must map each element of the achievements array to a single Modal and conditionally render it only if its corresponding index is the active one.
render() {
const achievements = this.props.store.achievements;
const { activeModalIndex } = this.state;
return achievements.map((data, index) => activeModalIndex === index &&
<Modal key={index}>
<View>
<View style={styles.container}>
<Text>{data.title}</Text>
<Text>{data.description}</Text>
<TouchableOpacity onPress={this.closeModal}>
<Text>Collect</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
)
}
When the users dismisses the currently active modal, simply increment the index of the active modal and the next modal will appear instead of the current one. If the new incremented value is equal or larger than the array length, nothing will get rendered, so no need to check for max index value before setting new state.
closeModal = () => {
this.setState(previousState => ({
activeModalIndex: previousState.activeModalIndex + 1,
}))
}
Also, please read about the dangers of setting index as key when rendering lists. If you happen to need ordering achievements by some value/priority and users can retrieve multiple pages of their achievements, it might cause rendering wrong components.
I want to generate a sliders dynamically according to user input, and don't know how to save values on change. Following is the code given of my implementation.
The problem is that I can't get value via event.target.value
// priceCities is an array of objects:
handlePrices(priceCities){
return priceCities.map( (cstate, index) => (
<li key={index} >{cstate.name} <Slider key={index} min={3} max={500} step={1} style={{height: 100}} axis="y"
defaultValue={5} id ={cstate.id} onChange={ this.handleSlider.bind(this,cstate.id )} value={this.state.values[cstate.id] } /> <span>{this.state.values[cstate.id]}</span> </li>
));
}
this.state = {
values: []
}
and onChange() method here:
handleSlider ( event,i ) {
// this.state.sliderValue[event.target.id] = event.target.value;
//console.log('handlerslider'+event.target.id+' '+event.target.value);
let values = [...this.state.values];
values[i] = event.target.value;
this.setState({ values });
}
Finally I found solution by defining the onChange method like this :
onChange={(event,value) => this.handleSlider(event, value,currState.id )}
and the code of handleSlider function :
handleSlider (event, value,id) {
let values = [...this.state.sliderValue];
values[id] = value;
this.setState({sliderValue: values });
console.log('handlerslider'+value+' '+id);
}
2020 Answer with Hooks
Something simple like this will work:
<Slider
onChange={(_, value) =>
setState(value)
}
step={1}
min={1}
max={50}
value={value}
valueLabelDisplay="auto"
/>