How to update an array by index using the useState hook? - javascript

I have a select component that I would like to use to update the values of an array of objects based on the index.
I use the hook like this:
const [areas, setAreas] = useState(product.areas);
This returns the following for "areas":
[
0: {de: "Getraenke", en: "Drinks"},
1: {de: "Snacks", en: "Snacks"}
]
My select component looks like this:
{areas?.map((area: {de: string, en: string}, index: number) => (
<Wrapper key={index}>
<Select
label="Area"
name="product-area"
value={area[lang] || ''}
onChange={e => setArea([...area, { [lang]: e.target.value }])}
>
{areaOptions.map(option => (
<option value={option} key={option}>
{option}
</option>
))}
</Select>
</InputWrapper>
))}
In this way I unfortunately get the following for "area" after selecting an option (here "Sweets"):
[
0: {de: "Getraenke", en: "Drinks"},
1: {de: "Snacks", en: "Snacks"}
2: {en: "Sweets" }
]
So the first object of the array is not updated as intended, but a further object is appended, which unfortunately also lacks the "de" language.
My goal is to update the first object (or the object based on the index of the select component) of the array so that instead of:
0: {de: "Getraenke", en: "Drinks"}
..the updated object looks like this:
0: {de: "Suessigkeiten", en: "Sweets"}
The objects within the array should therefore be updated based on the index of the current select component and also take into account the selected language (default "en").

The easiest way to do this is to clone the array, update the specific array item by index and then replace the old array with it using useState, like this.
const updateArea = (e, lang, index) => {
const updatedAreas = [...areas];
updatedArea[index][lang] = e.target.value;
setAreas(updatedAreas);
}
...
{areas?.map((area: {de: string, en: string}, index: number) => (
<Wrapper key={index}>
<Select
label="Area"
name="product-area"
value={area[lang] || ''}
onChange={e => updateArea(e, lang, index)}
>
{areaOptions.map(option => (
<option value={option} key={option}>
{option}
</option>
))}
</Select>
</InputWrapper>
))}

Let's solve this step by step
1. A further object is appended
That's because you're telling it to do so :)
onChange={e => setArea([...area, { [lang]: e.target.value }])}
This means, copy the entire array ...area and add a new object at the end { [lang]: e.target.value }
Another mistake you're making is not using a callback function, proper way to access current ...area should be this:
onChange={e => setArea((currentArea) => [...currentArea, { [lang]: e.target.value }]}
This way you always have the most up-to-date version of area.
Now, to update the specific object by the index, you would do the following:
onChange={e => setArea((currentArea) => {
const { value } = e.target;
const newArea = currentArea.map((singleArea, areaIndex) => {
if (areaIndex === index) {
return { [lang]: value }
}
return singleArea;
});
return newArea;
}}
2. Lacks the "de" language
Again, you're explicitly only adding one language to it :)
{ [lang]: value } // { en: 'something' }
Not sure how to fix this one for you, but at least you can understand why its wrong, It should be something like (just a concept, this won't work):
{ [langDe]: value, [langEn]: value } // { de: 'Guten Tag', en: 'something' }

Related

Error in displaying where there's an a array inside of the object

this is what the data shows inside the console:
I tried displaying it with these but it failed
{Object.entries(value).map(([key, value]) => {
return (
<p key={key}>
<li>
{key}
{value}
{console.log(value.id)} //this will show as undefined
</li>
</p>
);
})}
{value} will show this error :
Objects are not valid as a React child (found: object with keys {color, quantity}). If you meant to render a collection of children, use an array instead.
{value.id} or the {value.name} will show as undefined
With the map, it will say that value.map is not a function
{value.map((value, key) => (
<>
<div>{value.id}</div>
<div>{value.name}</div>
</>
))}
codesandbox: https://codesandbox.io/s/display-the-manipulated-data-dy204r
Your object has a complex structure, and in order to iterate, you need to check if one of the items is Array using Array.isArray(), if yes, then loop them and use, else use the properties directly
Below is the working code of the mock of your object and iteration. I have just logged the values, you can use them in any way you want
let myObj = {
s: [{
color: 'a',
q: '8'
}, {
color: 'b',
q: '2'
}],
name: 'Anne',
id : 18
}
Object.keys(myObj).forEach(function(key) {
if (Array.isArray(myObj[key])) {
myObj[key].forEach(function (item, index) {
console.log(item.color);
console.log(item.q);
});
}
else
console.log(myObj[key])
});
You can do something like this
{Object.entries(value).map((v, key) => {
return (
<p key={key}>
<li>
{key}
{v.name}
{console.log(v.id)} //this will show as undefined
</li>
</p>
);
})}

React, visualizing and editing a JSON object. Populating data on inputs

I have this kind of objects
{
asignaturaId: 1,
content: {
[question_var]: {
elements: {
text: 'text1',
image: 'img1.jpg'
}
}
[question_var]: {
text: 'text2',
image: 'image2.jpg'
}
}
}
the thing that varies is the [question_var] part and how many objects does content key includes.
I want to be able to dynamically write out all the data on inputs and being able to replace the needed data.
so far I got this:
[...]
<FormControl>
<InputLabel htmlFor="modulo">Asignatura ID:</InputLabel>
<Input id="asignaturaId" name="asignaturaId" value={arrayData[0]} onChange={(e) => setAsignaturaId(e.target.value)} />
</FormControl>
{arrayData.map(pregunta => {
console.log(pregunta)
return (
<>
<FormControl>
<InputLabel htmlFor="texto">Texto:</InputLabel>
<Input id="texto" name="texto" onLoad={() => setTexto(pregunta[1].elements.text)} value={pregunta[1].elements} onChange={(e) => setText(e.target.value)} />
</FormControl>
[...]
what I omited was, I took the object and did Object.entries(myObject) and assigned it to the arrayData array. asignaturaId will be just one key on every object I receive, so I can easily pass that value and change it on an input. The problem is the content, as it is dynamic and change too much, I find it hard to use useEffect() hook to populate my input with the data I receive and manipulating it in the same input without changing the other values with the same keys or straight foward, not being able to edit them at all.
I have 4 days in this problem. please help!
Edit: I am sorry about my english, if I didn't make myself clear, I want to overwrite the object, keeping the data the user doesn't update in the front end.
Declear your json array first in your React hook Component and make sure each object should have unique key
const [yourJson,setYourJson] = useState({
asignaturaId: 1,
content: {
[question_var1]: {
elements: {
text: 'text1',
image: 'img1.jpg'
}
},
[question_var2]: {
text: 'text2',
image: 'image2.jpg'
}
}
});
Add Setter to add new object in your json content
const addNewData = (text, imgSrc) =>{
let newIndex = Object.keys(yourJson.content).length + 1;
let newJson = yourJson;
newJson.content[`question_var${newIndex}`] = {
'text': text,
'image': imgSrc
}
setYourJson({...newJson})
}
And in render callback use this mapping
<FormControl>
{Object.keys(yourJson.content).map(key => (
<InputLabel htmlFor="texto">{yourJson.content[key].text}</InputLabel>
// <Input id="texto" name="texto" ....
))}
</FormControl>

Update Select Option list based on other Select field selection ant design

<Form
layout="vertical"
size="medium"
className="test-form"
requiredMark={false}
onFinish={onFinish}
>
<Form.Item
name="companyId"
label="Company/Customer"
rules={[{ required: true, message: "Please select Company!"}]}
>
<Select
onChange={this.handleSelectCompanyOnchange}
style={{ width: "50%" }}
name="companyId"
>
{users.map((user, index) => {
return (
<Option key={index} value={user.companyID}>
{user.companyName}
</Option>
);
})}
</Select>
</Form.Item>
<Form.Item
label="Products"
name="products"
rules={[{ required: true, message: "Please select Products!"}]}
>
<Select mode="multiple" allowClear style={{ width: "70%" }}>
{products.map((product, index) => {
if (this.state.companyId == product.companyId) {
return (
<Option key={index} value={product.id}>
{product.productName}
</Option>
);
}
})}
</Select>
</Form.Item>
</Form>
I am trying to achieve Options in Products Select element changes according to the Company Select onChange selection.
I have specified onChange in Select and calling this.handleSelectCompanyOnchange. In which I get selected companyId.
In this.state.companyId I had set companyId manually which I will remove.
I am really new to ant design and not able to figure out how to update the Products list once Company is selected.
Here, users and products are json as below.
users:
[{
companyID: 2
companyName: "TEST1"
},{
companyID: 7
companyName: "TEST2"
}]
products:
[{
companyId: 2
id: 1
productName: "TESTProduct1"
},{
companyId: 7
productName: "TESTProduct2"
id: 2
},{
companyId: 7
id: 3
productName: "TESTProduct3"
},{
companyId: 7
id: 4
productName: "TESTProduct4"
}]
However, I have tried getValueFromEvent but not able to achieve this. I am using Ant design Form and Select for this. Also I did referred to https://github.com/ant-design/ant-design/issues/4862 and how to get field value on change for FormItem in antd
Here is what you need to achieve it.
Use onValuesChange prop of the Form. This is the best place to perform setState when it comes to antd Form field changes, not on Select or Input onChange.
<Form onValuesChange={handleFormValuesChange}>
...
</Form>
A form instance (hook). This is optional in your case, but this is useful on setting and getting form values. See more here about it.
const [form] = Form.useForm();
<Form form={form} onValuesChange={handleFormValuesChange}>
...
</Form>
This is the product options render looks like, a combination of map and filter where selectedCompanyId comes from state. Take note that don't use index as key if the fixed length of the list is unknown, the react will confuse on this and you will get some logical error. Use some unique id.
<Form.Item label="Products" name="product">
<Select>
{products
.filter((product) => product.companyId === selectedCompanyId)
.map((product) => (
<Option key={product.id} value={product.id}>
{product.productName}
</Option>
))}
</Select>
</Form.Item>
And here is the handleFormValuesChange
const handleFormValuesChange = (changedValues) => {
const formFieldName = Object.keys(changedValues)[0];
if (formFieldName === "company") {
setSelectedCompanyId(changedValues[formFieldName]); // perform setState here
form.setFieldsValue({product: undefined}) //reset product selection
}
};
Here is the complete working code in react hooks:
the idea here is that you only need to watch the value change in the state.
For example, your select should "watch" a value of state and then you can easily update the state via its own setState method of React. Inside of an onChange event, you can simply just do something with our state.
<Select
value={state.productId}
onChange={e => {// Do something}}
>
{...}
<Select/>
Below is my example code how did I update the day every time I reselect week selection. Hopefully that this can help you.
import { Divider, Select } from 'antd';
import React, { useState } from 'react';
export function Example(): JSX.Element {
const [state, setState] = useState<{week: number, day: number}>({
day: 1,
week: 1
});
const weeks = [1,2,3,4];
const days = [1,2,3,4,5];
return <>
<Select
value={state.week}
onChange={(value) => setState({ week: value, day: 1})}
>
{
weeks.map(week => {
return <Select.Option
key={week}
value={week}
>
{week}
</Select.Option>;
})
}
</Select>
<Divider/>
<Select
value={state.day}
onChange={(value) => setState({...state, day: value})}
>
{
days.map(day => {
return <Select.Option
key={day}
value={day}
>
{day}
</Select.Option>;
})
}
</Select>
</>;
}
Lets say you want to update your option list based on data you get from backend;
note:categories is an array object. From this array you take out label and value for each option, see below:
const newOptions = categories.map((item, index) => {
return {
label: item.name,
value: item._id,
};
});
Then use newOptions inside your form like that:
<Form.Item label="anyName" name="anyName">
<Select style={{ width: 220 }} onChange={ handleChange } options={ newOptions } />
</Form.Item>

I want this select to have an object value, this works but I can't show the title of the selected object, does anyone know why?

I want this select to have an object value, this works but I can't show the title of the selected object, does anyone know why?
my code is:
const [categoria, setCategoria] = useState("");
const litaCategorias = [
{
"id": 2,
"titulo": "test",
"descripcion": "descripcion",
}];
const handleChangeCategoria = (value) => {
setCategoria(value)
console.log(value)
}
<Select
fullWidth
variant="outlined"
value={categoria.titulo}
onChange={e => handleChangeCategoria(e.target.value)}
labelWidth={0}
placeholder={"Seleccione un destino"}
>
{litaCategorias.map(categoria => {
return (
<option
value={categoria}
key={categoria.titulo}
>
{categoria.titulo}
</option>
);
})}
</Select>
I have double checked the Select component in material-ui. It provides props api value with description:
If the value is an object it must have reference equality with the option in order to be selected. If the value is not an object, the string representation must match with the string representation of the option in order to be selected.
You you should memorize you array litaCategorias. to make reference equality.
Should use useMemo to memorized array.
const litaCategorias = useMemo(() => [ { "id": 2, "titulo": "test", "descripcion": "descripcion", }], []);
I think it's because you're setting the param in your callback in the .map function to categoria which is defined in the state and is initialized to an empty string. This line here - {litaCategorias.map(categoria => {. The option value in the select dropdown is not getting the correct object because of this.
Try something like this instead:
import React, { useState } from "react";
export default function App() {
const [categoria, setCategoria] = useState("");
const litaCategorias = [
{ id: 1, titulo: "test", descripcion: "descripcion" },
{ id: 2, titulo: "test2", descripcion: "descripcion2" }
];
const handleChangeCategoria = (value) => {
setCategoria(value);
console.log(value);
};
return (
<select
value={categoria.titulo}
onChange={(e) => handleChangeCategoria(e.target.value)}
>
{litaCategorias.map((cat) => {
return (
<option value={cat.titulo} key={cat.id}>
{cat.titulo}
</option>
);
})}
</select>
);
}
https://codesandbox.io/s/wild-mountain-0r4p6?file=/src/App.js

show dynamic select items mapping over array of objects

I have an array that looks like this:
const teamsAndPlayers = [
{
team: 'Liverpool',
players: ['Salah', 'Henderson']
},
{
team: 'Man Utd',
players: ['Rashford', 'De Gea']
},
{
team: 'Chelsea',
players: ['Hazard', 'Willian']
}
];
I have 2 select boxes, the second of which is dynamic based on what the user selects. The issue I am having is that I am not sure the best way to find the related array based on the user choice. I know I could use find and other methods, but is there anyway I can do it all in the map function in the return statement?
My code looks like the following:
const Menu = () => {
const [selectedTeam, setSelectedTeam] = useState(null);
return (
<div>
<select
onChange={e => setSelectedTeam(e.target.value)}
id="team"
name="team"
>
{teamsAndPlayers(item => (
<option key={item.team} value={item.team}>
{item.team}
</option>
))}
<select id="players" name="players">
{teamsAndPlayers(item => (
// how can I get the related players in here
// based on first select choice?
))}
</div>
)
}
I would use object instead of array to define the input for the selects (if you really want to avoid using find).
So the teamsAndPlayers would look like this:
const teamsAndPlayers = {
liverpool: {
name: 'Liverpool',
players: ['Salah', 'Henderson']
},
manUtd: {
name: 'Man Utd',
players: ['Rashford', 'De Gea']
},
chelsea: {
name: 'Chelsea',
players: ['Hazard', 'Willian']
}
};
Then the options inside the fisrt select would look like this:
{Object.keys(teamsAndPlayers).map(key => (
<option key={key} value={key}>
{teamsAndPlayers[key].name}
</option>
))}
Then the options inside the second select would look like this:
{teamsAndPlayers[selectedTeam].players.map(player => (
<option key={player} value={player}>
{player}
</option>
))}
Simply combine find and map (if you don't want to change your data Array structure):
const playersByTeam = teamsAndPlayers.find(({ team }) => team === selectedTeam).players
<select id="players" name="players">
{ playersByTeam.map(player => <option key={player>{ player }</option>) }
</select>

Categories

Resources