How to implement UI based on nested array? - javascript

I have an array with some dynamic value which will have array of array and according to inner array length need to show OR text.
Example:
JSON data:
const renderArr = [
{
label: "Abc",
value: []
},
{
label: "XyZ",
value: ["Test", "Exam"]
},
{
label: "Dex",
value: []
},
{
label: "Mno",
value: ["Momo", "Pizza"]
},
{
label: "Pqr",
value: []
}
];
According to above structure there are value in renderArr[1].value and renderArr[3].value. So i need to show "OR" text between these in UI
Expected Logic:
I want to show OR condition based on below condition
let currentValue = renderArr[index].value.length > 0 then
If nextValue = renderArr[index+1].value.length > 0 then show OR condition, NOTE: it'll check till it not found the value.length > 0 for every index
Below is my logic, which is not working properly.
My Logic:
shouldShowNextOr = (value, rootIndex) => {
let shouldShow = false;
let f = renderArr;
if (rootIndex < 4) {
switch (rootIndex) {
case 0:
shouldShow =
f[1].value.length > 0 ||
f[2].value.length > 0 ||
f[3].value.length > 0 ||
f[4].value.length > 0
? true
: false;
break;
case 1:
shouldShow =
f[2].value.length > 0 ||
f[3].value.length > 0 ||
f[4].value.length > 0
? true
: false;
break;
case 2:
shouldShow =
f[3].value.length > 0 || f[4].value.length > 0 ? true : false;
break;
case 3:
shouldShow = f[4].value.length > 0 ? true : false;
break;
default:
shouldShow = false;
}
}
return shouldShow;
};
Here is online codesandbox
Current Output
Expected Output

Filter the renderArray (data in my example), and remove all items with empty value array. Render the filtered array, and if an item is not the last (i < filteredData.length - 1) you can render "OR" after it:
const { useMemo } = React;
const App = ({ data }) => {
const filteredData = useMemo(() => data.filter(o => o.value.length), [data]);
return (
<div className="App">
{filteredData.map((o, i) => (
<React.Fragment key={o.label}>
<div>
<span>{o.label}</span> ={" "}
{o.value.join(" and ")}
</div>
{i < filteredData.length - 1 &&
<span>OR<br /></span>
}
</React.Fragment>
))}
</div>
);
};
const renderArr = [{"label":"Abc","value":[]},{"label":"XyZ","value":["Test","Exam"]},{"label":"Dex","value":[]},{"label":"Mno","value":["Momo","Pizza"]},{"label":"Pqr","value":[]}];
ReactDOM.render(
<App data={renderArr} />,
root
)
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
If you need the original index, use Array.map() to include it in the object before filtering:
const { useMemo } = React;
const App = ({ data }) => {
const filteredData = useMemo(() =>
data
.map((o, index) => ({ ...o, index }))
.filter(o => o.value.length)
, [data]);
return (
<div className="App">
{filteredData.map((o, i) => (
<React.Fragment key={o.label}>
<div>
<span>{o.label} - {o.index}</span> ={" "}
{o.value.join(" and ")}
</div>
{i < filteredData.length - 1 &&
<span>OR<br /></span>
}
</React.Fragment>
))}
</div>
);
};
const renderArr = [{"label":"Abc","value":[]},{"label":"XyZ","value":["Test","Exam"]},{"label":"Dex","value":[]},{"label":"Mno","value":["Momo","Pizza"]},{"label":"Pqr","value":[]}];
ReactDOM.render(
<App data={renderArr} />,
root
)
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>

Another way would be : Store all the index values of the renderArray which has value in another array(arr in my example). Check if rootIndex is part of arr.
Here's the code
arrIndex = [];
shouldShowNextOr = (value, rootIndex) => {
let shouldShow = false;
let f = renderArr;
let arr = this.arrIndex;
//storing all the indices which has value
if (arr.length === 0) {
f.forEach(function (a, index) {
if (a.value.length > 0) arr.push(index);
});
}
//check if root index exists
if (arr.includes(rootIndex) && arr.length > 1) {
//skip the last element in the array.
if (rootIndex === arr[arr.length - 1]) {
shouldShow = false;
} else {
shouldShow = true;
}
}
return shouldShow;
};
Input:
const renderArr = [
{
label: "Abc",
value: []
},
{
label: "XyZ",
value: ["Yes", "No"]
},
{
label: "Dex",
value: []
},
{
label: "Mno",
value: ["Hi","Hello"]
},
{
label: "Pqr",
value: ["Yes", "No"]
}
];
Output:
XyZ = Yes and No
OR
Mno = Hi and Hello
OR
Pqr = Yes and No

Related

Toggle the button name on click in ReactJS

I have an array of objects. So for every object, which has subItems, I'm trying to add a button to it. On click of the particular button, the name of the particular button should toggle between 'Expand' and 'Hide'. I'm displaying them using map.
export default function App() {
const [button,setButton] = useState([
{pin: 'Expand', id: 0},
{pin: 'Expand', id: 1},
{pin: 'Expand', id: 2},
]);
const dataObj = [
{
title: 'Home'
},
{
title: 'Service',
subItems: ['cooking','sleeping']
},
{
title: 'Contact',
subItems: ['phone','mobile']
}
];
const expandFunc = (ind) => {
// toggle logic here
}
return (
<div className="App">
{
dataObj.map((arr,ind) => (
<div>
<span>{arr.title}:</span>
{
// console.log(ind)
arr.subItems &&
<button onClick={() => expandFunc(ind)}>{button[ind].pin}</button>
}
</div>
))
}
</div>
);
}
This is how the output looks -
If I click on service button, then the button name should toggle between 'expand' and 'hide'. Could someone help me with this?
You need to update your state by determining new pin based on current state, try using Array.map:
const expandFunc = (ind) => {
const togglePin = oldPin => oldPin === "Expand" ? "Hide" : "Expand";
const updatedButtons = button.map((btn, index) =>
ind === index ? { ...btn, pin: togglePin(btn.pin) } : btn);
setButton(updatedButtons);
}
You can use something like this, I'll also suggest combining dataObj into button state, and using key while mapping elements in React helps to skip them in the rendering process making your site faster.
export default function App() {
const [button, setButton] = useState([{
expanded: false,
id: 0
},
{
expanded: false,
id: 1
},
{
expanded: false,
id: 2
},
]);
const dataObj = [{
title: 'Home'
},
{
title: 'Service',
subItems: ['cooking', 'sleeping']
},
{
title: 'Contact',
subItems: ['phone', 'mobile']
}
];
const toggleExpandFunc = useCallback((ind) => {
// toggle logic here
setButton([...button.map(btn => btn.id === ind ? { ...btn,
expanded: !btn.expanded
} : btn)]);
}, [button]);
return ( <
div className = "App" > {
dataObj.map((arr, ind) => ( <
div >
<
span > {
arr.title
}: < /span> {
// console.log(ind)
arr.subItems &&
<
button onClick = {
() => toggleExpandFunc(ind)
} > {
button[ind].expanded ? 'Expanded' : 'Hide'
} < /button>
} <
/div>
))
} <
/div>
);
}
You can also need another state like Toggle and expnadFunc can be handled like this;
const expandFunc = (ind) => {
let tmp = [...button];
tmp.map((arr, index) => {
if (index === ind) {
return (arr.pin = arr.pin === 'Toggle' ? 'Expand' : 'Toggle');
}
return arr;
});
setButton(tmp);
};

How to insert a name in an array with number values

I'm trying to render out the lowest value of three values in an array. It looks like this
const compareValues = [
companyResult.physicalAverage,
companyResult.stressAverage,
companyResult.developmentAverage,
];
const getLowestValue = (Math.min(...compareValues)); // returns 4.5 (which is correct)
return (
<div>
<p>{getLowestValue}</p> // HERE I want to render out the name (string) instead of 4.5
</div>
);
However, I want to name each property, i.e
companyResult.physicalAverage = "Physical Activity"
companyResult.stressAverage = "Stress",
companyResult.developmentAverage = "Personal Development"
I still want to return the lowest value with Math.min, but somehow return the 4.5 as a name. Anyone that has a clue on how?
Get the min value yourself.
const compareValues = [
{name: 'physicalAverage', value: companyResult.physicalAverage},
{name: 'stressAverage', value: companyResult.stressAverage},
{name: 'developmentAverage', value: companyResult.developmentAverage},
];
let minObj = compareValues[0]
compareValues.forEach(obj => {
if(obj.value < minObj.value)
minObj = obj;
})
return (
<div>
<p>{minObj.name}</p>
</div>
);
You can use useMemo hooks to apply sort function. But you will have to tweak your compareValues array a bit to include details about title.
const {useMemo} = React;
const companyResult = {
physicalAverage: 4.5,
stressAverage: 5,
developmentAverage: 6
};
function App() {
const compareValues = useMemo(
() => [
{ value: companyResult.physicalAverage, title: "Physical Activity" },
{ value: companyResult.stressAverage, title: "Stress" },
{ value: companyResult.developmentAverage, title: "Personal Development" }
],
[]
);
const lowest = useMemo(() => {
return compareValues.sort((a, b) => a.value < b.value)[0];
}, [compareValues]);
return (
<div>
<p>Label: {lowest.title}</p>
<p>Value: {lowest.value.toString()}</p>
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App />
);
<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>
<div id="root"></div>
If these are the only values in the object, you can make it even simpler by using
const compareValues = Object.entries(companyResult);
const minValue = compareValues.reduce((acc, entry) => entry[1] > acc[1] ? acc : value);
// minValue[0] is the key
// minValue[1] is the value
You need to change your data structure. And then create a custom function (findMinValue) to find the minimum value in your data.
const values = [
{ name: "Physical Activity", value: companyResult.physicalAverage },
{ name: "Stress", value: companyResult.stressAverage },
{ name: "Personal Development", value: companyResult.developmentAverage },
];
const findMinValue = (array) =>
array.reduce((prev, curr) => (prev.value < curr.value ? prev : curr));
const lowestValue = findMinValue(values);
return (
<div>
<p>{lowestValue.name}</p>
</div>
);

useState not updating array correctly

I am working on carousel slider. In which i use 2 arrays. 1 array 'slideData' of objects which contain images and id's and the other array 'slideNum' contain indexs to iterate. slideArr is the final array which we will map, it contain images from 'slideData' and map according to 'slideNum' indexes. When i update 'slideArr' array with useState than is not updating but when i update directly using array.splice than its working.
const SecondGrid = () => {
const len = SlideData.length - 1;
const [first, setFirst] = useState(1);
const [second, setSecond] = useState(2);
const [third, setThird] = useState(3);
let [slideNum, setSlideNum] = useState([0, 1, 2]);
const [slideArr, setSlideArr] = useState([
{
id: 0,
imgsrc: "./assets/c1.jpg",
data: "Here is Light salty chineese touch",
},
{
id: 1,
imgsrc: "./assets/c2.jpg",
data: "Try our special breakfast",
},
{
id: 2,
imgsrc: "./assets/c3.jpg",
data: "Taste the italian likalu food",
},
]);
const next = () => {
setFirst(first >= len ? 0 : (prevState) => prevState + 1);
setSecond(second >= len ? 0 : (prevState) => prevState + 1);
setThird(third >= len ? 0 : (prevState) => prevState + 1);
// const arr = [...slideArr];
// storing next index image into all three Cards
slideNum.forEach((val, key1) => {
SlideData.forEach((value, key) => {
if (key === val) {
slideArr.splice(key1, 1, value);
// this is not working
// arr[key1] = value;
// console.log(arr);
// setSlideArr(arr);
//console.log(slideArr);
}
});
});
};
useEffect(() => {
next();
}, []);
useEffect(() => {
const interval = setTimeout(() => {
//updaing slideNum number,in which 'first' contain id of image which will be on 0 index, its updating through useState.
setSlideNum([first, second, third]);
next();
}, 3000);
return () => clearTimeout(interval);
}, [first, second, third]);
return (
<Container>
<div className="row">
<div className="d-flex flex-wrap justify-content-around ">
{slideArr.map((val) => (
<SlideCard val={val} />
))}
</div>
</div>
</Container>
);
};
It would be helpful to have some standalone code that demonstrates the problem but just looking at the commented out bit you say is not working, you can use map() to change values in an array:
setSlideArr(slideArr.map(item => (item.id === 1 ? value : item)))
Here's a demo CodeSandbox.
Also, your console.log(slideArr) should be in a useEffect hook if you want to see the state value after it has changed.
As far as I know, what happens is that you can't splice slideArr because it's useState, what you would be better off doing is:
if (key === val) {
var arr = slideArr;
arr.splice(key1, 1, value);
setSlideArr(arr);
}

how to watch field in react (dynamically add useEffect)?

I am trying to watch a field which have watch:true field.In other words I want add useEffect dynamically.I have one json (which is coming from server).I want to watch field value (which have property watch:true).using it's value I want to update other field value .
here is my code
https://codesandbox.io/s/crimson-violet-uxbzd
see this object have watch: true, so I need to watch or check if it value is changed or not
{
label: "First",
name: "first",
type: "select",
remove: true,
watch: true,
options: ["", "one", "two"]
},
if it's value is changed then call this function
const getSecondDropdownValue = function(value) {
return new Promise((resolve, reject) => {
if (value === "one") {
setTimeout(function() {
resolve(["three", "four"]);
}, 1000);
}
if (value === "two") {
setTimeout(function() {
resolve(["five", "six"]);
}, 1000);
}
});
};
any update?.
Check if the item has watch property, if it does pass getSecondDropdownValue to onChange event of the select option. something like
<select onChange={hasWatch ? getSecondDropdownValue : () => {}}>...</select>
Create a component that will render select options.
// options = array of list options
// onChange = onchange event handler
// name = name of the select
const Option = ({ options, onChange, name }) =>
(options.length && (
<select onChange={(e) => onChange(e.target.value, name)}>
{Array.isArray(options) &&
options.map((option, index) => (
<option value={option} key={option + index}>{option}</option>
))}
</select>
)) ||
false;
Add useState for storing the data from the api.
// initial data from the api
const [data, updateData] = useState(apiData);
// update select options and the list
const updateSelectData = (list, updated) => {
const index = list.findIndex(item => item.name === updated.name);
return [
...list.slice(0, index),
Object.assign({}, list[index], updated),
...list.slice(index + 1)
];
};
getSecondDropdownValue function
const getSecondDropdownValue = function(value, name) {
const updated = data.find(
item => item.dependentField && item.dependentField[0] === name
);
// return new Promise((resolve, reject) => {
if (value === "one") {
// setTimeout(function() {
// resolve(["three", "four"]);
// }, 1000);
updated.options = ["three", "four"];
}
if (value === "two") {
// setTimeout(function() {
// resolve(["five", "six"]);
// }, 1000);
updated.options = ["five", "six"];
}
// });
updateData(updateSelectData(data, updated));
};
Example
// Get a hook function
const {useState} = React;
const apiData = [
{
label: "First",
name: "first",
type: "select",
watch: true,
options: ["", "one", "two"]
},
{
label: "Second",
name: "second",
options: [],
dependentField: ["first"],
type: "select"
}
];
// option component
const Option = ({ options, onChange, name }) =>
(options.length && (
<select onChange={(e) => onChange(e.target.value, name)}>
{Array.isArray(options) &&
options.map((option, index) => (
<option value={option} key={option + index}>{option}</option>
))}
</select>
)) ||
false;
function App() {
const [data, updateData] = useState(apiData);
const updateSelectData = (list, updated) => {
const index = list.findIndex(item => item.name === updated.name);
return [
...list.slice(0, index),
Object.assign({}, list[index], updated),
...list.slice(index + 1)
];
};
const getSecondDropdownValue = function(value, name) {
const updated = data.find(
item => item.dependentField && item.dependentField[0] === name
);
// return new Promise((resolve, reject) => {
if (value === "one") {
// setTimeout(function() {
// resolve(["three", "four"]);
// }, 1000);
updated.options = ["three", "four"];
}
if (value === "two") {
// setTimeout(function() {
// resolve(["five", "six"]);
// }, 1000);
updated.options = ["five", "six"];
}
// });
updateData(updateSelectData(data, updated));
};
return (
<div className="App">
{data.map((options, index) => (
<Option
name={options.name}
key={index}
onChange={options.watch ? getSecondDropdownValue : () => {}}
options={options.options}
/>
))}
</div>
);
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>

How to filter item from array in React if their length is less than a certain number?

I'm trying to use the filter() method to filter out items from an array, if the array's length is less than a certain number, in Reactjs. I haven't been able to accomplish this so far.
Code Sample:
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.state = {
itemNumber: 2,
items: [
{ text: "Item 1" },
{ text: "Item 2" },
{ text: "Item 3" },
{ text: "Item 4" }
]
}
}
filterItem = () => {
if ( this.state.items.length > this.state.itemNumber ) {
console.log('Items length is higher');
let newItems = this.state.items.filter(item => {
return item < this.state.itemNumber;
});
console.log(newItems);
} else {
console.log('Items length is lower');
}
};
render() {
const { items } = this.state;
return (
<div>
<ul>
{ this.state.items.map(item =>
(
<li key={item.id}>
{ item.text }
</li>
)
)}
</ul>
<button onClick={this.filterItem}>Filter items!</button>
</div>
)
}
}
Edit:
Solution here.
You're never setting the state
filterItem = () => {
if ( this.state.items.length > this.state.itemNumber ) {
console.log('Items length is higher');
let newItems = this.state.items.filter((item,i) => {
return i < this.state.itemNumber;
});
this.setState({items: newItems})
console.log(newItems);
} else {
console.log('Items length is lower');
}
};
I'm not exactly sure what you're trying to do but can't you just use slice?
const newItems = [...this.state.items].slice(0, this.state.itemNumber).map(item =>
Just as #Dupocas said, you never update your state.
You need to use this.setState to update React component's state.
I think you should have 2 kinds of array in your states
this.state = {
itemNumber: 2,
items: [
{ text: "Item 1" },
{ text: "Item 2" },
{ text: "Item 3" },
{ text: "Item 4" }
],
filteredItems: [],
}
Then you should modify the filter function like this:
filterItem = () => {
if ( this.state.items.length > this.state.itemNumber ) {
console.log('Items length is higher');
let newItems = this.state.items.filter((item, index) => {
return index < this.state.itemNumber;
});
console.log(newItems);
this.setState({filteredItems: newItems});
} else {
console.log('Items length is lower');
}
}
Notice the this.setState({filteredItems: newItems}); line, this line will update this.state.filteredItems.
Finally, in the render function:
render() {
const { filteredItems } = this.state;
return (
<div>
<ul>
{ filteredItems.map(item =>
(
<li key={item.id}>
{ item.text }
</li>
)
)}
</ul>
<button onClick={this.filterItem}>Filter items!</button>
</div>
)
}
This will render an empty array until the filter button is clicked.
To display the complete array on first time render, add this function to the component
componentDidMount() {
this.setState({filteredItems: [ ...this.state.items ]});
}
In case somebody is asking what does the [ ...this.state.items ] do, it's called array destructuring.
use filter to filter an array,
let arr = ["item1", "item12", "item123"];
arr = arr.filter((item) => item.length >= 6)); //6 is the min length of item that you want.
It will return the new array with the filtered result.
You can declare any other array and store the result in that also
like
let arr = ["item1", "item12", "item123"];
let arr1 = arr.filter((item) => item.length >= 6));

Categories