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);
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 this FormControl element with Select that accepts an array of options that is being used for MenuItem options and also a value as props and this component looks like this:
const TaxonomySelector = (props) => {
const { isDisabled, taxonomies, selectedTaxonomy, handleTaxonomyChange } = props;
return (
<Grid item xs={12}>
{console.log(selectedTaxonomy)}
{console.log(taxonomies)}
<FormControl disabled={isDisabled} fullWidth>
<InputLabel>Таксономия</InputLabel>
<Select
value={selectedTaxonomy || ''}
onChange={handleTaxonomyChange}>
{Object.values(taxonomies).map((taxonomy) => (
<MenuItem key={taxonomy.id} name={taxonomy.name} value={taxonomy}>
{taxonomy.name} от {moment(taxonomy.date).format('YYYY-MM-DD')}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
);
};
The values that I pass as props are correctly displaying as filled out in the console at all stages the component is being rendered. And also in the case when this component is used for selection through using handleTaxonomyChange function everything is working correctly with user being able to select a particular option out of the array provided to the MenuItem. However, the problem occurs in case when the parent component of this component is being open for View Only or with already pre-defined values. In this case I get the following:
It seems like there's something is being passed to the Select component (even I checked through React Component in DevTools and value was showed correctly) but for some reason it is not being displayed.
The parent component contains the following code related to this part:
const INITIAL_SELECTED_TAXONOMY = null;
const [selectedTaxonomy, setSelectedTaxonomy] = useState(INITIAL_SELECTED_TAXONOMY);
const handleTaxonomyChange = (e) => setSelectedTaxonomy(e.target.value);
useEffect(() => {
getTaxonomies();
}, []);
useEffect(() => {
if (viewTypeOnlyView) {
handleStageChange(1);
handleDialogTitleChange('Конструктор КС. Режим просмотра');
}
if (viewTypeEdit) {
handleDialogTitleChange('Конструктор КС. Режим редактирования');
}
if (viewTypeCopy) {
handleDialogTitleChange('Конструктор КС. Дублирование КС');
}
if (defaultData) {
if (defaultData.name) setName(defaultData.name);
if (defaultData.taxonomy) setSelectedTaxonomy(defaultData.taxonomy);
// if (defaultData.entryPoints) setSelectedEntryPoints(defaultData.entryPoints);
if (defaultData.entryPoints) {
getEntryPointDescsFn('4.1', defaultData.entryPoints);
}
if (defaultData.message) setMessage(defaultData.message);
}
}, [viewType]);
ViewType is a prop that is being passed to this component and calling those methods in order to fill up the state with predefined values.
And the TaxonomySelector component inside the return statement:
<TaxonomySelector
taxonomies={taxonomies}
isDisabled={currentStage === 1}
selectedTaxonomy={selectedTaxonomy}
handleTaxonomyChange={handleTaxonomyChange} />
At first I thought that the issue could be related to how the component is being rendered and maybe it renders before that data pre-fill useEffect hook is being triggered. However, it seems that other elements, like the ones with string values name and message are being correctly filled out with no issues. Seems like that the issue is specifically related to Select elements. Could you let me know what could it possibly be?
Looks like disabled prop in FormControl is true.
For debug set disabled prop false
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])
}
Dynamic forms with react and antd are eluding me. I have scoured the web looking for answers to no avail. Here is a codepen with a recreation of the issue I am having: https://codepen.io/sethen/pen/RwrrmVw
Essentially, the issue boils down to when you want loop through a bunch of values that are stored in state, like so:
class MyClass extends React.Component<{}, {}> {
constructor(props) {
super(props);
this.state = {
data: [
{ name: 'foo' },
{ name: 'bar' },
{ name: 'baz' }
]
};
}
You can think of these values as being fetched from some remote API.
As you can see, I have an array of objects with the key of name in the state. Further on down in the render cycle is the following:
return data.map((value, index) => {
const { name } = value;
return (
<Form key={ index } initialValues={ { name } }>
<Form.Item name='name'>
<Input type='text' />
</Form.Item>
<Button onClick={ this.handleOnDeleteClick.bind(this, index) }>Delete</Button>
</Form>
);
This attempts to loop through the values stored in the state and put the values into an input. It also adds a little delete button to get rid of that item. The first time it renders, it does as you expect it to loading the value into the input value.
The issue is when you try to delete one of the items, like the middle one, it will delete the next item. The core of the issue is that the render is acting different than I expect it to when deleting an item. I am expecting that when I delete an item, it will take it out of state and load the ones that are left. This is not happening.
My question is, how am I able to load dynamic data in this way with antd whilst being able to delete each item?
The main mistake in this form that you assign the key property as the array index, and on deleting the middle item, the last component will get a new key.
In React, changing the key will unmount the component and lose its state.
Don’t pass something like Math.random() to keys. It is important that keys have a “stable identity” across re-renders so that React can determine when items are added, removed, or re-ordered. Ideally, keys should correspond to unique and stable identifiers coming from your data, such as post.id.
Also, in your example, you actually render three forms instead of a single form and three fields.
Every <form/> has in its inner state all states of its form fields, so you will have a single object with all input values in it.
Antd.Form just a wrapper for such form, you can get Form.Item values in onFinish callback for example.
class MyClass extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [{ name: "foo" }, { name: "bar" }, { name: "baz" }]
};
}
handleOnDeleteClick = index => {
this.setState({
data: [
...this.state.data.slice(0, index),
...this.state.data.slice(index + 1)
]
});
};
render() {
const { data } = this.state;
return (
<Form>
{data.map(({ name }, index) => {
return (
<Form.Item key={name}>
<Input type="text" />
<Button onClick={() => this.handleOnDeleteClick(index)}>
Delete
</Button>
</Form.Item>
);
})}
</Form>
);
}
}
I've tried to ask this ungooglable to me question dozens of times. I've made almost the simpliest example possible to ask this question now.
I change the value of the hook in the handleChange method. But then console.log always shows previous value, not new one. Why is that?
I need to change the value of the hook and then instead of doing console.log use it to do something else. But I can't because the hook always has not what I just tried to put into it.
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState(options[0]);
const handleChange = val => {
setValue(val);
console.log(value);
};
return (
<div>
<div>{value}</div>
<br />
<Autocomplete
value={value}
onChange={(event, newValue) => {
handleChange(newValue);
}}
options={options}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
/>
</div>
);
}
You can try it here.
https://codesandbox.io/s/awesome-lumiere-y2dww?file=/src/App.js
I believe the problem is that you are logging the value in the handleChange function. Console logging the value outside of the function logs the correct value. Link: https://codesandbox.io/s/async-fast-6y71b
Hooks do not instantly update the value you want to update, as you might have expected with classes (though that wasn't guaranteed either)
State hook, when calling setValue will trigger a re-render. In that new render, the state will have the new value as you expected. That's why your console.log sees the old value.
Think of it as in each render, the state values are just local variables of that component function call. And think as the result of your render as a result of your state + props in that render call. Whenever any of those two changes (the props from your parent component; the state, from your setXXX function), a new render is triggered.
If you move out the console.log outside of the callback handler (that is, in the body of your rendered), there you will see in the render that happens after your interaction that the state is logged correctly.
In that sense, in your callbacks events from interactions, you just should worry about updating your state properly, and the next render will take care to, given the new props/state, re-render the result
The value doesn't "change" synchronously - it's even declared with a const, so even the concept of it changing inside the same scope doesn't make sense.
When changing state with hooks, the new value is seen when the component is rerendered. So, to log and do stuff with the "new value", examine it in the main body of the function:
const ControllableStates = () => {
const [value, setValue] = React.useState(options[0]);
const handleChange = val => {
setValue(val);
};
// ADD LOG HERE:
console.log('New or updated value:', value);
return (
<div>
<div>{value}</div>
<br />
<Autocomplete
value={value}
onChange={(event, newValue) => {
handleChange(newValue);
}}
options={options}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
/>
</div>
);
}
You're printing out the old value in handleChange, not the new val.
i.e.
const handleChange = val => {
setValue(val);
console.log(value);
};
Should be:
const handleChange = val => {
setValue(val);
console.log(val);
};
Actually, lets get a little back and see the logic behind this scenario.
You should use the "handleChange" function ONLY to update the state hook, and let something else do the logic depends on that state hook value, which is mostly accomplished using "useEffect" hook.
You could refactor your code to look like this:
const handleChange = val => {
setValue(val);
};
React.useEffect(() => {
console.log(value);
// do your logic here
}, [value])
So I think that the main problem is that you're not understanding how React
deals with components and states.
So, I'll vastly simplify what React does.
React renders a new component and remembers it's state, it's inputs (aka
props) and it's the state and inputs of the children.
If at any given point an input changes or a state changes, React will render
the component again by calling the component function.
Consider this:
function SomeComponent(text) {
return (<div>The <i>text</i> prop has the value {text}</div>)
}
Let's say the initial prop value is "abc", React will call SomeComponent("abc"), then the function returns
<div>The <i>text</i> prop has the value abc</div> and React will render that.
If the prop text does not change, then React does nothing anymore.
Now the parent component changes the prop to "def", now React will call
SomeComponent("def") and it will return
<div>The <i>text</i> prop has the value def</div>, this is different from
last call, so React will update the DOM to reflect the change.
Now let's introduce state
function SomeComponent() {
const [name, setName] = React.useState("John")
function doSomething()
{
alert("The name is " + name)
}
return (
<p>Current name: {name}</p>
<button onClick={() => setName("Mary")}>Set name to Mary</button>
<button onClick={() => setName("James")}>Set name to James</button>
<button onClick={() => doSomething()}>Show current name</button>
)
}
So here React will call SomeComponent() and render the name John and the 3
button. Note that the value of the name variable does not change during the
current execution, because it's declared as const. This variable only reflects
the latest value of the state.
When you press the first button, setName() is executed. React will internally store
the new value for the state and because of the change of state, it will render
the component again, so SomeComponent() will be called once again. Now the variable name will
reflect again the latest value of the state (that's what useStatedoes), so in this case Mary. React
will realize that the DOM has to be updated and it prints the name Mary.
If you press the third button, it will call doSomething() which will print the
latest value of the name variable because every time React calls
SomeComponent(), the doSomething() function is created again with the latest
value of name. So once you've called setName(), you don't need to do
anything special to get the new value. React will take care of calling the
component function again.
So when you don't use class components but function components, you have to think
differently: the function gets called all the time by React and at any single
execution it reflects the latest state at that particular point in time. So when you
call the setter of a useState hook, you know that the component function will
be called again and useState will return the new value.
I recommend that you read this article, also read again Components and
Props from the React documentation.
So how should you do proceed? Well, like this:
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState(options[0]);
const handleChange = val => {
setValue(val);
console.log(value);
};
const handleClick = () => {
// DOING SOMETHING WITH value
alter(`Now I'm going to do send ${value}`);
}
return (
<div>
<div>{value}</div>
<br />
<Autocomplete
value={value}
onChange={(event, newValue) => {
handleChange(newValue);
}}
options={options}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
/>
<button type="button" onClick={handleClick}>Send selected option</button>
</div>
);
}
See CodeSandbox.