Recursively structure array of objects : ReactJS - javascript

I am trying to render an array as a nested tree which can go up-to n levels. Basically I have an JSX that depicts parent and children. Based on if the node is parent or child , I need to render the tree by attaching classname.
JSX of the parent looks like:
<div className="level-0 group">
<div className="details-holder">
<span className="item-text">{name}</span>
</div>
</div>
JSX of the children look like:
<div className="level-1 leaf">
<span className="line" />
<div className="details-holder">
<span className="item-text">{name}</span>
</div>
</div>
<div className="level-2 leaf">
<span className="line" />
<div className="details-holder">
<span className="item-text">{name}</span>
</div>
</div>
These JSX needs to rendered dynamically based on the array of the objects with the follolwing structure
[{
"name": "abc",
"className": "level-0 group",
"children": [{
"name": "abc.xyz",
"className": "level-1 leaf",
"children": [{
"className": "level-2 leaf",
"name": "abc.xyz.pqr",
"children": []
}]
}]
}]
So ideally the parent should have level-0 group class, children level-1 leaf , grandchildren level-2 leaf and so on.
Code that I tried is below, but how would I do it dynamically
return(
{ data.map(item => {
<>
<div className=`${item.className}`>
<div className="details-holder">
<span className="item-text">{item.name}</span>
</div>
</div>
<div className={`${item[0].children[0].className`}>
<span className="line" />
<div className="details-holder">
<span className="item-text">{item[0].children[0].name}</span>
</div>
</div>
<div className={`${item[0].children[1].className`}>
<span className="line" />
<div className="details-holder">
<span className="item-text">{item[0].children[1].name}</span>
</div>
</div>
</>
}
}
)
Thanks for the help.

Please check this if it's work for you. It more in modular form which help you to update the parent and child HTML fragment separately.
const Parent = (item) => (
<div className={`${item.className}`}>
<div className="details-holder">
<span className="item-text">{item.name}</span>
</div>
</div>
);
const Child = (item) => (
<div className={`${item.className}`}>
<span className="line" />
<div className="details-holder">
<span className="item-text">{item.name}</span>
</div>
{item.children.map((ch) => (
<Child {...ch} />
))}
</div>
);
export default function App() {
return (
<div className="App">
{data.map((item) => (
<>
<Parent {...item} />
{item.children.map((ch) => (
<Child {...ch} />
))}
</>
))}
</div>
);
}

This is actually very simple if you implement the recursion inside the component. Just pass children as data into the same component calling itself.
const Tree = (props) => {
return <>
{
props.data.map((item) => {
return <div className = {item.className} > {
(props.level !== 0) && < span className = "line" />
} <div className = "details-holder" >
<span className = "item-text" > {name} </span> </div> {
props.data.children.map(() => <Tree data={item.children} level={props.level + 1} > )
} </div>
})
}
</>
}
<Tree data={data} level={0} />

Related

How to assign a classname to particular division after map in React js?

I am mapping data inside a div, all i need a border to div of "lab_add_to_cart_samp1" classname after clicking that division. I am confused how to make a division show active onclick even after mapping the data, there is an array of allabtest from API call
<div className="lab_add_to_cart_samp">
{
allabtest.map((item, index) => {
return <div className="lab_add_to_cart_samp1" key={index}>
<div className="lab_add_to_cart_samp_img1">
<img src={REACT_APP_BASE_URL+"lab-test/download/"+item.id} />
</div>
<div className="lab_add_to_cart_test_desc">
<p id="labtest_desc_txt1">{item.name}</p>
<p id="labtest_desc_txt2">What it include :</p>
<div className="labtest_desc1">
<div className="labtest_desc_detail">
<ul>
<li>Platate</li>
<li>CBC</li>
<li>HB</li>
</ul>
</div>
<div>
<ul id="lab_test_detail">
<li>PCV</li>
<li>WBC/TLC</li>
<li>WBC/TLC</li>
</ul>
</div>
<div>
<ul id="lab_test_detail">
<li>DLC</li>
<li>RBC</li>
<li>ESR</li>
</ul>
</div>
<div>
<ul id="lab_test_detail">
<li>Platelets</li>
<li>Reticilocytes</li>
<li>Blood Grouping Rh type</li>
</ul>
</div>
</div>
</div>
<div className="lab_add_to_cart_price">
<p>Rs. {item.price}</p>
<div className="lab_add_to_cart_atc">
<button onClick={()=>addtocart(item)}>
<p>Add to Cart</p>
</button>
</div>
</div>
</div>
})
}
</div>
If you want to set active for multiple divs at the same time you can do it like this
const allabtest = [
{id: 'one', name: 'one'},
{id: 'two', name: 'two'},
{id: 'three', name: 'three'},
]
function App() {
const [activeCart, setActiveCart] = useState({});
return (
<div className="lab_add_to_cart_samp">
{allabtest.map((item, index) => {
return (
<div
className={
activeCart[item.id] === true
? "lab_add_to_cart_samp1 active"
: "lab_add_to_cart_samp1"
}
onClick={() =>
setActiveCart({
...activeCart,
[item.id]: !Boolean(activeCart[item.id])
})
}
key={item.id}
>
<div>{item.name}</div>
</div>
);
})}
</div>
);
}
If you want to track only one div then
function App() {
const [activeCart, setActiveCart] = useState();
return (
<div className="lab_add_to_cart_samp">
{allabtest.map((item, index) => {
return (
<div
className={
activeCart === item.id
? "lab_add_to_cart_samp1 active"
: "lab_add_to_cart_samp1"
}
onClick={() => setActiveCart(item.id)}
key={item.id}
>
{/* Rest of your stuffs */}
</div>
);
})}
</div>
);
}

How Can I change the property of components of React?

I am new to React and recently started working on it. I know that we cannot change the components properties using the props.
I want to know how can we change the properties of Component?
Below is my code:
Courses.jsx
function Courses(){
return (
<div className="courses">
<h1>Ongoing Courses</h1>
<div className="row">
{CourseData.map((value,index)=>{
return (
<div className="col-md-3">
<Card title={value.title} completed={value.completed} content={value.content} value="Resume !" key={index} id={index} />
</div>
);
})}
</div>
</div>
);
}
Here above i am having a Array of Data named as courseData, I am mapping it on a Card component.
Card.jsx:
function Card(props){
function handleClick(){
}
return (
<div className="card">
<div className="card-body">
<h2 className="card-title">{props.title}</h2>
{props.content}
<br/>
<button className="btn btn-danger" > {props.value}</button>
</div>
</div>
);
}
the CourseData has following properties :
courseData : [{
key,
title,
completed
content}]
I simply want that when ever the button present is card gets clicked then the completed attribute of courseData changed to some different value that is passed through the props .
I have tried a lot but not able to do .
Any help regarding this will be helpful for me .
courseData.jsx:
const notes = [{
key: 1,
title: "some Text",
completed:false,
content: "some Text"
},
{
key: 2,
title: "some Text",
completed:false,
content: "some Text"
}]
export default notes;
Add CourseData to the state of the Courses component. Then add a method to adjust the data there. Pass the method throught props that will be called when clicking button in the Card component:
function Courses() {
const [courseData, setCourseData] = useState(CourseData);
const updateCourseData = (index) => {
courseData.splice(index, 1);
setCourseData(courseData);
}
return (
<div className="courses">
<h1>Ongoing Courses</h1>
<div className="row">
{courseData.map((value,index)=>{
return (
<div className="col-md-3">
<Card title={value.title} updateCourseData={updateCourseData} completed={value.completed} content={value.content} value="Resume !" key={index} id={index} />
</div>
);
})}
</div>
</div>
);
}
in the Card.jsx:
<button onClick={() => props.updateCourseData(props.id)} className="btn btn-danger" > {props.value}</button>
function Courses(){
const [coursesData, setCoursesData] = useState(CourseData)
return (
<div className="courses">
<h1>Ongoing Courses</h1>
<div className="row">
{coursesData.map((value,index)=>{
return (
<div className="col-md-3">
<Card coursesData={coursesData} setCoursesData={setCoursesData} title={value.title} completed={value.completed} content={value.content} value="Resume !" key={index} id={index} />
</div>
);
})}
</div>
</div>
);
function Card({id,title,value,content,coursesData,setCoursesData }){
function handleClick(e){
e.preventDefault()
setCoursesData(coursesData => {
const data = coursesData
data.splice(id,1,{
title: title,
completed: value,
content: content,
key: id
})
return data
})
}
return (
<div className="card">
<div className="card-body">
<h2 className="card-title">{title}</h2>
{content}
<br/>
<button onClick={handleClick} className="btn btn-danger">{value}</button>
</div>
</div>
);

Cards inside the grid-container: each child in a list should have a unique "key" prop

What I`m doing wrong?It also says: "Check the render method of Card" , which is here:
<div className="grid-container">
{pokemonData.map((pokemon, i) => {
console.log(pokemon.id) // unique numbers are here
return <Card key={pokemon.id} pokemon={pokemon} />
})}
</div>
Card component itself:
function Card({ pokemon }) {
return (
<div className="card">
<div className="card__image">
<img src={pokemon.sprites.front_default} alt="Pokemon" />
</div>
<div className="card__name">
{pokemon.name}
</div>
<div className="card__types">
{
pokemon.types.map(type => {
return (
<div className="card__type" style={{backgroundColor: typeColors[type.type.name]}}>
{type.type.name}
</div>
)
})
}
</div>
<div className="card__info">
<div className="card__data card__data--weight">
<p className="title">Weight:</p>
<p>{pokemon.weight}</p>
</div>
<div className="card__data card__data--height">
<p className="title">Height:</p>
<p>{pokemon.height}</p>
</div>
<div className="card__data card__data--ability">
<p className="title">Abilities:</p>
{/* {console.log(pokemon.abilities)} Temporary for dev puprose */}
{pokemon.abilities.map(ability => <p>{ability.ability.name}</p>
)}
</div>
</div>
</div>
);
}
export default Card;
You can use the index of the array may be your data is having some kind of duplicate. It is recommended that you pass a key prop whenever you are returning a list.
<div className="grid-container">
{pokemonData.map((pokemon, i) => {
console.log(pokemon.id) // unique numbers are here
return <Card key={i} pokemon={pokemon} />
})}
</div>
Equally, check this segment of card components.
{
pokemon.types.map((type,i) => {
return (
<div key={i} className="card__type" style={{backgroundColor:
typeColors[type.type.name]}}>
{type.type.name}
/div>
)
})
}
And
<div className="card__data card__data--ability">
<p className="title">Abilities:</p>
{/* {console.log(pokemon.abilities)} }
{pokemon.abilities.map((ability, i) => <p key={i}>{ability.ability.name}
</p>
)}
</div>
Previous answer will solve your problem. However, for your info, I would also like to add here.
For React a key attribute is like an identity of a node/element/tag which helps React to identify each item in the list and apply reconciliation correctlyon each item. Without a key React will render your component but may cause issue when you re-order your list.
React recommends to use id of the data instead of index number. However, if your list does not re-orders/ sorts or do not have id then you can use index.
You can read more here:
https://reactjs.org/docs/lists-and-keys.html
Change this:
<div className="card__types">
{
pokemon.types.map(type => {
return (
<div className="card__type"
style={{backgroundColor:typeColors[type.type.name]}}
>
{type.type.name}
</div>
)
})
}
</div>
to:
<div className="card__types">
{
pokemon.types.map((type, key) => {
return (
<div key={key} className="card__type"
style={{backgroundColor:typeColors[type.type.name]}}
>
{type.type.name}
</div>
)
})
}
</div>
and:
{pokemon.abilities.map(ability => <p>{ability.ability.name}</p>
to:
{pokemon.abilities.map((ability,key) => <p key={key} >{ability.ability.name}</p>

Conditionally adding a row after 3 elements: ReactJS

I want to render several row containing three columns each. The columns have just a Card. The way I thought I could do this is to map through the elements and create a row when the index modulus is 0 and close that row when it's the third column of the row. I've tried with if-else statements and with ternary operators. But I keep getting syntax errors.
render(){
var { isLoaded, items } = this.state;
if(!isLoaded) {
return (<div> Fetching items </div>);
}
else {
var results = items.results;
return (
<div className="App">
<div className ="container">
{ result.map ((result, i) => {
return(
{i%3===0 ?
<div className ="row mt-4">
<div key ={i} className="col-md-4">
<Card result={result}></Card>
</div>
:
<div key ={i} className="col-md-4">
<Card result={result}></Card>
</div>);
}
{i%3===1 ?
</div>
:null}
})}
</div>
</div>
);
}
}
With this piece of code I'm getting an error in this line
{i%3===0 ?
How can I solve this?
Because, you have a unclosed <div> tag which is invalid JSX, also { in return means an object not dynamic content.
Don't forget we write JSX, not html. Each tag needs to be closed properly, because it will get converted into React.createElement(component/html tag, props, children).
To solve the problem, first prepare the array and after 3 items, just push the elements in row arrays, like this:
renderRows() {
let results = items.results;
let finalArr = [], columns = [];
result.forEach ((result, i) => {
// prepare the array
columns.push(
<div key ={i} className="col-md-4">
<Card result={result}></Card>
</div>
);
// after three items add a new row
if((i+1) % 3 === 0) {
finalArr.push(<div className ="row mt-4">{columns}</div>);
columns = [];
}
});
return finalArr;
}
render(){
var { isLoaded, items } = this.state;
if(!isLoaded) {
return (<div> Fetching items </div>);
} else {
return (
<div className="App">
<div className ="container">
{this.renderRows()}
</div>
</div>
);
}
}
You are getting the error, because you are not rendering a valid element each time - it is missing a closing tag when "i%3===0" resolves to true.
<div className ="row mt-4">
<div key ={i} className="col-md-4">
<Card result={result}></Card>
</div>
</div> // <-- this one
Also, you could just render all the cards in that one container and set the style of the card accordingly to be the width of a third of the parent container.
<div className="container">
{result.map((result, i) => {
return (
<div key={i} className="col-md-4"> // add styles for width 30%
<Card result={result} />
</div>
);
})}
</div>
And one other idea is that instead of feeding data like [1,2,3,4,5], you could reduce the array to buckets of other arrays like [[1,2,3], [4,5,6]] and render those.
<div className="container">
{result.map((row, i) => (
<div key={i} className="row mt-4">
{row.map((col, i) => (
<div key={i} className="col-md-4">
<Card result={col} />
</div>
))}
</div>
))}
</div>
P.s. don't use the index for the element key. Use something unique, like the value of the card or an id.
You are simply missing some closing brackets.
Try this,
<div className ="row mt-4">
{ result.map ((result, i) => {
return(
<React.Fragment>
{(i%3===0) ?
<div key ={i} className="col-md-4">
<Card result={result}></Card>
</div>
:
<div key ={i} className="col-md-4">
<Card result={result}></Card>
</div>
}
</React.Fragment>
)
})}
</div>
As per your code,
{ result.map ((result, i) => {
return(
{i%3===0 ?
<div className ="row mt-4">
<div key ={i} className="col-md-4">
<Card result={result}></Card>
</div>
:
<div key ={i} className="col-md-4">
<Card result={result}></Card>
</div>); //You have added `);` which is cause of error. this means you are returning this much part only.
Here's how you can fix it:
render() {
const { isLoaded, items } = this.state;
if (!isLoaded) {
return (<div> Fetching items </div>);
}
const results = items.results;
return (
<div className="App">
<div className="container">
{results.map((result, i) => (
<React.Fragment>
{(i + 1) % 4 === 0 ?
(<div className="row mt-4" key={`row-${i}`}>
<div key={i} className="col-md-4">
<Card result={result} />
</div>
</div>) :
(<div key={i} className="col-md-4">
<Card result={result} />
</div>)}
{i % 3 === 1 ? <div /> : null }
</React.Fragment>
))}
</div>
</div>
);
}
I strongly suggest you to use ESLint or linter to find and fix these errors then and there. Take a look at: https://medium.com/#RossWhitehouse/setting-up-eslint-in-react-c20015ef35f7. Eslint will help you with better indentation, matching brackets, best practices and much more. Once you have set eslint, you can sort these out yourself.
Edit:
Grouping items into four and putting them under one row:
class Application extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
isLoaded: true,
items: {
results: ["1", "2", "3", "4", "5", "6", "7", "8"]
}
};
}
render() {
const { isLoaded, items } = this.state;
if (!isLoaded) {
return (<div> Fetching items </div>);
}
const results = items.results;
// Group them into sets of 4.
const grouped = results.reduce((acc, post, ind) => {
var index = parseInt(ind / 4);
acc[index]= acc[index] || [];
acc[index].push(<div key={`colmn-${index}`} className="col-md-4">{results[ind]}</div>);
return acc;
}, []);
return (
<div className="App">
<div className="container">
{grouped.map((row, i) => {
return <div className="row mt-4" key={`row-${i}`}>{row}</div>})}
</div>
</div>
);
}
}
ReactDOM.render(<Application />, document.getElementById("retrospect-app"));
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.css" rel="stylesheet"/>
<div id="retrospect-app"></div>
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>

React Component populate Item component props from Json data

I have a React component which list items from the Item component.
Below you can see how it looks.
const ListHolder = () => (
<div>
<div className="panel">
<div className="panel-heading">
<h3 className="panel-title">List Title</h3>
</div>
<div className="panel-body">
<Item
title="A Title"
desc="Desc 1."
/>
<Item
title="Another Title"
desc="Desc 2."
/>
<Item
title="Some of title"
desc="Desc 3."
/>
</div>
</div>
</div>
);
export default ListHolder;
Item components are currently hardcoded.
How can I populate / insert the data from some Json data?
If you pass your items data as props of your component, you could do something like:
const ListHolder = ({items}) => (
<div>
<div className="panel">
<div className="panel-heading">
<h3 className="panel-title">List Title</h3>
</div>
<div className="panel-body">
{ items.map((item) => <Item title={item.title} desc={item.desc} />)}
</div>
</div>
</div>
);
~ EDIT after first comment ~
That is if you can use so called
Otherwise the "usual" way can look something like:
class ListHolder extends React.Component {
render() {
const items = [{ "title": "title", "desc": "some desc" }, { "title": "title2", "desc": "some other desc" }];
return (
<div>
<div className="panel">
<div className="panel-heading">
<h3 className="panel-title">List Title</h3>
</div>
<div className="panel-body">
{ items.map((item) => <Item title={item.title} desc={item.desc} /> )}
</div>
</div>
</div>
);
}
}

Categories

Resources