create a new array, removing duplicates and setting state with React - javascript

I have a MenuOptions component that I pass an options prop to. Options is a large array of objects. Each object has a nested array called 'services' inside services is an object with a key 'serviceType' which is the only value I want. I want to take all those values, push them into a new array and remove any duplicates if there are any, and then map through that new array and display each item in an 'option' html tag.
here is my createArray function:
const createNewArray = () => {
let optionsArr = []
let uniqueArr;
options.map((option) => {
option.services.map((service) => optionsArr.push(service.serviceType))
})
uniqueArr = [...new Set(optionsArr)];
return uniqueArr;
}
uniqArr seems to be holding what I want but now I want to set this to a piece of global state. Trying something like this does not work. array seems to still be set as null
const [array, setArray] = useState(null)
useEffect(() => {
setArray(createNewArray())
}, [])
any solutions? Thanks

1) You should add your array state initial value as an empty array:
const [array, setArray] = useState([]);
Live Demo
2) You can simplify the creating of a new array as:
const createNewArray = () => [
...new Set(options.flatMap((o) => o.services.map((obj) => obj.serviceType)))
];
3) set array state in useEffect as:
useEffect(() => {
setArray(createNewArray());
}, []);

From your description is this your data?
const options = [{
services: [
{
serviceType: 'serviceType',
}
],
},{
services: [
{
serviceType: 'serviceType',
}
],
},{
services: [
{
serviceType: 'serviceType',
}
],
},
{
services: [
{
serviceType: 'serviceType',
}
],
}]
here is the solution
const uniq = (a) => [...new Set(a)];
const createNewArray = (array) => {
const c = [...array]
const newArray = []
for (let i = 0; i < c.length; i++) {
const e = c[i];
for (let ii = 0; ii < e.length; ii++) {
const ee = e[ii].serviceType;
newArray.push(ee);
}
}
const toReturn = uniq(newArray)
return toReturn;
}

If you want unique options, just pass the options in and set them to the state after you massage the data.
const { useEffect, useMemo, useState } = React;
const unique = (arr) => [...new Set(arr)];
const uniqueOptions = (options) =>
unique(options.flatMap(({ services }) =>
services.map(({ serviceType }) => serviceType)));
const data = {
options: [
{ services: [{ serviceType: "Home" } , { serviceType: "About" }] },
{ services: [{ serviceType: "About" } , { serviceType: "Help" }] },
{ services: [{ serviceType: "Help" } , { serviceType: "Home" }] },
],
};
const MenuOptions = (props) => {
const { options } = props;
const [opts, setOpts] = useState([]);
useEffect(() => setOpts(uniqueOptions(options)), [options]);
return useMemo(
() => (
<select>
{opts.map((opt) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
),
[opts]
);
};
const App = ({ title }) =>
useMemo(
() => (
<div>
<h1>Services</h1>
<form>
<MenuOptions options={data.options} />
</form>
</div>
),
[]
);
ReactDOM.render(<App />, document.getElementById("react-app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react-app"></div>

Related

advice with removing object from react state array

So this has me puzzled. I've been banging my head against the wall trying to figure this out.
So I am trying to remove an object from a state managed array. I don't believe I am mutating the array.
I am using prevState. My delete function which gets sent to another component
{this.state.educationArray.map((item, i) => (<RenderEducation education={item} onDelete={this.handleDelete} />))}
Sending back the id to the handleDelete function.
My handleDelete:
handleDelete = itemId => {
//const newStudy = this.state.educationArray.filter(item => { return item.id !== itemId });
//this.setState({ educationArray: newStudy })
let tempArray = [];
let num = this.state.educationArray.length;
for (let i = 0; i < num;) {
//console.log("itemId is: ", itemId)
let tempId = this.state.educationArray[i].id
if (tempId != itemId) {
let obj = this.state.educationArray[i]
tempArray.push(obj)
}
i++
}
this.setState(prevState => ({ educationArray: tempArray }));
}
Stack Snippet w/loop:
const { useState } = React;
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
educationArray: [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
],
};
}
handleDelete = (itemId) => {
// const newStudy = this.state.educationArray.filter(item => { return item.id !== itemId });
// this.setState({ educationArray: newStudy })
let tempArray = [];
let num = this.state.educationArray.length;
for (let i = 0; i < num; ) {
//console.log("itemId is: ", itemId)
let tempId = this.state.educationArray[i].id;
if (tempId != itemId) {
let obj = this.state.educationArray[i];
tempArray.push(obj);
}
i++;
}
this.setState((prevState) => ({ educationArray: tempArray }));
};
render() {
return (
<ul>
{this.state.educationArray.map((element) => (
<li key={element.id}>
{element.name}{" "}
<input type="button" value="Del" onClick={() => this.handleDelete(element.id)} />
</li>
))}
</ul>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Stack Snippet w/filter:
const { useState } = React;
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
educationArray: [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
],
};
}
handleDelete = (itemId) => {
const newStudy = this.state.educationArray.filter(item => { return item.id !== itemId });
this.setState({ educationArray: newStudy })
/*
let tempArray = [];
let num = this.state.educationArray.length;
for (let i = 0; i < num; ) {
//console.log("itemId is: ", itemId)
let tempId = this.state.educationArray[i].id;
if (tempId != itemId) {
let obj = this.state.educationArray[i];
tempArray.push(obj);
}
i++;
}
this.setState((prevState) => ({ educationArray: tempArray }));
*/
};
render() {
return (
<ul>
{this.state.educationArray.map((element) => (
<li key={element.id}>
{element.name}{" "}
<input type="button" value="Del" onClick={() => this.handleDelete(element.id)} />
</li>
))}
</ul>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
I've tried using the 2 lines commented out, I've tried rearranging how I do the for loop, its always the same result, it never removes the intended id.
I have sent console.log after console.log of the ids getting moved around and every seems to be working, but when it comes right now to push the specific objects that don't match the id to the temp array it never works and the object add the end gets removed.
Please and thank you for your advice
EDIT:
i call the handleDelete inside RenderEducation component:
<button onClick={() => this.props.onDelete(this.state.id)}> X - {this.state.id}</button>
from each
and my constructor:
constructor(props) {
super(props);
this.state = {
educationArray: [],
}
}
and how i add to the array:
addEducation = (e) => {
e.preventDefault();
this.setState(prevState => ({
educationArray: [...prevState.educationArray, {
id: uniqid(),
school: '',
study: '',
dateFrom: '',
dateTo: '',
editing: true,
}]
}))
}
Both versions of your code work in regular, non-edge-case situations, as we can see from the Stack Snippets I added to your question. The only problem I can see with the code shown is that it's using a potentially out-of-date version of the educationArray. Whenever you're updating state based on existing state, it's best to use the callback form of the state setter and use the up-to-date state information provided to the callback. Both of your versions (even your loop version, which does use the callback) are using this.state.educationArray instead, which could be out of date.
Instead, use the array in the state passed to the callback:
handleDelete = (itemId) => {
// Work with up-to-date state via the callback
this.setState(({educationArray: prevArray}) => {
// Filter out the element you're removing
return {
educationArray: prevArray.filter(({id}) => id !== itemId)
};
});
};
Live Example:
const { useState } = React;
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
educationArray: [
{ id: 1, name: "One" },
{ id: 2, name: "Two" },
{ id: 3, name: "Three" },
],
};
}
handleDelete = (itemId) => {
// Work with up-to-date state via the callback
this.setState(({educationArray: prevArray}) => {
// Filter out the element you're removing
return {
educationArray: prevArray.filter(({id}) => id !== itemId)
};
});
};
render() {
return (
<ul>
{this.state.educationArray.map((element) => (
<li key={element.id}>
{element.name}{" "}
<input type="button" value="Del" onClick={() => this.handleDelete(element.id)} />
</li>
))}
</ul>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

Remove duplicates from inner array of array of objects

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>
))

Where do I fill an array for a datatable to display on render?

I am filling a datatable to display on my webpage. It only renders when I interact with the sort button. Where should I fill this array for use in a datatable such that it happens before the rendering?
export function MyComponent() {
const columns = [
{
name: 'Player',
selector: row => row.Player,
sortable: true,
},
];
const [teamArray, setTeamArray] = useState([])
const [managerTeamList, setTeamList] = useState([])
const [bootstrapData, setBootstrapData] = useState([])
var managersteam = [];
useEffect(() => {
getBootstrap().then(bootstrap => {
setBootstrapData(bootstrap.elements)
})
getManagersTeam(27356,28).then(data => {
setTeamList(data.picks)
})
for (var i in managerTeamList) {
for (var j in bootstrapData) {
if (managerTeamList[i].element == bootstrapData[j].id) {
managersteam.push({
"Player" : bootstrapData[j].second_name
})
break;
}
}
}
setTeamArray(managersteam)
}, []);
return (
<DataTable columns={columns} data={teamArray}/>
)
}
Please know that the api requests are asynchronous - getBootstrap and getManagersTeam - so they are empty when you try to loop through the data.
You can use async/await or just update the teamArray when the promises are solved.
You can use the below code:
export function MyComponent() {
const columns = [
{
name: "Player",
selector: (row) => row.Player,
sortable: true,
},
];
const [teamArray, setTeamArray] = useState([]);
const fetchData = async () => {
var managersteam = [];
const [bootstrap, data] = await Promise.all([
getBootstrap(),
getManagersTeam(27356, 28)
]);
for (var i in data.picks) {
for (var j in bootstrap.elements) {
if (data.picks[i].element == bootstrap.elements[j].id) {
managersteam.push({
Player: bootstrap.elements[j].second_name,
});
break;
}
}
}
setTeamArray(managersteam);
}
useEffect(() => {
fetchData();
}, []);
return <DataTable columns={columns} data={teamArray} />;
}

Fill an object dynamically in react with values from an array

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
}
});
});
}, []);

How to filter an array and add values to a state

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!

Categories