I'm trying to fill an object with values that I'm getting from an array of objects but it's not working as expected.
This is a simplified code example
https://codesandbox.io/s/crazy-nobel-c7xdb?file=/src/App.js
import "./styles.css";
import React, { useEffect, useState } from "react";
export default function App() {
const [fieldsValues, setFieldsValues] = useState({});
const items = [{ value: "a" }, { value: "b" }, { value: "c" }];
useEffect(() => {
items.map((item, index) => {
return setFieldsValues({
...fieldsValues,
[index]: item.value
});
});
}, []);
return (
<div className="App">
<h2> {` fieldsValues = ${JSON.stringify(fieldsValues)}`} </h2>
</div>
);
}
I want the fieldsValues to return this:
{
0: "a",
1: "b",
2: "c"
}
What I'm getting now:
fieldsValues = {"2":"c"}
You fix it by doing this
useEffect(() => {
items.map((item, index) => {
return setFieldsValues((prev) => ({
...prev,
[index]: item.value,
}));
});
}, []);
Better way of doing this is
useEffect(() => {
const data = items.reduce(
(prev, item, index) => ({ ...prev, [index]: item.value }),
{}
);
setFieldsValues((prev) => ({ ...prev, ...data }));
}, []);
To create the object map the array to [index, value] pairs, and convert to an object with Object.fromEntries():
const items = [{ value: "a" }, { value: "b" }, { value: "c" }];
const result = Object.fromEntries(items.map(({ value }, index) => [index, value]))
console.log(result)
However, the way you are using the array, and then need to set the state doesn't actually makes sense in the react context.
If the array is a prop, you should add it to useEffect as a dependency:
const arrToObj = items => Object.fromEntries(items.map(({ value }, index) => [index, value]))
export default function App({ items }) {
const [fieldsValues, setFieldsValues] = useState({});
useEffect(() => {
setState(() => arrToObj(items))
}, [items]);
...
If it's a static array, set it as the initial value of setState:
const arrToObj = items => Object.fromEntries(items.map(({ value }, index) => [index, value]))
const items = [{ value: "a" }, { value: "b" }, { value: "c" }];
export default function App({ items }) {
const [fieldsValues, setFieldsValues] = useState(() => arrToObj(items));
...
By your way It would be like this
useEffect(() => {
let test={}
items.map((item, index) => {
return setFieldsValues((prev)=>{
return {
...prev,
[index]: item.value
}
});
});
}, []);
Related
Here is my array. How can I remove duplicates in this type of structure? When I map over arr I get the values of each array nested in each object. And I want to filter the duplicated values.
current output: bye hello hello
The expected output should be: bye hello
[
{
arr: ['']
val: "string"
}
{
arr: ['bye', 'hello']
val: "string"
}
{
arr: ['hello']
val: "string"
}
]
myArray.map(item => item.arr.map((el, index) =>
<p key={index}>{el}</p>
))
I hope it will help you:
const filteredArray = useMemo(() => {
const used = []
return myArray.map(sub => {
return { ...sub, arr:sub.arr.map(el
=> {
if(used.includes(el) return null
used.push(el)
return el
}}
})
}, deps)
And then in JSX:
filteredArray.map(() => ...)
You could simply manage an array to filter what you want to display :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const data = [
{
arr: [''],
val: 'string',
},
{
arr: ['bye', 'hello'],
val: 'string',
},
{
arr: ['hello'],
val: 'string',
},
];
const buildData = () => {
const passedValues = [];
return data.map((item) => {
return item.arr.map((el) => {
if (!passedValues.includes(el)) {
passedValues.push(el);
return <div>{el}</div>;
}
});
});
};
return <div>{buildData()}</div>;
};
render(<App />, document.getElementById('root'));
Here is the repro on StackBlitz.
All of these answer are good...I think #vitaliyirtlach has the best as its the closest to React.
I'll just put it out there that you can also loop through myArray, remove the duplicates with Set and then place them in an array that you can loop over:
const myArray = [
{
arr: [''],
val: "string"
},
{
arr: ['bye', 'hello'],
val: "string"
},
{
arr: ['hello'],
val: "string"
}
]
const removeDupes = () => {
const newArr = []
myArray.map(item => item.arr.map((el, index) =>
newArr.push(el)
))
return [...new Set(newArr)]
}
const loopArray = removeDupes();
console.log(loopArray)// logs ["","bye","hello"]
loopArray.map((el, index) =>
<p key={index}>{el}</p>
))
I have a provider that receives data prop, puts it in a state. Also, there are a few methods to manipulate that state.
I pass the state and the data prop to consumers, but every time I change the state, there is no difference between the prop and the state. I want to be able to see what changed so I could update that value.
import { createContext, useContext, useEffect, useState } from "react";
const TableContext = createContext({
data: [],
headings: [],
onChangeCellContent: () => {},
});
const TableProvider = ({ data, headings, children }) => {
const [tableData, setData] = useState(data);
const [tableHeadings, setHeadings] = useState(headings);
useEffect(() => {
setData((previousData) => {
return data.length !== previousData.length ? data : previousData;
});
}, [data]);
const onChangeHeadingCell = ({ key, value }) => {
setHeadings((oldHeadings) =>
oldHeadings.map((heading) => {
if (heading.key === key) {
heading.title = value;
}
return heading;
})
);
};
const onChangeCellContent = ({ rowId, cellKey, value }) => {
setData((previousData) =>
[...previousData].map((row) => {
if (row.id === rowId) {
row[cellKey] = value;
return row;
}
return row;
})
);
};
const onAddNewRow = (rowData) => {
setData((oldData) => [...oldData, rowData]);
};
return (
<TableContext.Provider
value={{
tableData,
data,
onChangeCellContent,
onChangeHeadingCell,
onAddNewRow,
headings: tableHeadings,
}}
>
{children}
</TableContext.Provider>
);
};
export default TableProvider;
export const useTable = () => {
const context = useContext(TableContext);
if (context === "undefined") {
throw Error("Table provider missing");
}
return context;
};
Here is the change handler, it works, but it also changes the original data:
const Row = ({ data: row}) => {
const { onChangeCellContent, headings, data } = useTable();
...
// GIVES ME THE SAME VALUE WHEN I TRIGGER ONCHANGE
console.log(row.value, data.find((s) => s.id === row.id).value);
return <tr><td><select
className="w-full h-full focus:outline-none"
style={{
backgroundColor: "inherit",
}}
value={row.value}
onChange={(e) =>
onChangeCellContent({
rowId: row.id,
cellKey: "value",
value: e.target.value,
})
}
>...</select></td></tr>
I have from with input type number, which has an pretty simple onChange function when entered a value the last number is missed out
Example:
If 99 is type in the input box, only 9 is recorded, and when 2523 is typed in the input box 252 is been recorded, the last number is been skipped
Here is the sandbox link
inside of onChange function
const handleRequestingCost = (e, index) => {
setCostsFormValues({
...costsFormValues,
[e.target.name]: [e.target.value]
});
for (const key in costsFormValues) {
const mappedData = {
[key]: costsFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
...
What I am missing here? Or if is this the bad way to do ?
setState is async. So you are reading costsFormValues before it is updated
import "./styles.css";
import { Button, Form } from "react-bootstrap";
import React, { useState } from "react";
export default function App() {
const [costsFormValues, setCostsFormValues] = useState({});
const [row, setRow] = useState([
{
index: 5399,
name: "user name",
quantity: "1000",
remainingQty: 2000,
requestingCost: [],
budgetedCost: [
{
key: "Labour Cost",
value: 176
},
{
key: "Material Cost",
value: 890
}
],
amountForQuantity: [
{
key: "Labour Cost",
value: 150
},
{
key: "Material Cost",
value: 570
}
]
}
]);
const handleRequestingCost = (e, index) => {
const mappedDataArray = [];
setCostsFormValues({
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
});
const updatedFormValues = { // added this local variable which holds latest value update
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
};
for (const key in updatedFormValues) {
// loop each array and merge into one object
const mappedData = {
[key]: updatedFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
console.log(updatedFormValues);
};
Please try to make changes to a local variable rather than setting the state object directly as it takes about 300-500ms to update after the setState() call; as it creates queues for performance optimization.
Please refer to the code snippet:
const handleRequestingCost = (e, index) => {
const mappedDataArray = [];
let TempValue = {};
TempValue = {
...TempValue,
[e.target.name]: [e.target.value]
};
for (const key in TempValue) {
// loop each array and merge into one object
const mappedData = {
[key]: TempValue[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
console.log(TempValue);
};
As setState is async, you should use the value just after mutate it, that why we have useEffect hook to do it instead
const handleRequestingCost = (e, index) => {
const mappedDataArray = [];
setCostsFormValues({
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
});
for (const key in costsFormValues) {
// loop each array and merge into one object
const mappedData = {
[key]: costsFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
};
useEffect(() => {
console.log(costsFormValues);
}, [costsFormValues]);
The console will show the right value, but be careful with my snippet, your handleRequestCost do some stuff with costsFormValues you should pass it to useEffect as well
const handleRequestingCost = (e, index) => {
setCostsFormValues({
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
});
};
useEffect(() => {
const mappedDataArray = [];
console.log(costsFormValues);
for (const key in costsFormValues) {
// loop each array and merge into one object
const mappedData = {
[key]: costsFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
}, [costsFormValues]);
Something like this, but there is the index that I didnt get from you logic yet
I am getting an array or an object from the backend what my task is to set the first index mean [0] to set in default value but when I set I am getting undefined you can see in code userL taking my array of an object but when I print userle it's showing label: undefined value: undefined or when I print userL I'm getting my array of object list
const [userL, setUserL] = useState([]);
useEffect(() => {
axios
.get(`${DJANGO_SERVER_ADDRESS}/auth/analyst/`)
.then((res) => {
setUserL(res.data);
})
.then(
(result) => {
},
(error) => {
}
);
}, []);
console.log("ena1", userL);
const [userle, setUserle] = useState(
{ value: userL[0].username,
label: userL[0].username,
});
console.log("nnnnnnnnnuuuu",userle)
console.log('nnnnnnnnnnn',userL[0])
const handleSelectChangeL = (object, action) => {
setIndex(userL[object.id]);
setUserlevel(null);
console.log("select check", object.label, action.name, index);
let name = action.name;
let value = object.value;
setFormData((prevFormData) => ({
...prevFormData,
[name]: value,
}));
};
<Col lg={2}>
<label for="user">
<header>User</header>
</label>
<Select
options={userL.map((data, index) => ({
value: data.username,
label: data.username,
id: index,
}))}
styles={styles2}
value={userle}
name="user"
onChange={handleSelectChangeL}
placeholder="User"
theme={(theme) => ({
...theme,
colors: {
...theme.colors,
text: "black",
primary25: "#d6fdf7",
primary: "#0bb7a7",
primary50: "#d6fdf7",
},
})}
></Select>
</Col>
If you want to set the state of the userle from the first element of the data, do it this way.
const [userle, setUserle] = useState()
const [userL, setUserL] = useState([]);
useEffect(() => {
axios
.get(`${DJANGO_SERVER_ADDRESS}/auth/analyst/`)
.then((res) => {
setUserL(res.data);
setUserle(res.data[0]?.username);
})
.then(
(result) => {
},
(error) => {
}
);
}, []);
EDIT
To also update the userle state onChange of the dropdown list, add setUserle() under your handleSelectChangeL function.
JS
const handleSelectChangeL = (object, action) => {
setIndex(userL[object.id]);
setUserlevel(null);
console.log("select check", object.label, action.name, index);
let name = action.name;
let value = object.value;
setUserle(value); // Add this line
setFormData((prevFormData) => ({
...prevFormData,
[name]: value,
}));
};
if you using useState, You cannot get expected result of updated state in same function, but if want to print the result, you must write useEffect with dependency userle state or maybel you can console it above "return" statement in functional Commponent
I have the current state as:
const [data, setData] = useState([
{ id: 1, name: "One", isChecked: false },
{ id: 2, name: "Two", isChecked: true },
{ id: 3, name: "Three", isChecked: false }
]);
I map through the state and display the data in a div and call a onClicked function to toggle the isChecked value on click:
const clickData = index => {
const newDatas = [...data];
newDatas[index].isChecked = !newDatas[index].isChecked;
setData(newDatas);
const newSelected = [...selected];
const temp = datas.filter(isChecked==true) // incomplete code, struggling here.
const temp = datas.isChecked ?
};
I have another empty state called clicked:
const[clicked, setClicked] = setState([]). I want to add all the objected whose isChecked is true from the datas array to this array. How can I do this?
I just add checkBox & onChange event instead of using div & onClick event for your understanding
import React, { useState, useEffect } from "react";
import "./style.css";
export default function App() {
const [data, setData] = useState([
{ id: 1, name: "One", isChecked: false },
{ id: 2, name: "Two", isChecked: true },
{ id: 3, name: "Three", isChecked: false }
]);
const [clicked, setClicked] = useState([]);
const clickData = index => {
let tempData = data.map(res => {
if (res.id !== index) {
return res;
}
res.isChecked = !res.isChecked;
return res;
});
setClicked(tempData.filter(res => res.isChecked));
};
useEffect(() => {
setClicked(data.filter(res => res.isChecked));
}, []);
return (
<div>
{data.map((res, i) => (
<div key={i}>
<input
type="checkbox"
checked={res.isChecked}
key={i}
onChange={() => {
clickData(res.id);
}}
/>
<label>{res.name}</label>
</div>
))}
{clicked.map(({ name }, i) => (
<p key={i}>{name}</p>
))}
</div>
);
}
https://stackblitz.com/edit/react-y4fdzm?file=src/App.js
Supposing you're iterating through your data in a similar fashion:
{data.map((obj, index) => <div key={index} onClick={handleClick}>{obj.name}</div>}
You can add a data attribute where you assign the checked value for that element, so something like this:
{data.map((obj, index) => <div key={index} data-checked={obj.isChecked} data-index={index} onClick={handleClick}>{obj.name}</div>}
From this, you can now update your isClicked state when the handleClick function gets called, as such:
const handleClick = (event) => {
event.preventDefault()
const checked = event.target.getAttribute("data-checked")
const index = event.target.getAttribute("data-index")
// everytime one of the elements get clicked, it gets added to isClicked array state if true
If (checked) {
let tempArr = [ ...isClicked ]
tempArr[index] = checked
setClicked(tempArr)
}
}
That will let you add the items to your array one by one whenever they get clicked, but if you want all your truthy values to be added in a single click, then you simply need to write your handleClick as followed:
const handleClick = (event) => {
event.preventDefault()
// filter data objects selecting only the ones with isChecked property on true
setClicked(data.filter(obj => obj.isChecked))
}
My apologies in case the indentation is a bit off as I've been typing from the phone. Hope this helps!