Adding input box values to existing object based on checkbox - javascript

I have a table of data that is rendered via an api call. It displays 4 values from the initial object. I mainly care about the name value as this is what's important later in a post request.
Each row in the table has a checkbox. When the checkbox is selected (true) the name associated with that checkbox is added to an object called selectedFields, as an object. For example If I select the checkbox with name id it creates an object store like:
"selectedFields": {
"id" : {}
}
This works fine and well. However, I've added 3 input boxes that are associated with each name. The inputs are lengthType, size, maxArrayElements, which are of course user selectable.
What I'm having trouble with is adding these values back to the object so it looks like:
"selectedFields": {
"id": {
lengthType: Variable,
size: 1,
maxArrayElements: 1
},
"price": {
lengthType: Fixed,
size: 10,
maxArrayElements: 1
}
}
How can I add these 3 values back to the name object that was created so it looks like the above example?
I don't want to post a wall of code, so I'm posting the checkbox function that handles creating the selectedFields object with the appropriate selected names. I suspect that the input values should get added here somehow, but I'm not sure.
checkbox = ({ name, isChecked }) => {
//handle check box of each fieldName
const obj = this.state.fieldNames.find(field => field.name === name);
if (isChecked === true) {
//checked conditional
this.setState(
{
selectedFields: {
...this.state.selectedFields,
[name]: {
...obj
}
}
},
() => {
console.log(
"callback in isChecked if conditional",
this.state.selectedFields
);
}
);
} else {
const newSelectedFields = this.state.selectedFields;
delete newSelectedFields[name];
this.setState(
{
selectedFields: newSelectedFields
},
() => {
console.log(
`box unchecked, deleted from object --->`,
this.state.selectedFields
);
}
);
}
};
You will have to make the first dropdown selection to view the data.
CodeSandbox link here

Answer
You need to change a few things because nothing is stating where to assign the new state outside of the root state object.
Handlers in your Index.js:
Your handlers for the change events aren't looking for the name of the object to determine if it exists or not. If you want to add it to the specified child object you better be sure it's there.
We adjust the handlers to take an object name and setState with object.assign on that specific object if it exists:
note: since lengthType doesn't have a name property we simply provide it with a string. e.currentTarget will provide the option span, not the root Dropdown element, so even supplying a name property to that component wouldn't allow us to use e.currentTarget.name - you may want to consult the Semantic UI documentation if you would prefer something different. I gave it a quick scan but didn't want to deep dive it.
handleChange = (e, obj_name) => {
if (this.state.selectedFields[obj_name]) {
let selectedFields = Object.assign({}, this.state.selectedFields);
selectedFields[obj_name] = Object.assign(
this.state.selectedFields[obj_name],
{ [e.target.name]: e.target.value }
);
this.setState({ selectedFields });
}
};
onLengthTypeChange = (e, obj_name) => {
if (this.state.selectedFields[obj_name]) {
let selectedFields = Object.assign({}, this.state.selectedFields);
selectedFields[obj_name] = Object.assign(
this.state.selectedFields[obj_name],
{ lengthType: e.currentTarget.textContent }
);
this.setState({ selectedFields });
}
};
The above, of course, won't work if you don't adjust your onChange events on your components so that, in addition to your event object, they also send your object name.
Handlers in your Component file:
Note: It was odd because in your Index.js file you seemed to half do this with lengthType but you weren't passing over additional data. You can't simply pass parameters into a handler - to get it to work you need to pass an anonymous function to the onChange properties that will take the event and pass it on to the handler functions with your object name:
<Table.Cell>
<Dropdown
placeholder="Pick a length Type:"
clearable
selection
search
fluid
noResultsMessage="Please search again"
label="lengthType"
multiple={false}
options={lengthTypeOptions}
header="Choose a Length Type"
onChange={e => onLengthTypeChange(e, name)}
value={lengthType}
required
/>
</Table.Cell>
<Table.Cell>
<Input
onChange={e => handleChange(e, name)}
value={this.state.size}
type="number"
name="size"
min="1"
placeholder="1"
required
/>
</Table.Cell>
<Table.Cell>
<Input
onChange={e => handleChange(e, name)}
value={this.state.maxArrayElements}
type="number"
name="maxArrayElements"
placeholder="1"
min="1"
max="100"
required
/>
</Table.Cell>
Once these things are adjusted, the code will update the specified properties on the child objects after the corresponding checkbox is selected.
Final Note:
I did not adjust it to save the previous state if you uncheck and then check the box. It wasn't specified in your question and I don't want to make assumptions.
Code Sandbox:
The adjusted code sandbox: https://codesandbox.io/s/createqueryschema-table-rewrite-bwvo4?fontsize=14
Additional Recommendations:
In your initial state your selectedFields is declared as an Array and then it is promptly turned into an Object when any checkbox is selected. I would suggest not doing this. Changing data types on a property during the course of running an application is very much asking for trouble.
When a checkbox is loaded you provide a checkbox function from your Index.js file. This is simply called box in your component. I would suggest keeping the names of properties and state equivalent when passing down from parent to child. It is much, much, much easier for someone else to come in and maintain if they have to - not to mention easier to retain your own sanity.
The above checkbox function takes props from child and passes them up to the parent. This would be the place to pass your collected data into a cache on the parent, into local/session storage, or whatever you want to do with your data. You could instead write code to the effect of: if the checkbox is selected when an input handler is called do a save - but I would say that it would probably be best on render since the screen is constantly updating anyway and you have the checkbox function readily passing props currently. This is preference, so it's your call
Good luck! Hope this helped!

Related

React-table: State doesn't change as expected, and table doesn't update

Codesandbox example: https://codesandbox.io/s/react-table-state-not-updating-hmquq?file=/src/App.js
I am using the react-table package (version 7.1.0).
I have a table which displays some invoices like so:
The user should be able to select some or all of these items using the selected checkbox.
The selected field is not part of the data. However, when the user hits a selected checkbox, the field should toggle and an array storing document numbers should be populated.
To store the document numbers, I have a stateful getter and setter:
const [selectedInvoiceIds, setSelectedInvoiceIds] = useState([]);
To populate the field, I am attempting to simply add the document number to the array immutably, from the onChange of the checkbox:
{
Header: "Selected",
accessor: "selected",
Cell: item => {
const { documentNumber } = item.row.values;
return (
<input
type="checkbox"
checked={selectedInvoiceIds.includes(documentNumber)}
onChange={() => {
setSelectedInvoiceIds([...selectedInvoiceIds, documentNumber]);
}}
/>
);
}
}
When a checkbox is clicked for the first time, the selectedInvoiceIds becomes populated:
selectedInvoiceIds: ["706942"]
The problems are:
The table does not update to reflect the state change, despite this prop on the checkbox:
checked={selectedInvoiceIds.includes(documentNumber)}
The selectedInvoiceIds value gets overwritten when another document number is added, instead of being added to, as if the state is re-initialising to [] somewhere in between.
Can it be explained why these state issues are occurring and how to get around it?
I am aware of the useTableState value exposed by react-table, but I don't know how I can apply it to this use case.
Codesandbox example: https://codesandbox.io/s/react-table-state-not-updating-hmquq?file=/src/App.js
There are multiple issues in this code:
// 1) updates to state should should use callback function if it uses prv state
return (
<input
type="checkbox"
checked={selectedInvoiceIds.includes(documentNumber)}
onChange={() => {
setSelectedInvoiceIds(prvSelectedInovicesId => [...prvSelectedInovicesId, documentNumber]);
}}
/>
);
// 2) also columns is using useMemo, however not providing dependencies, so columns are not being updated when the selectedInvociesIds state gets updated
// 3) you are not yet handling the toggle, this mean you are just always just adding to the array and not removing from it
here is a working version code sandbox
https://codesandbox.io/s/react-table-state-not-updating-wkri3?file=/src/App.js
Because of useMemo, add your array to last parameter
const columns = React.useMemo(
// ....
,
[selectedInvoiceIds]
);
And as we need toggle checkboxed it is more reasonable to keep selected ids in object instead of array and update it as this
setSelectedInvoices({
...selectedInovicesIds,
documentNumber: !selectedInovicesIds[documentNumber]}
)
so it will toggle mark

React - Mutating form object for each change . Is that anyway we can do it in a better way

handleChange(evt, field) {
let form = this.state.form;
form[field] = evt.value;
console.log('change triggered');
this.setState({
form: form
});
}
render(){
return(
<div>
<input type="text" name="name" id="name"
text={this.state.form.name}
onChange={event => { this.handleChange(event, 'name'); }} />
<br />
<input type="text" name="email" id="email"
text={this.state.form.email}
onChange={event => { this.handleChange(event, 'email'); }} />
</div>
);
}
I have added simple form for reference . In the above content Whenever form field changes handleChange method invoked and In that form object mutated updated as per field changes and setting to state object.
I want to know whether we can avoid mutation of object of each field text changes or Is there any other better way to address the same . Also want to know object mutation affects the performance in any way because here I mentioned couple of fields and In my original project I am working contains atleast 15 to 20 fields for each form.
Added the same working module.
https://stackblitz.com/edit/react-ssgcnu?file=Hello.js
Please help me out . Thanks in advance
If you don't want to update each character change you can use onBlur event.
onBlur={event => { this.handleChange(event, 'name'); }}
So that on leaving the field only you can update the state.
'...' Spread operator produces a shallow copy of an object
handleChange(evt, field) {
let form = {...this.state.form}; //object spread notation
form[field] = evt.value;
console.log('change triggered');
this.setState({ form: form });
}
When it comes to forms with React, there are two approaches.
1) Uncontrolled components, which basically making use of refs and getting values from the DOM (please check the official documentation for more details https://reactjs.org/docs/uncontrolled-components.html) or
2) Controlled components making use of states and getting/handling values from React states (please check the official documentation for more details https://reactjs.org/docs/forms.html#controlled-components)
With controlled components => React, state is the single source of truth (as official document suggests), that means, you need to provide methods to handle state changes as user provides the input
With uncontrolled components => Instead of updating state on every single change, you can get values inside of onSubmit method and handle them before submit. Since you won't need to update the state, you won't need additional functions to handle state changes.
For you seeking of better way to handling things and avoid mutations, it actually depends on your use case, but official documentation suggests that
In most cases, we recommend using controlled components to implement
forms. In a controlled component, form data is handled by a React
component
When it comes to mutating object, indeed mutations is not good, but there are ways to avoid mutations during state changes. as #mhkit already suggested, you can use spread operator to create a new object (shallow copy, that means it only copies the values) or you could use Object.assign() method.
Let's say you have the following state
state = {
form: {
email: '',
name: '',
lastName: '',
}
}
When you user provides the email, you basically need to update the email field,
in that case in your handleChangeEmail() method, what you can do is the following
this.handleChangeEmail = (value) => {
this.setState(({form}) => {
return {
form: {
...form,
email: value
}
})
}
So with this method, what I basically do is,
1) I utilize functional setState and extracted the current value of form via ES6 object destructuring, then I say that, okay my new form object inside of the state, will have all the existing field that former form has, BUT, the email field will have a new value based on the new input user provided.
By this way, instead of mutating form object, we created a shallow copy of it with some values are exactly the same, but some values are updated. THUS we prevent the mutation

Warning: This synthetic event is reused for performance reasons happening with <input type="checkbox" />

I've been working on a simple react-redux todo example for a class and I came across several warning messages that show in the console everytime I check and uncheck a checkbox input.
You can see the warnings in the following images.
I also did a google search for the warning message but couldn't find any solution that works. Also, what stroke my attention was that it looks like it was trying to access every property of the native event, and DOM element.
This is the code for the presentational component that has the input checkbox
class TodoItem extends React.Component {
state = {
isChecked: false
};
handleCheckbox = () => {
this.setState({
isChecked: !this.state.isChecked
});
};
render() {
const { todos, onItemClick } = this.props;
const { isChecked } = this.state;
return (
<div>
<ul>
{todos.map((todo, id) => {
return (
<li key={id} onClick={onItemClick}>
<input
onChange={this.handleCheckbox}
type="checkbox"
checked={isChecked}
/>
<label>
<span />
{todo.textInput}
</label>
</li>
);
})}
</ul>
</div>
);
}
}
export default TodoItem;
I uploaded the example on CodeSandbox as well: https://codesandbox.io/s/k0mlxk1yqv
If you want to replicate this error you need to add an Item to the todo List and click the checkbox to check and uncheck a couple of times.
If anyone has any idea why this warning signs keep appearing and how to disable them I would appreciate your input very much :)
This happened because the event implicitly passed to onItemClick is used in an asynchronous context.
As Andre Lemay said, you should assign your needs to local variables and reference them.
In my case, I had this code:
handleInput = e => { // <-- e = synthetic event
this.setState(state => ({ // <-- asynchronous call
data: {
...state.data,
[e.target.name]: e.target.value // <-- this was causing the warnings (e.target is in an asynchronous context)
}
}));
};
Then I changed it to:
handleInput = e => {
const { name, value } = e.target; // <-- moved outside asynchronous context
this.setState(state => ({
data: {
...state.data,
[name]: value
}
}));
};
I'd suggest trying two solutions:
First change
onChange={this.handleCheckbox}
to
onChange={() => this.handleCheckbox()}
If that won't work, in 'handleCheckbox' add event.persist(); Like this:
handleCheckbox = (event) => {
event.persist();
this.setState({
isChecked: !this.state.isChecked
});
};
This may be a little late, but I just came across the same problem and solved in a way that I think might be better than Adam Orlov's answer. I don't believe either answer is directly applicable to the asked question, but this comes up when googling about synthentic events and checkboxes so it's as good a place as any...
I believe Adam is correct in his belief that React will essentially clear all properties of the SyntheticEvent object (which makes sense, since React is telling us that it's reusing the object).
However, unless you need the entire object, I don't think calling event.persist() is the best solution, as according to the documentation, that will remove the object from the pool (presumably they put it there for a good reason).
If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.
Instead of doing this, if you only need one or two values from the event object, you can just assign those to local variables, and then reference the local variables inside your own function, like this:
<input type="checkbox" onChange={(event) => {
let checked = event.currentTarget.checked; //store whatever values we need from from the event here
this._someOtherFunction(checked)
} />
In this way, you don't have to restructure your code in any way to avoid doing anything async that relies on event data, and you also don't have to worry about potential performance impacts as you allow React to do what it wants with the event pool.
Similar problem here though my setup is, functional component, Material UI <Textform /> input.
The guy above that mentioned event.persist();, thank you that worked for me, but the first suggestion had no noticeable affect, not sure if thats because Im using functional components and not class components. (I dont use class components anymore, only functional with hooks)
Also note the warning info suggested to use event.persist(). My issue was I was capturing form input using onChange and storing input into my state, after about the second or third character it would throw errors and also crash my app.
Before:
const handleChange = (e) => {
setState((form) => ({
...form,
[e.target.name]: e.target.value,
}));
};
After:
const handleChange = (e) => {
e.persist();
setState((form) => ({
...form,
[e.target.name]: e.target.value,
}));
};
So it appears this is the correct solution to a similar issue, while I was not using a checkbox, I was using a form input, a Material-UI <TextField />. I can remove the single line of
e.persist();
and the code will fail again, add it back, everything is good no crashing no warnings.
for the ones that came to this problem with react native.
i face this problem on a navigation
PersonalProductsScreen.navigationOptions=({navigation})=>{
const handleEditButton=navigation.navigate.bind(this,"EditProduct")
return {
headerTitle:"My Products",
headerRight:<CustomHeaderButton
iconName="ios-add"
title="Add"
iconSize={26}
color={colors.bright}
onPress={handleEditButton}
/>
}
}
pay attention to the method i used . I was trying to bind the navigate method.
This is the refactor:
const handleAddButton=()=>navigation.navigate("EditProduct")

ReactJS - Using .setState to change nested values

I'm attempting to set the state of a specific element (a checkbox) so that I can later pass this data back to a parent component that will in turn update a JSON object. I am able to set the state of higher level elements but I am not understanding how to access the nested values.
How do I use .setState to set the state of a specific element? Such as this.state.data[0].checked
I'm attempting to set the state with something like this which would only update data at the moment:
handleChange: function(event) {
this.setState({data: event.target.value}});
},
It looks like you could use the immutability helpers, and if you are using a numerical / dynamic key, you should look at my question here.
Your solution would look something like this:
handleChange: function(index, event) {
var data = React.addons.update(this.state.data, {
[index]: {
checked: {$set: event.target.checked}
}
});
this.setState({
data: data
})
},
Notice the use of e.target.checked and not e.target.value for checkboxes to get the boolean state and not the value associated with the checkbox.
This is how you'd attach your function with the i index you'd have to set beforehand:
onChange={this.handleChange.bind(this, i)}

The best way to retrieve data of a range of different child components in React Js

I am quite new to react js. Have searched a bit but haven't got an answer to this question:
Suppose I have two dynamic input tables in my page, and each of them is a separate component. And the lines are different components (React classes) as well. And on the page, there is a save button. Once the save button is clicked, all the information of the page should be gathered and pushed to server as a JSON string.
The very obvious approach for me is to gather the information via jQuery. This will definitely work. But it makes me feel it is not the react way of doing it. Since react data is one way binding, I am not quite sure how to handle this situation more appropriately. Any suggestions?
As always, there are a few ways of doing this.
1. Using refs.
You can assign references to input fields and then loop through them to get the values. The good thing about this approach is that no events are needed, but you still have to somehow know the reference to each. It adds complexity if you have dynamic fields or heavily nested fields. This is still my preferred way, mainly because you don't need events (e.g. keyup, blur, change, depending on your usage)
2. Using state
This makes it easier to instantly get values, if the values are updated in state as soon as the user makes a change to the field. Obviously you will need to know when a change has been made so you need events.
Your event callback can do one of many things, such as
update a global state object (e.g. via redux)
update form's values (or state) object via context usage
I hope this helps plan your forms.
Please check out this simplified example: https://jsfiddle.net/352v4n72/2/
It has three components. The most basic one is Input, which informs its parent when the value is changed:
var Input = React.createClass({
render: function() {
return (
<input type="text" placeholder={this.props.name}
onChange={e => this.props.onInputChange(e.currentTarget.value)} />
);
}
});
Now, its parent component, Line:
var Line = React.createClass({
onInputChange(inputId, value) {
var obj = {};
obj[inputId] = value;
this.setState(obj, state => {
this.props.onLineChange(this.props.lineId, this.state);
});
},
render: function() {
return (
<div>
<Input name="firstName" onInputChange={value => this.onInputChange('firstName', value)} />
<Input name="lastName" onInputChange={value => this.onInputChange('lastName', value)} />
<Input name="email" onInputChange={value => this.onInputChange('email', value)} />
</div>
);
}
});
This component holds three Input components. When each of them changes, the onInputChange function is called, and this function basically aggregates all the input values, creating a whole "line" data.
The last component is Table:
var Table = React.createClass({
onLineChange(lineId, value) {
var obj = {};
obj[lineId] = value;
this.setState(obj, state => console.log(this.state));
},
render: function() {
return (
<div>
<Line lineId={1} onLineChange={this.onLineChange} />
<Line lineId={2} onLineChange={this.onLineChange} />
<Line lineId={3} onLineChange={this.onLineChange} />
</div>
);
}
});
This component holds three lines, and just like the line aggregates Input's, Table aggregates Line's. You can see the state as it changes in the console.

Categories

Resources