How to display result of map in two different columns - javascript

I have a task, but i couldnt get the solution for it in reactjs. I would like to show the result in two columns like: So every next element should be added in the second column, but the first column should be limited to 2 rows.
item 1 | item 4
item 2 | item 5
| item 6
When i loop through the array the columns are getting divided into equal order, but as you see in the diagram i need to split them in 2:3 ratio.
I am giving the sandbox link here on what i have tried so far.
// Example class component
class Thingy extends React.Component {
render() {
const secondaryNav = [
{
title: "Games",
name: ["Roadrash", "Prince of Persia", "Counter Strike"]
},
{
title: "Resources",
name: ["Images", "Videos", "E-books"]
},
{
title: "Shop",
name: ["Shop now"]
},
{
title: "Account",
name: ["Log In", "Register"]
},
{
title: "Support",
name: ["Email", "Call Us", "Get a callback"]
}
];
return (
<div className="App">
<div className="container">
<div className="row">
{secondaryNav.map((item, i) => {
return (
<div className="col-lg-6 col-md-6">
<h3>{ item.title }</h3>
<ul>
{item.name.map((items, i) => {
return <li>{items}</li>
})}
</ul>
</div>)
})}
</div>
</div>
</div>
)
}
}
// Render it
ReactDOM.render(
<Thingy title="I'm the thingy" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
crossorigin="anonymous">
SANDBOX

You need to add the css on it and also you could slice your array as you like.
this is the piece of the code
<div className="row">
{secondaryNavData.slice(0, 2).map((item, i) => {
return (
<div className="col-lg-6 col-md-6">
<h3>{item.title}</h3>
<ul>
{item.name.map((items, i) => {
return <li>{items}</li>;
})}
</ul>
</div>
);
})}
</div>
<div className="row-2">
{secondaryNavData.slice(2, 5).map((item, i) => {
return (
<div className="col-lg-6 col-md-6">
<h3>{item.title}</h3>
<ul>
{item.name.map((items, i) => {
return <li>{items}</li>;
})}
</ul>
</div>
);
})}
</div>
Try this codesandbox based on yours for the full code with the css
https://codesandbox.io/s/elegant-grass-nmeux

slice can help us slice the array between (0,2) and (2,5) indices and accordingly we can map over the divs. We don't need row class coming from bootstrap for this use-case. Can simply replace it with col as well.
Updated question sandbox -
https://codesandbox.io/s/goofy-black-4m4ii?file=/src/styles.css

Related

Recursively structure array of objects : ReactJS

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} />

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>

Making React Component of list of posts and their comments below them from 2 API

I'm Using Two API Endpoints in my React.js App and Axios so that it must list posts and their comments below them.
The Problem is I Can't Excute Two map functions inside each other to do that, I Googled the issue but I had no lead, I also thought using other JavaScript Functions like Reduce instead of map but I dont think it will work out.
The 2 API Endpoints Are :
https://jsonplaceholder.typicode.com/posts
https://jsonplaceholder.typicode.com/comments?postId=1
The Component:
import React, { Component } from 'react';
import axios from 'axios';
import './Postlist.css';
class Postlist extends Component {
state = {
posts: [],
comments: []
};
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/posts`).then(res => {
const posts = res.data;
this.setState({ posts });
});
axios.get(`https://jsonplaceholder.typicode.com/comments`).then(res => {
const comments = res.data;
this.setState({ comments });
});
}
render() {
return (
<div className="container">
<div className="jumbotron-div col s12">
<ul className="collection">
{this.state.posts.map(post => (
<li
key={post.id}
className="collection-item left-align red lighten-3"
>
<h5>User ID: {post.id}</h5>
<p>Post: {post.body}</p>
</li>
))}
</ul>
</div>
<div className="jumbotron-div col s12">
<ul className="collection">
{this.state.comments.map(comment => (
<li
key={comment.id}
className="collection-item left-align purple lighten-2"
>
<p>User ID: {comment.id}</p>
<p>Post: {comment.body}</p>
</li>
))}
</ul>
</div>
</div>
);
}
}
export default Postlist;
In The Previous code snippet I made 2 requests to the api endpoints and set them to the states posts and comments, Inside render() function the list of posts and comments successfully renders but it listed all posts then all comments, and I need each post with its comments
You can nest 2 maps. Just if you want a comment inside a post you need to nest your HTML tags inside each other and then call the map from inside the other one.
class Postlist extends React.Component {
state = {
posts: [],
comments: []
};
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/posts`).then(res => {
const posts = res.data;
this.setState({ posts });
});
axios.get(`https://jsonplaceholder.typicode.com/comments`).then(res => {
const comments = res.data;
this.setState({ comments });
});
}
render() {
return (
<div className="container">
<div className="jumbotron-div col s12">
<ul className="collection">
{this.state.posts.map(post =>
(
<div>
<li
key={post.id}
className="collection-item left-align red lighten-3"
>
<h5>User ID: {post.id}</h5>
<p>Post: {post.body}</p>
</li>
<div className="jumbotron-div col s12">
<ul className="collection">
{
this.state.comments.map(comment => (
<li
key={comment.id}
className="collection-item left-align purple lighten-2"
>
<p>User ID: {comment.id}</p>
<p>Post: {comment.body}</p>
</li>
)
)
}
</ul>
</div>
</div>
))
}
</ul>
</div>
</div>
);
}
}
ReactDOM.render(
<Postlist name="World" />,
document.getElementById('container')
);
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Working JSFiddle

React bind function to each item inside array

I'm trying to make it so when you click on the dropdown arrow the settings dropdown will appear.
When I currently press an arrow dropdown, all the settings dropdown open that are within the array loop.
This is the function that renders the loop:
viewPublishedPages() {
const pages = this.state.pages;
return (
<div>
{pages.map((val, i) => {
let dropdown = 'none';
return (
<div className="block" key={i}>
<div className="columns">
<div className="column is-10">
<p>PUBLISHED</p>
<h2>{val.title}</h2>
</div>
<div className="column">
<div className="settings">
<div className="arrow__container">
<div className="arrow" onClick={this.showSettings.bind(this, i)} />
</div>
{
this.state.settingPanel
?
<ClickOutside onClickOutside={::this.hide}>
<div className="arrow__dropdown">
<Link href={{pathname: '/admin/edit-page', query: {title: val.title}}}>
<a className="arrow__dropdown__link">Edit</a>
</Link>
<button
className="arrow__dropdown__delete"
onClick={() => this.handleDelete(i)}>Delete</button>
</div>
</ClickOutside>
: null
}
</div>
</div>
</div>
</div>
);
})}
</div>
);
}
Notice: <div className="arrow" onClick={this.showSettings.bind(this, i)} />
This is the state:
static dataStruc () {
return {
loading: true,
settingPanel: false,
pages: [],
};
}
Your are currently saving a boolean value to settingPanel and therefore all dropdowns open upon click.
My suggestion is replace settingPanel from boolean to the respective page id. In case you don't have page ids, then store the current page index on it.
That makes it easier to render the dropdown so you have access/control to the selected one and later render its settings:
showSettings(index) {
this.setState({
settingPanel: index,
})
}
And then in viewPublishedPages:
{this.state.settingPanel === i &&
<ClickOutside onClickOutside={::this.hide}>
..
</ClickOutside>}
I wrote a sample code so you get the idea.
class App extends React.Component {
constructor() {
super()
this.state = {
pages: [
{ title: 'Home' },
{ title: 'Contact' },
{ title: 'Page' }
],
settingPanel: -1,
}
this.showSettings = this.showSettings.bind(this)
}
showSettings(index) {
this.setState({
settingPanel: this.state.settingPanel === index ? -1 : index,
})
}
render() {
const { pages, settingPanel } = this.state
return (
<div>
{pages.map((page, index) =>
<div key={index} className="page">
<div onClick={this.showSettings.bind(this, index)}>
{page.title}
</div>
{settingPanel === index &&
<div className="settings">
<div>Setting 1</div>
<div>Setting 2</div>
<div>Setting 3</div>
</div>
}
</div>
)}
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
.page {
background-color: cyan;
margin-top: 10px;
padding: 10px;
}
.settings {
background-color: black;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Categories

Resources