Updating nested state in objects through an input element in React - javascript

I am creating a form that allows orders to be updated. The input fields have to be populated with the current state of each object that I render and I'd like to be able to edit the input field. I have simplified my code to just one input field and believe that I'm able to do the majority of what I'm attempting using the following code --
class EditOrderForm extends React.Component {
...
handleChange(e, key) {
const order = this.props.orders[key];
const updatedOrder = {
...order,
[e.target.name]: e.target.value
}
this.props.updateOrder(key, updatedOrder);
}
renderEditOrderForm(key) {
const order = this.props.orders[key]
return (
<div key={key}>
<form >
<input type="text" name="name" value={order.data.name} placeholder="order name" onChange={(e) => this.handleChange(e, key)} />
...
</form>
</div>
)
}
render() {
return (
<div>
<h2>Edit Orders</h2>
{
Object.keys(this.props.orders).map(this.renderEditOrderForm)
}
</div>
)
}
}
*************Parent Component*************
class AppComponent extends React.Component {
import EditOrderForm from './EditOrderForm';
...
updateOrder(key, updatedOrder) {
const orders = [...this.state.orders]
orders[key] = updatedOrder;
this.setState({ orders: orders });
}
...
}
The state that's set at the parent component level is an array of objects and the data structure for the objects that I'm passing to renderEditOrderForm() has the structure --
{
data: Object,
meta: Object,
__proto__: Object
}
Where data: Object contains the key-value pairs that I'm trying to change, in this case the key name nested under data: Object (above) and I would like put it back into the array once updated/edited. I am slightly able to update the name of an order however when I try to update it (say, type an 'x') the object now has this structure --
{
data: Object,
meta: Object,
name: "John Smithx"
__proto__: Object
}
I can intuit that [e.target.name]: e.target.value is probably the culprit, however I'm completely at a loss as to how I'm supposed to access the nested key name in data: Object -- I have tried e.target.data.name, however that gives me undefined and have tried a variety of other combinations. Without using Redux (unfortunately don't have time to learn due to time constraints), does anyone know how I can access/target the key name in order to update nested in data: Object?

You need to change the field order.data.name but your code is only adding a new field to the order object. Replace
handleChange(e, key) {
const order = this.props.orders[key];
const updatedOrder = {
...order,
[e.target.name]: e.target.value
}
this.props.updateOrder(key, updatedOrder);
}
with
handleChange(e, key) {
const order = this.props.orders[key];
let updatedOrder = { ...order };
updatedOrder.data[e.target.name] = e.target.value;
// or if you have only 1 field called name, you can use updatedOrder.data.name = e.target.value;
this.props.updateOrder(key, updatedOrder);
}

Related

Dynamic input value in React

So I have a fragment factory being passed into a Display component. The fragments have input elements. Inside Display I have an onChange handler that takes the value of the inputs and stores it in contentData[e.target.id]. This works, but switching which fragment is displayed erases their values and I'd rather it didn't. So I'm trying to set their value by passing in the state object to the factory. I'm doing it in this convoluted way to accomodate my testing framework. I need the fragments to be defined outside of any component and passed in to Display as props, and I need them all to share a state object.
My problem is setting the value. I can pass in the state object (contentData), but to make sure the value goes to the right key in the contentData data object I'm trying to hardcode it with the input's id. Except contentData doesn't exist where the fragments are defined, so I get an error about not being able to reference a particular key on an undefined dataObj.
I need to find a way to set the input values to contentData[e.target.id]. Thanks.
File where fragments are defined. Sadly not a component.
const fragments = (onChangeHandler, dataObj) => [
<Fragment key="1">
<input
type="text"
id="screen1_input1"
onChange={onChangeHandler}
value={dataObj['screen1_input1']} // this doesn't work
/>
one
</Fragment>,
<Fragment key="2">
<input
type="text"
id="screen2_input1"
onChange={onChangeHandler}
value={dataObj['screen2_input1']}
/>
two
</Fragment>
]
Display.js
const Display = ({ index, fragments }) => {
const [contentData, setContentData] = useState({})
const onChange = e => {
// set data
const newData = {
...contentData,
[e.target.id]: e.target.value
}
setContentData(newData)
};
return (
<Fragment>{fragments(onChange, contentData)[index]}</Fragment>
);
};
After conversing with you I decided to rework my response. The problem is mostly around the implementation others might provide in these arbitrary fragments.
You've said that you can define what props are passed in without restriction, that helps, what we need to do is take in these nodes that they pass in, and overwrite their onChange with ours, along with the value:
const RecursiveWrapper = props => {
const wrappedChildren = React.Children.map(
props.children,
child => {
if (child.props) {
return React.cloneElement(
child,
{
...child.props,
onChange: props.ids.includes(child.props.id) ? child.props.onChange ? (e) => {
child.props.onChange(e);
props.onChange(e);
} : props.onChange : child.props.onChange,
value: props.contentData[child.props.id] !== undefined ? props.contentData[child.props.id] : child.props.value,
},
child.props.children
? (
<RecursiveWrapper
ids={props.ids}
onChange={props.onChange}
contentData={props.contentData}
>
{child.props.children}
</RecursiveWrapper>
)
: undefined
)
}
return child
}
)
return (
<React.Fragment>
{wrappedChildren}
</React.Fragment>
)
}
const Display = ({ index, fragments, fragmentIDs }) => {
const [contentData, setContentData] = useState(fragmentIDs.reduce((acc, id) => ({
...acc, [id]: '' }), {}));
const onChange = e => {
setContentData({
...contentData,
[e.target.id]: e.target.value
})
};
const newChildren = fragments.map(fragment => <RecursiveWrapper onChange={onChange} ids={fragmentIDs} contentData={contentData}>{fragment}</RecursiveWrapper>);
return newChildren[index];
};
This code outlines the general idea. Here we are treating fragments like it is an array of nodes, not a function that produces them. Then we are taking fragments and mapping over it, and replacing the old nodes with nodes containing our desired props. Then we render them as planned.

Why my setState doesn't accept this object?

I'm working in a to-do list on React and when method deleteItemis called, I get Uncaught Error: Objects are not valid as a React child (found: object with keys {text}) but I don't understand the reason
class Form extends Component{
constructor(props){
super(props);
this.state = {
task: "",
list: []
}
this.saveItem = this.saveItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
saveItem(event){
let state = this.state;
event.preventDefault();
if(this._taskInput.value !== ""){
state.list.push({ text: this._taskInput.value });
this.setState(state);
}
this.setState({task: ""});
}
deleteItem(index){
const {list} = this.state;
this.setState({
list: list.filter((item,itemCurrent) => {
// console.log(item.key, index);
return (itemCurrent !== index);
}),
});
}
render(){
return(
<Fragment>
<form onSubmit={this.saveItem}>
<input value= {this.state.task} id="to-do"
onChange={(e) => this.setState({task: e.target.value})}
ref={event => this._taskInput = event} type="text" className="validate"/>
<label htmlFor="to-do">My tasks</label>
</div>
<button type="submit" name="action">Submit
</button>
</div>
{{this.state.task}}
{this.state.list}
</form>
</div>
<Table list= {this.state.list} deleteItem = {this.deleteItem}/>
first of all there's a big conceptual error here:
if (this._tarefaInput.value !== ""){
state.list.push({ text: this._taskInput.value });
this.setState(state);
}
you are directly editing the state with that push function, you should never do this in react as it will lead to unexpected consequences, this is how you should update the state:
if (this._tarefaInput.value !== ""){
//use the spread operator (...) to create a copy of the list in the state
const newList = [... state.list];
// push the new element tot the new list
newList.push({ text: this._taskInput.value });
// update the state
this.setState({list: newList});
}
Now the error you are getting is likely happening because somewhere in your code (possibly inside <Table/>) you are trying to print each element of the list array as a react component. You haven't shared the part where the list is rendered, but I'm guessing you are doing something like this:
//somewhere inside a render:
{
list.map(Element => <Element />);
}
//Proper way of doing it
{
list.map(element => <p>{element.text}</p>);
}
I can try to help more if you share more of your code and the entitre log with the the error description (with file and line number)
The issue is this line inside your render: {this.state.list}. You can render an array, but you can't render an object. The solution is to map over the array and output some JSX such as the following. Let's assume you have a list of objects with a name and id property:
{this.state.list.map(item => (<div key={item.id}>{item.id}</div>))}

Filter Array of Objects based on input field in React

Got in a rather troublesome situation
I have an array of objects
[
{
"title":"placeholder",
"text":"placeholder"
},
{
"title":"test",
"text":"placeholder"
},
{
"title":"javascript",
"text":"placeholder"
}
]
I am displaying them in a div,but thats not important
I got an input field which users should type in title's and as they type the array should only show matching object.
Inputing java would show the javascript titled object
I need to somehow change the array so it doesnt display anything but the entered title and if the input is empty shows the whole array
I am using React but i can only use hooks
So i copy the json
var [arrayOfObjects, setArray] = useState(Json)
the Json is imported from a local file
arrayOfNotes is the array that i need to change
pointing out so its easier to understand
ty in advance
The array filter method is what you're looking for.
Here's what your component might looks like.
const List = ({ data }) => {
const [value, setValue] = useState('')
return (
<div>
<input
type="text"
value={value}
onChange={e => setValue(e.target.value)}
/>
{data
.filter(item => {
if (!value) return true
if (item.title.includes(value) || item.text.includes(value)) {
return true
}
})
.map(item => (
<div>
<h1>{item.title}</h1>
<p>{item.text}</p>
</div>
))
}
</div>
)
}
And you pass your json data to that component
<List data={Json} />
Here's a working example of the above code
You didn't share your component so I'll assume you know how to get the input value and call the corresponding variable input, besides you have your original array, from your example I judge it is called Json.
Then you can filter your value as follows:
const [arrayOfObjects, setArray] = useState(Json);
const filteredArray = input ?
originalArray.filter(item => item.title.includes(input) :
originalArray;
So that later you can render the filteredArray as follows:
<ul>
{filteredArray.map(item => (<li>{item.title}</li>))}
</ul>

How to modify input value correctly and modify array value at the same time?

I render the values that I have inside an object and an input next to each of them with that object index item as value. When I try to use the onChange prop, I cannot modify neither the rendered value nor the input value. How can I do this changing the state of the element in the array?
I want the Toto value to be modified as I change the input value
My object players looks like this:
[
{
"name": "Toto";
},
{
"name": "Lolz",
}
]
Here, I try to render my table:
modifyItem = (event, index) => {
this.state.players[index].name = event.target.value
//my problem is clearly here
}
render() {
const playersList = [...new Array(this.state.players.length)].map((it, index) => {
return (
<tr key={index}>
<td>{this.state.players[index].name}</td>
<input type="text" value={this.state.players[index].name} onChange={this.modifyItem}/>
</td>
</tr>
)
})
return () {
<div>
{playersList}
</div>
}
What I want to do is:
In each element of the table AND input ( for example the first one Toto), I want to modify this value in the input and consecutively in the table. I can't seem to find the correct answer.
I've created a codesandbox example for you here:
https://codesandbox.io/s/vnkoxop6z3
You need to store your values in state, and when you modify each item you need to know what the new value is via the onChange callback, and the index of that current value.
From here you can duplicate the array by spreading it into a new one, access that particular index item on the new array, and update the name with the new value.
Once you have done this. setState with the new array.
class Component extends React.Component {
constructor() {
super();
this.state = {
values: [
{
name: "Toto"
},
{
name: "Lolz"
}
]
};
}
modifyItem(e, index) {
const newValue = e.target.value;
const values = [...this.state.values];
values[index].name = newValue;
this.setState({
values
});
}
render() {
return this.state.values.map((value, index) => {
return (
<Fragment>
<label>{value.name}</label>
<input
type="text"
value={value.name}
onChange={e => this.modifyItem(e, index)}
/>
</Fragment>
);
});
}
}

In react, how to use condition to create a custom search functionality?

I have a state of projects which is of type an array that contains the following data.
Using this data, I need to filter the user's result based on search input that matches with company_name, project_name, project_description, release_name, and release's description
this.state = {
projects: [],
searchfield: ''
};
onSearchChange = (event) => {
this.setState({ searchfield: event.target.value })
}
<input type="text" placeholder="Search" className="form-control search-input" onChange={this.onSearchChange.bind(this)} />
render() {
return(
const filterProjects = this.state.projects.filter(data => {
// filter projects based on project's name
return data.project_name.toLowerCase().includes(this.state.searchfield.toLowerCase());
})
)
}
At the moment, I am only able to filter the result according to keywords that matches with project's name.
Hw can I use other properties or add a condition to filter data based on other properties as well?
If you want to see if a term exists in any of those fields you can use Array#some()
const filterFields = ['company_name', 'project_name', 'project_description', 'release_name', 'descrip'];
const { projects, searchfield) = this.state;
const term = searchfield.toLowerCase();
const filterProjects = projects.filter(data => {
return filterFields.some(field => data[field].toLowerCase().includes(term))
});

Categories

Resources