React - Render children in matching sort order? - javascript

Given a component that renders its children, I can't get the children to render sorted as per their newOrder order:
export default class EnhancedLinks extends React.Component {
render() {
return (
<div>
{React.Children.map(
this.props.children.filter((c) => c),
(el) => el
)}
</div>
);
}
}
export default class App extends React.Component {
render() {
const newOrder = ["dandelion", "cantaloupe", "apple", "banana"];
return (
<div>
<EnhancedLinks>
<div name="apple" color="red">apple</div>
<div name="banana" color="yellow">banana</div>
<div name="cantaloupe" color="orange">cantaloupe</div>
<div name="dandelion" color="green">dandelion</div>
</EnhancedLinks>
</div>
);
}
}
Any idea how to get the EnhancedLinks children to render as per the order of the newOrder array?
Here's a fiddle:
https://codesandbox.io/s/react-and-scss-forked-z1p0b?file=/src/index.js

Map the newOrder array to <div>s instead of writing them out individually?
const colors = ['red', 'yellow', 'orange', 'green'];
return (
<div>
<EnhancedLinks>
{newOrder.map((name, i) => <div name={name} color={colors[i]}>{name}</div>)}
</EnhancedLinks>
</div>
);
The

Your EnchancedLinks component should render the div, color and text, not the outside wrapping component. This way there's less copy/pasted code and you can set the order in your array, or manipulate your array as you need to.
export default class EnhancedLinks extends React.Component {
render() {
return (
<div name={this.props.name} color={this.props.color}>
{this.props.name}
</div>
);
}
}
export default class App extends React.Component {
render() {
const newOrder = [
{
name: "dandelion",
color: "green"
},
{
name: "cantaloupe",
color: "orange"
},
{
name: "apple",
color: "red"
},
{
name: "banana",
color: "yellow"
}];
return (
<div>
{newOrder.map((link) => {
return <EnhancedLinks {...link} />
})}
</div>
);
}
}
I've forked your fiddle with a solution I've come up with: https://codesandbox.io/s/react-and-scss-forked-c1r1f

Related

Display Content from Array to React Component

I'm trying to display my AboutPageContent.js to AboutPage.js. I would assume I would have to map it out somehow, but I'm not sure how.
Relevant Code
AboutPage.js
import React from 'react';
// CSS import statements
import '../css/AboutPage.css';
import '../css/App.css';
// Content import Statements
import AboutPageContent from '../content/AboutPageContent.js';
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return(
<div className='about-page'>
<div className='page-header'>{this.state.about.name}</div>
<div>{this.state.about.desc.map(paragraph => <p>{paragraph}</p>)}</div>
</div>
)
}
}
export default About;
AboutPageContent.js
let AboutPageContent = {
about: [{
name: 'About Me',
desc: [
'p1',
'p2',
'p3',
'p4',
'p5',
'p6'
],
id: 1
}]};
export default AboutPageContent;
You have to do 2 maps, 1 for state.about and another one for state.about[i].desc.
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return (
<div className="about-page">
<div className="page-header">{this.state.about.name}</div>
<div>
{this.state.about.map((currAbout) =>
currAbout.desc.map((paragraph, i) => <p key={i}>{paragraph}</p>)
)}
</div>
</div>
);
}
}
Or if you want to display the current about.name, move the <div className='page-header'>... inside this.state.about loop.
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return (
<div className="about-page">
<div>
{this.state.about.map((currAbout, i) => (
<React.Fragment key={i}>
<div className="page-header">{currAbout.name}</div>
{currAbout.desc.map((paragraph, i) => (
<p key={i}>{paragraph}</p>
))}
</React.Fragment>
))}
</div>
</div>
);
}
}
Your this.state.about is an array, so you can't reference the name and desc properties with dot notation. Currently, this.state.about.name would be undefined. this.state.about[0].name would be equal to 'About Me'.
I would simply remove the [ ]s around the this.state.about property. That would make it an object (as opposed to an array) so the rest of your code should work fine. Leave this.state.about.desc as an array so you can use the map method.
You can access the object in about this way state.about[0]
const AboutPageContent = {
about: [
{
name: "About Me",
desc: ["p1", "p2", "p3", "p4", "p5", "p6"],
id: 1,
},
],
};
class About extends React.Component {
constructor(props) {
super(props);
this.state = AboutPageContent;
}
render() {
return (
<div className="about-page">
<div className="page-header">{this.state.about[0].name}</div>
<div>
{this.state.about[0].desc.map((paragraph) => (
<p>{paragraph}</p>
))}
</div>
</div>
);
}
}
ReactDOM.render(
<About />,
document.getElementById('root')
);
<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="root"></div>
However if you have more than one property in the object and you want to display description by name you could try this
const AboutPageContent = {
about: [
{
name: "About Me",
desc: ["p1", "p2", "p3", "p4", "p5", "p6"],
id: 1,
},
{
name: " Section 2",
desc: ["p12", "p10"],
id: 1,
},
],
};
class About extends React.Component {
state = AboutPageContent;
render() {
return (
<div className="about-page">
<div className="page-header">
{Object.values(this.state)
.flat()
.map((o, i) => (
<React.Fragment key={i}>
<p>{o.name}</p>
{o.desc.map((paragraph) => (
<p>{paragraph}</p>
))}
</React.Fragment>
))}
</div>
</div>
);
}
}
ReactDOM.render(
<About />,
document.getElementById('root')
);
<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="root"></div>

Need to Trigger individual Element in an array of elements written through map function reactjs

class Services extends Component {
constructor(props) {
super(props);
this.state = {showoffer: false};
}
showOffers=( )=>{
this.setState({showoffer: !this.state.showoffer});
}
render() {
return (
<div className="OSServicesContainer">
<img className="OSlogomark" src={logomark} alt="logo mark" />
<article className="OssHeadingText">OOM INTERIORS OFFERS</article>
{offersdata.map((offers,index)=>{
return ( <div key={index} className="OssoffersContainermain">
<div className="OssoffersContainer">
<div className="OssofferHeadingmain">
<article className="OssofferHeading">{offers.heading}</article>
</div>
<article className="OssofferText">{offers.subheading}</article>
<div className="OssofferViewbtnmain">
<article key={index} className="OssofferViewbtn" onClick={this.showOffers}>{this.state.showoffer?"View Less":"View More"}</article>
</div>
</div>
{!this.state.showoffer?
null:
<div className="OssOfferSubCompmain">
{offers.offersub.map((offer,key) =>{
return <OssOfferSubComp ofrtext={offer.text} ofrsubtext={offer.subtext} />
})}
</div>}
</div>
)
})}
</div>);
}
}
export default Services;
Above is my code
i want to call showoffer function and update only that element clicked
please what shall i do it is triggering all elements
how to trigger single element??
You can try something like this:
`class Services extends Component {
constructor(props) {
super(props);
this.state = {showoffer: 0};
}
showOffers = ( offerIndex ) => {
this.setState({showoffer: offerIndex});
}
hideOffers = () => {
this.setState({showoffer: 0});
}
render() => {
...
<div className="OssofferViewbtnmain">
<article key={index} onClick={ () => this.showOffers(index) }>
{this.state.showoffer?"View Less":"View More"}
</article>
</div>
...
{
this.state.showOffer && this.state.showOffer === index
? // then show it
: ''
}
}`
Hey if you wish to have multiple items open at the same time you can do something like this where you mutate the mapped item to track show hide state. I have added a visible property to the list item that keeps track if the item is open or closed:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends Component {
state = {
items: [
{ header: "Test 1", extra: "Some extra content A" },
{ header: "Test 2", extra: "Some extra content B" },
{ header: "Test 3", extra: "Some extra content C" }
]
};
onItemClick(index) {
const selected = this.state.items[index];
this.setState({
items: [
...this.state.items.slice(0, index),
{ ...selected, visible: !selected.visible },
...this.state.items.slice(index + 1)
]
});
}
render() {
return (
<div>
<ul>
{this.state.items.map((item, index) => {
return (
<li
key={index}
style={{ cursor: "pointer" }}
onClick={() => this.onItemClick(index)}
>
<h3>{item.header}</h3>
{item.visible ? <div>{item.extra}</div> : null}
</li>
);
})}
</ul>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
https://codesandbox.io/s/busy-germain-hdmrn

What do I need to change in toggle function to show 1 element at a time rather than show all elements?

I'm building a dog breed info application and when I click on the breed name I want to show the info for that breed only. Currently, the toggle function does show the dog breed info when the breed name is clicked but it shows the info for all breeds rather than just the breed clicked.
I think the problem is that I'm not calling the object id correctly but I can't figure out where the id needs to be called. What am I missing? Thanks!
class Dog extends React.Component {
constructor(props) {
super(props);
this.state = {
dogInfo: [
{
id: 1,
key: 'dogBreed',
breedName: 'Tibetan Mastiff',
class: 'Working',
colors: ['Black', 'Black & Tan', 'Blue Gray', 'Blue Gray & Tan', 'Brown', 'Brown & Tan', 'Red Gold', 'Red Gold Sable'],
image: tibetanMastiff,
alt: 'Black and Tan Tibetan Mastiff'
},
{
id: 2,
key: 'dogBreed',
breedName: 'Great Dane',
class: 'Working',
colors: ['Black', 'Black & White', 'Blue', 'Brindle', 'Fawn', 'Harlequin', 'Mantle', 'Merle', 'White'],
image: greatDane,
alt: 'Merle Great Dane'
},
{
id: 3,
key: 'dogBreed',
breedName: 'Cavalier King Charles Spaniel',
class: 'Toy',
colors: ['Blenheim', 'Black & Tan', 'Tri-Color', 'Ruby'],
image: cavalier,
alt: 'Tri-Color Cavalier King Charles Spaniel'
},
{
id: 4,
key: 'dogBreed',
breedName: 'Italian Greyhound',
class: 'Toy',
colors: ['Black', 'Blue', 'Blue Fawn', 'Cream', 'Fawn', 'Red', 'Red Fawn', 'Sable', 'Seal'],
image: italianGrayhound,
alt: 'Fawn Italian Grayhound'
}
]
}
}
toggleSelected(id, key){
let temp = this.state[key]
temp[id].selected = !temp[id].selected
this.setState({
[key]: temp
})
}
render() {
return (
<div className="App">
<div className='wrapper'>
<DogList
title='Choose Dog Breed'
breedInfo={this.state.dogInfo}
toggleItem={this.toggleSelected}
/>
</div>
</div>
);
}
}
export default Dog;
class DogList extends React.Component {
constructor(props) {
super(props);
this.state = {
listOpen: false,
headerTitle: this.props.title
}
}
toggleList(){
this.setState(prevState => ({
listOpen: !prevState.listOpen
}))
}
render(){
const{breedInfo} = this.props
const{listOpen, headerTitle} = this.state
return(
<div>
<div >{headerTitle}</div>
{<ul onClick={() => this.toggleList()}>
{breedInfo.map((dog) => (
<li key={dog.id} >{dog.breedName}
{listOpen && <ul onClick={() => this.toggleList()}>
<img src={dog.image}/>
<li key={dog.id} >{dog.colors.map((color) => (
<ul>
<li>{color}</li>
</ul>
))}</li>
</ul>}
</li>
))}
</ul>}
</div>
)
}
}
export default DogList;
Hello smallDisgruntledDog, great name by the way. Taking a look at your code, there are a variety of ways to accomplish what you're looking for. As currently constructed, it looks like you are trying to make use of your toggleList()function to display one dog-breed at a time. However, the listOpen value is shared across all of your dogs, so it ends up opening all lists. There is no unique value being passed to determine which dog was selected.
I have 2 ways for you to solve this.
Option 1) Add an extra state value in your DogList component called selectedDog. Update your toggleList function to take in a dog breed value, which we will use to update the selectedDog state. Now in order to display a dog list, both listOpen has to be true and the selectedDog must match dog.breedName:
import React from "react";
class DogList extends React.Component {
constructor(props) {
super(props);
this.state = {
listOpen: false,
headerTitle: this.props.title,
selectedDog: null
};
}
toggleList(dogBreed) {
this.setState(prevState => ({
listOpen: !prevState.listOpen,
selectedDog: dogBreed
}));
}
render() {
const { breedInfo } = this.props;
const { listOpen, headerTitle, selectedDog } = this.state;
return (
<div>
<div>{headerTitle}</div>
{
<ul>
{breedInfo.map(dog => (
<li key={dog.id} onClick={() => this.toggleList(dog.breedName)}>
{dog.breedName}
{listOpen && selectedDog === dog.breedName && (
<ul>
<img src={dog.image} />
<li key={dog.id}>
{dog.colors.map(color => (
<ul>
<li>{color}</li>
</ul>
))}
</li>
</ul>
)}
</li>
))}
</ul>
}
</div>
);
}
}
export default DogList;
Option 2) Separate each Dog into a different component. I'm going to display this using a component called DogBreed. There are many benefits to doing this including writing cleaner code, giving each dog their own state and giving us more control over each dog.
DogBreed component:
import React from "react";
class DogBreed extends React.Component {
state = {
show: false
};
handleClick = () => {
this.setState(prevState => {
return {
show: !prevState.show
};
});
};
render() {
const { show } = this.state;
return (
<li onClick={this.handleClick}>
{this.props.breedName}
{show ? (
<ul>
<img src={this.props.image} />
<li>
{this.props.colors.map(color => (
<ul>
<li>{color}</li>
</ul>
))}
</li>
</ul>
) : null}
</li>
);
}
}
export default DogBreed;
Updated DogList component:
import React from "react";
import DogBreed from "./DogBreed";
class DogList extends React.Component {
constructor(props) {
super(props);
this.state = {
headerTitle: this.props.title
};
}
toggleList() {
this.setState(prevState => ({
listOpen: !prevState.listOpen
}));
}
render() {
const { breedInfo } = this.props;
const { headerTitle } = this.state;
return (
<div>
<div>{headerTitle}</div>
{
<ul>
{breedInfo.map(dog => (
<DogBreed
breedName={dog.breedName}
colors={dog.colors}
image={dog.image}
/>
))}
</ul>
}
</div>
);
}
}
export default DogList;
Let me know if you have any questions. For reference, here are the codesandboxes for both options:
Option 1: https://codesandbox.io/s/1x82qq82j
Option 2: https://codesandbox.io/s/v3062w69v0

React rendering html elements in nested loops

I have a data structure like this {key: [array of object]}. I want to render each element in array of object using nested for loop like this:
for each entry(k, v) in map:
for each element in array v:
display html data
I am using react version 16.
I tried this in JSX:
class Positions extends React.Component {
renderPosition(position) {
var expiry = position["ExpiryDay"] + "-" + position["ExpiryMonth"] + "-" + position["ExpiryYear"];
console.log(expiry);
return (<label>{expiry}</label>);
}
render() {
return (
<div>
{this.props.positionsGrouped.forEach(function(positions) {
return (
<div>
{positions.map(function(position) {
return (
<div>
{this.renderPosition(position)}
</div>
);
}.bind(this))}
</div>
);
}.bind(this))}
</div>
);
}
}
Here is the JS that it compiles to:
class Positions extends React.Component {
renderPosition(position) {
var expiry = position["ExpiryDay"] + "-" + position["ExpiryMonth"] + "-" + position["ExpiryYear"];
console.log(expiry);
return React.createElement(
"label",
null,
expiry
);
}
render() {
return React.createElement(
"div",
null,
this.props.positionsGrouped.forEach(function (positions) {
return React.createElement(
"div",
null,
positions.map(function (position) {
return React.createElement(
"div",
null,
this.renderPosition(position)
);
}.bind(this))
);
}.bind(this))
);
}
}
However I don't see anything being rendered except for the top most div. Here is the rendered html:
<div id="app">
<div></div>
</div>
Here is what I see in react developer tools:
<App>
<Positions>
<div></div>
</Positions>
</App>
I don't see any errors in the console. I expected at least three nested divs to be rendered however I only see one so it sounds like something is wrong at the level of the first for loop. But, I do see my expiry variable being printed to console properly so I know renderPosition is getting called with the correct data.
Does anyone know what I am doing wrong? I'm new to react and sorry for any typos. Thanks in advance.
this.props.positionsGrouped.forEach would return undefined. I mean it wouldn't return anything. So nothing gets rendered.
Just change your component code like this
import React from "react";
class Positions extends React.Component {
constructor(props) {
super(props);
this.renderPosition = this.renderPosition.bind(this);
}
renderPosition(position) {
var expiry = position["name"] + "-" + position["title"];
console.log(expiry);
return <label>{expiry}</label>;
}
render() {
const { positionsGrouped } = this.props;
return (
<div>
{positionsGrouped.map(positions => {
const keys = Object.keys(positions);
return (
<div>
{positions[keys[0]].map(position => {
return <div>{this.renderPosition(position)}</div>;
})}
</div>
);
})}
</div>
);
}
}
export default Positions;
Inside your parent file
import React from "react";
import ReactDOM from "react-dom";
import Position from "./test";
import "./styles.css";
function App() {
var positionGroup = [
{
a: [
{
name: "hello",
title: "sdfd"
},
{
name: "hello",
title: "sdfd"
},
{
name: "hello",
title: "sdfd"
}
]
},
{
b: [
{
name: "hello",
title: "sdfd"
},
{
name: "hello",
title: "sdfd"
},
{
name: "hello",
title: "sdfd"
}
]
}
];
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Position positionsGrouped={positionGroup} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The return value of forEach is undefined no matter what you return in callback function. use map instead.
class Positions extends React.Component {
getExpiry(position) {
return `${position.ExpiryDay}-${position.ExpiryMonth}-${position.ExpiryYear}`;
}
render() {
return (
<div>
{this.props.positionsGrouped.map(positions => (
<div>
{positions.map((position) => (
<div>
<label>{this.getExpiry(position)}</label>
</div>
))}
</div>
))}
</div>
);
}
}
I changed your code a little to make it more concise.

Nested React list not working

I am trying to recursively render JSON data to nested list using React. Right now I am using simple data object like this:
[{"id": "1",
"name": "Luke"
},
{"id": "2",
"name": "Jim",
"childNodes":[{
"id": "3",
"name": "Lola"
}]
}]
using this class:
export default class NestedList extends Component {
constructor(props) {
super(props);
this.state = {
visible: true
};
}
toggle = () => {
this.setState({ visible: !this.state.visible });
};
renderChild = (child) => {
if (child.childNodes) {
return (
<ul>
{child.myData.map(item => {
return this.renderChild(item);
})}
</ul>
);
}
else if (child.name) {
return <input type="checkbox"><Child name={child.name}/></input>;
}
return null;
}
render() {
return (
<aside>
<div>
<h4>Data Sets</h4>
<ul>
{this.renderChild(this.props.myData)}
</ul>
</div>
</aside>
);
}
}
which calls a Child class that creates list element:
export default class Child extends Component {
render() {
let {name}=this.props;
return (
<li>{name}</li>
);
}
}
but it doesn't print anything. I have tried removing attribute childNodes altogether and tried to print the list but it doesn't work still. I don't understand where I am doing wrong. I would appreciate some help regarding how to fix this.
You need to map through myData first so the rendering process begins:
<ul>
{this.props.myData.map(data => this.renderChild(data))}
</ul>
Also, on childNodes you need to loop through child.childNodes:
if (child.childNodes) {
return (
<ul>
{child.childNodes.map(node => this.renderChild(node))}
</ul>
);
}
there were couple of issues here:
You passed myData to renderChild which doesn't hold childNodes
property nor name property. Hence none of the conditions were met
(null was returned).
So maybe you should loop through myData and
pass each member of the array to renderChild.
Even if we will pass a valid "child" to the renderChild method,
inside this condition:
if (child.childNodes) {
Again you are using a wrong property:
<ul>
{child.myData.map(item => {
return this.renderChild(item);
})}
</ul>
this should be:
{child.childNodes.map(item => {...
Last thing, You can't nest child elements inside an input element.
so change the layout, maybe like this? :
<input type="checkbox"/>
<Child name={child.name} />
Here is a running example with your code:
const data = [
{
id: "1",
name: "Luke"
},
{
id: "2",
name: "Jim",
childNodes: [
{
id: "3",
name: "Lola"
}
]
}
];
class NestedList extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: true
};
}
toggle = () => {
this.setState({ visible: !this.state.visible });
};
renderChild = child => {
if (child.childNodes) {
return (
<ul>
{child.childNodes.map(item => {
return this.renderChild(item);
})}
</ul>
);
} else if (child.name) {
return (
<div>
<input type="checkbox"/>
<Child name={child.name} />
</div>
);
}
return null;
};
render() {
return (
<aside>
<div>
<h4>Data Sets</h4>
<ul>{this.props.myData.map(item => this.renderChild(item))}</ul>
</div>
</aside>
);
}
}
class Child extends React.Component {
render() {
let { name } = this.props;
return <li>{name}</li>;
}
}
ReactDOM.render(<NestedList myData={data} />, document.getElementById("root"));
<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