I have data coming from getTasks() and I store it on tasks
I created a component for each task with their data with a checkbox for each task.
I want to click a checkbox and display only the one I clicked, not them all.
I am using React Native. How can I do this?
thanks.
export default () => {
const [tasks, setTasks] = React.useState([]);
const [checked, setChecked] = React.useState(false);
React.useEffect(() => {
(async () => {
const response = await getTasks();
setTasks(response);
})();
}, []);
return tasks.map(({id, title, description, date}) => (
<View key={id} style={styles.task}>
<CheckBox
value={checked}
onValueChange={() => {
setChecked((prevState) => !prevState);
}}
/>
<View style={styles.taskTextContainer}>
<Text>{title}</Text>
<Text>{description}</Text>
<Text>{date}</Text>
</View>
</View>
));
};
You're passing the same value={checked} to all checkboxes. You can create an array of booleans to accomplish that.
You have two problems in that code, first is the problem you described, the second one is the way you're changing the state of that checkbox.
About the first problem, in order to display the respective state for each task, you should also include a property that defines if the task is marked or not.
First, verify the name of the property thats set if the task is done or not, I'll give the name here as done and done is of type boolean.
// notice the done property
return tasks.map(({id, title, description, date, done}) => (
Then, you should use done to change the checked state of your Checkbox like this:
<Checkbox
value={done}
...
/>
Now you should change a little how you change that checked state.
First, you need to write a function to change the value of done, to fase or true, according by the Checkbox component's state and that's really simple:
const onCheckboxChange = (taskId) => {
// first, let's find the index of the changed task
const index = tasks.findIndex(({ id }) => id === taskId)
// next, let's get the data by the index
const data = tasks[index]
// now, we can just toggle done value like this
data.done = !data.done
// then, let's assign updated data to its own index in tasks array
tasks[index] = data
// finally, we can update the tasks state using a copy of changed tasks
setTasks([...tasks])
}
Related
I have a problem where useState updates the state but does not show the changes until I refresh the app. First, I declare an array called sampleFriends, made up of objects with fields "name", "location", and "picture" (each element of the array looks similar to: {name: 'John', location: 'Boston', picture: TestImage}).
Then, I have the following useState:
const [selectedFriends, setSelectedFriends] = useState([])
At some point, I successfully render
sampleFriends.map(({ name, location, image }, index) => (
<NewMsgTableRow
name={name}
index={index}
location={location}
image={image}
onPress={() => selectFriend(name)}
/>
))
And I also have this function right above
const selectFriend = name => {
// if the friend is not already selected
if (!selectedFriends.find(e => e === name)) {
const newFriends = selectedFriends
newFriends.push(name)
setSelectedFriends(newFriends)
}
}
The component NewMsgTableRow has a button that uses onPress
<TouchableOpacity
onPress={onPress}
>
So, I want to render selectedFriends as soon as they are selected (the TouchableOpacity is touched and thus the state updates). However, when I click the button, nothing shows up until I edit and save my code and it refreshes automatically. It was my understanding that useState rerendered the components as soon as it was updated, but it is not happening in this case and I can't figure out why. I've been reading that it is async and that it does not change it instantly, but I don't know how to make it work. Hope it makes sense and thanks for your help!!
You can use array spread or Array.concat() to make a shallow clone, and add new items as well) so change the line below:
const newFriends = selectedFriends
to this line :
const newFriends = [...selectedFriends]
I have 2 dropdowns, I need to update the values of the second dropdown based on the first. On page load, I need to initialize the dropdown values based on conditions, like so:
const getInitialState= () => {
//defaultSelected has a value if saved in DB, if not then I set the 0 option value in type, based on this value option value will be populated in the dropdown. I get all the values from parent component
let updatedType= defaultSelected !== ""
? defaultType
: Region.RegionType;
console.log('typeeeee', type)
return updatedType;
}
useEffect(() =>{
//Here in options dropdown values object is set based on type selected
let updatedOptions = !_.isEmpty (type)
? "a" //value from db if save
: "b"; //If not 0th value of the object
setOptions(prevOptions => ({
...prevOptions,
["options"]: updatedOptions
}));
},[type]);
const [type, setType] = useState(getInitialState);
const [options, setOptions] = useState('');
const changeCrTypeHandler = (event) =>{
const { name, value } = event.target;
setType(type =>({
...type,
[name]: value}));
}
return (
<>
<select
onChange={changeCrTypeHandler}
style={{ width: "150px" }}
value={!_.isEmpty(type)}
name= "type"
>
{_.map(customMap, (crId) => { //I get this cusomMap from Parent component
return (
<option
id={crId.customType}
value={crId.customType}
key={crId.customType}
>
{crId.customType}
</option>
);
})}
</select>{" "}
<select
onChange={changeCrHashHandler} //second dropdown onchange
style={{ width: "250px" }}
value={crHashId}
name= "options"
>
{_.map(!_.isEmpty(options), (o) => { //This options are created based on type, which is currently not getting set
return (
<option
id={o.customId}
value={o.customId}
key={o.customId}
>
{o.name}
</option>
);
})}
</select>
Based on type value then I need to set the value of the option.
But my useEffect hook doesn't get called upon changes in type. How can I call the useEffect method whenever there is any changes in the type?
Any help for this would be much appreciated.
You are calling setState before it is defined. For the initial value, you just return it without calling setState. For having the second dropdown state depending on the first one's state, you could use the useEffect hook, like so:
const [firstDropdown, setFirstDropdown] = useState(getInitialState({type:"", options:""}));
const [secondDropdown, setScondDropdown] = useState(0); // you could use whatever initial value you want here
const getInitialState= () => {
//some logic to get the value in type
//just return that inital value without calling setState
}
// the function inside useEffect runs every time firstDropdown changes
useEffect(()=>{
// some logic on firstDropdown
let value = firstDropdown+25
setScondDropdown(value)
},[firstDropdown]);
Update:
In your useEffect, change this :
setOptions(prevOptions => ({
...prevOptions,
["options"]: updatedOptions
}));
to this :
setOptions(updatedOptions)
Use the useState as below:
const [state, setState] = useState({})
setState((prev) => {
// do whatever you want to do with the previous state
})
And for the name, please do not use setState as your setter function of useState. because setState is another method in the class based components in React
I have a data array that I am mapping onto a material-ui Typography.
{
array.map((item, id)=> <Typography key={id} value={item.name} />)
}
The code displays the typography with their respective values on the browser as expected, however, I am trying to set the mapped value of Typography into a state like this...
const [data, setData] = useState();
...
{
array.map((item, id) =>
<Typography key={id} value={item.name} {(e)=>setData(e.target.value)} />
)}
This method does not work.
How do I make data to be the value of <Typography/>
If I understand correctly, you're trying to store item.name within local state? How are you receiving the data that you are mapping through?
Are you trying to store each item you're mapping through and represent key-value pairs of an object within local state? What are you trying to do with the state once you have it?
As far as I know, there's is no way to assign values to local state while you are mapping through an array. However, there are multiple ways to assign the data to local state outside of the return.
Also, what is this line doing?
{(e)=>setData(e.target.value)}
It seems you're trying to create a controlled input on a Typography component without an onChange prop, as well as on a component that does not take input.
One more aside, although it is not a big deal -
{
array.map((item, id) =>
<Typography key={id} value={item.name} ..../>
)}
id here is usually referred to as index
Edited for answer
I normally use Redux to manage the global state, but the process should be the same for Context Provider:
const yourComponent = () => {
const [data, setData] = useState();
// This is the array you get from the Context Provider
const yourArray = getArrayFromContextProvider();
useEffect(() => {
// If you want to normalize the array data
if (yourArray) {
const dataObj = {};
yourArray.forEach((item, index) => {
// Make the key unique by using the item id OR you can use index
dataObj[item.id] = item;
// OR you can add the item value as the key
dataObj[item.value] = item;
});
setData(dataObj);
}
// If you just want to store the array
if (yourArray) {
setData(yourArray)
}
}, [yourArray]);
return <div>
{/* ...Your map */}
</div>;
};
export default yourComponent
I have an array of objects that looks like this:
const columns = [
{
key: "Source_campname",
title: "TS Camp Name",
customElement: function (row) {
return (
<FormControlLabel
control={
<Checkbox
checked={checkbox[row.id]}
key={row.id}
onChange={() =>
handleChange(row.Source_campname, row.id, checkbox)
}
name={row.id}
/>
}
label={[row.Source_campname]}
/>
);
}
},
{
key: "Tracker_campname",
title: "TR Camp Name"
}
];
You can see a "handleChange" function above, this is used to check/uncheck the component
The handleChange function looks like this:
const handleChange = (name, campid) => {
setCheckBox({ ...checkbox, [campid]: !checkbox[campid] });
};
You can also see a "customElement" function above. This function is rendered in another React component named ThanosTable. I will just write down part of the code where the rendering of customElement happens below.
return (
<> columnArray[0].customElement(row) </>
);
In the end you get 10 checkboxes, and you have a few pages that can be changed using pagination.
Do check my codesandbox link here for a working example:
https://codesandbox.io/s/magical-germain-8tclq
Now I have two problems:
Problem 1) If I select a few checkboxes, then go to second page and return, the checkbox state is empty and the original checkboxes are unselected. No idea why that is happening. How do I prevent that?
Problem 2) The value of checkbox state is always an empty object ({}) inside customElement function. You can see this by checking console.log(checkbox) inside customElement function (Check Line 76 in codesandbox). I thought it should be an array with selected checkbox items.
The useEffect hook embodies all the lifecycle events of a component. Therefore if you try to set checkbox in useEffect it'll infinitely update the component because updating state calls useEffect. This is probably why you see your state constantly being reset.
Instead, initialize your state with the rows before rendering.
const rows = [
...
];
let checkboxObj = {};
// if (rows) {
rows.forEach((e) => {
checkboxObj[e.id] = false;
});
const [checkbox, setCheckBox] = useState(checkboxObj);
I have populated a FlatList with data fetched from Google's firebase backend. The implementation is rather standard, here's a stripped down version:
export default class Day extends Component {
state = { data : [], today: false }
componentWillMount = async () => {
const { today } = this.state;
const { calendarDb } = this.props
await calendarDb.onNewAgenda({
day : today
, then: this.parseOnListed
})
}
parseOnListed = blob => {
const { data } = this.state;
data.push(blob)
this.setState({ data: data })
}
renderItem = ({ item }) =>
<Hour data = {item}/>
render = () =>
<FlatList
data = {this.state.data}
renderItem = {this.renderItem}
keyExtractor = {item => item.ID}
/>
}
The issue is that every time a new blob is pushed into data, the <Image/> component in <Hour data={item}/> flickers. This makes the list a no-go in terms of user experience. What gives? <Hour/> is standard as well, and more or less look like this:
const Hour = ({ data }) =>
<View>
<Image source={{uri:data.uri}}/>
<Text> {data.name} </Text>
</View>
The content of <Text> does not flicker, only the image from <Image .../>
Check whether keyExtractor is getting unique ID or not.
The flat list is re-rendering on state update and images are downloaded again. Because, each row is not uniquely identified as said in comments by #Guruparan Giritharan.
I found another reason that triggers this issue, of the FlatList flikering on React native. In my case, it happened every time I updated/changed the state of any function component. So, for instance, I was keeping the fetch results (data) and the next-page-id (for the next paginated fetch) in two separate function components:
const [data, setData] = useState([]);
const [pageId, setPageId] = useState(null);
Hence, every time would capture the results of my fetch, I would first set the data update and then the page id. It was the page id update what was causing the flicker.
const onEndReachedFetch = async () ={
fetch(pageId).then(result => {
setData(result.Data);
setPageId(result.pageId);
});
}
The fix was just to put the state data together so there is a single update instead. Then react is happy and doesn't flicker when adding new items to the list.
const onEndReachedFetch = async () ={
fetch(pageId).then(result => {
setResult(result);
});
}
Beware of any side states that you may be updating in the background, as they may also cause the flickering if they are triggered by anything on the FlatList.