react mapping over array of objects - reactjs

My current state is an array of objects. I am mapping over them and I am getting exactly what I want. However, inside of my array of objects I am not receiving the ingredients that I want. I am receiving the console.log of that value but the value it self I am not displaying anything on the dom. Heres my code. I am trying to have my ingredients show up inside of the li that I am mapping but when I click on my panels I am receiving no value. However, my console.log below it shows a value. idk...
import React from 'react';
import Accordian from 'react-bootstrap/lib/Accordion';
import Panel from 'react-bootstrap/lib/Panel';
import Button from 'react-bootstrap/lib/Button';
import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar';
import Modal from 'react-bootstrap/lib/Modal';
import FormGroup from 'react-bootstrap/lib/FormGroup';
import ControlLabel from 'react-bootstrap/lib/ControlLabel';
import FormControl from 'react-bootstrap/lib/FormControl';
export default class App extends React.Component {
constructor () {
super();
this.state = {recipes: [
{recipeName: 'Pizza', ingredients: ['Dough', 'Cheese', 'Sauce']},
{recipeName: 'Chicken', ingredients: ['Meat', 'Seasoning', 'other']},
{recipeName: 'Other', ingredients: ['other1', 'other2', 'other3']}
]};
console.log(this.state.recipes);
}
render() {
const recipes = this.state.recipes.map((recipe, index) => {
return (
<Panel header={recipe.recipeName} eventKey={index} key={index}>
<ol>
{recipe.ingredients.map((ingredient) => {
<li> {ingredient} </li>
console.log(ingredient);
})}
</ol>
</Panel>
)
});
return (
<div className="App container">
<Accordian>
{recipes}
</Accordian>
</div>
)
}
}

Because you are not returning anything from inner map body.
Write it like this:
{recipe.ingredients.map((ingredient) => {
console.log(ingredient);
return <li key={...}> {ingredient} </li> //use return here
})}
Or you can simply write it like this:
{
recipe.ingredients.map((ingredient) => <li key={...}> {ingredient} </li>)
}
As per MDN Doc:
Arrow functions can have either a "concise body" or the usual "block
body".
In a concise body, only an expression is needed, and an implicit
return is attached. In a block body, you must use an explicit return
statement.
Check MDN Doc for more details about Arrow Function.

Related

Re-Rendering a component

I'm doing a simple todo list using React. What I fail to do is to remove an item once I click on the button.
However, if I click delete and then add a new item, it's working, but only if I add a new todo.
Edit:I've edited the post and added the parent componenet of AddMission.
import React,{useState}from 'react';
import { Button } from '../UI/Button/Button';
import Card from '../UI/Card/Card';
import classes from '../toDo/AddMission.module.css'
const AddMission = (props) => {
const [done,setDone]=useState(true);
const doneHandler=(m)=>{
m.isDeleted=true;
}
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li className={mission.isDeleted?classes.done:''} key={mission.id}>
{mission.mission1}
<div className={classes.btn2}>
<Button onClick={()=>{
doneHandler(mission)
}} className={classes.btn}>Done</Button>
</div>
</li>
)) }
</ul>
</Card>
);
};
export default AddMission;
import './App.css';
import React,{useState} from 'react';
import { Mission } from './components/toDo/Mission';
import AddMission from './components/toDo/AddMission';
function App() {
const [mission,setMission]=useState([]);
const [isEmpty,setIsEmpty]=useState(true);
const addMissionHandler = (miss) =>{
setIsEmpty(false);
setMission((prevMission)=>{
return[
...prevMission,
{mission1:miss,isDeleted:false,id:Math.random().toString()},
];
});
};
return (
<div className="">
<div className="App">
<Mission onAddMission={addMissionHandler}/>
{isEmpty?<h1 className="header-title">Start Your Day!</h1>:(<AddMission isVisible={mission.isDeleted} missions={mission}/>)}
</div>
</div>
);
}
const doneHandler=(m)=>{
m.isDeleted=true;
}
This is what is causing your issue, you are mutating an object directly instead of moving this edit up into the parent. In react we don't directly mutate objects because it causes side-effects such as the issue you are having, a component should only re-render when its props change and in your case you aren't changing missions, you are only changing a single object you passed in to your handler.
Because you haven't included the code which is passing in the missions props, I can't give you a very specific solution, but you need to pass something like an onChange prop into <AddMission /> so that you can pass your edited mission back.
You will also need to change your function to something like this...
const doneHandler = (m) =>{
props.onChange({
...m,
isDeleted: true,
});
}
And in your parent component you'll then need to edit the missions variable so when it is passed back in a proper re-render is called with the changed data.
Like others have mentioned it is because you are not changing any state, react will only re-render once state has been modified.
Perhaps you could do something like the below and create an array that logs all of the ids of the done missions?
I'm suggesting that way as it looks like you are styling the list items to look done, rather than filtering them out before mapping.
import React, { useState } from "react";
import { Button } from "../UI/Button/Button";
import Card from "../UI/Card/Card";
import classes from "../toDo/AddMission.module.css";
const AddMission = (props) => {
const [doneMissions, setDoneMissions] = useState([]);
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li
className={
doneMissions.includes(mission.id)
? classes.done
: ""
}
key={mission.id}
>
{mission.mission1}
<div className={classes.btn2}>
<Button
onClick={() => {
setDoneMissions((prevState) => {
return [...prevState, mission.id];
});
}}
className={classes.btn}
>
Done
</Button>
</div>
</li>
))}
</ul>
</Card>
);
};
export default AddMission;
Hope that helps a bit!
m.isDeleted = true;
m is mutated, so React has no way of knowing that the state has changed.
Pass a function as a prop from the parent component that allows you to update the missions state.
<Button
onClick={() => {
props.deleteMission(mission.id);
}}
className={classes.btn}
>
Done
</Button>;
In the parent component:
const deleteMission = (missionId) => {
setMissions(prevMissions => prevMissions.map(mission => mission.id === missionId ? {...mission, isDeleted: true} : mission))
}
<AddMission missions={mission} deleteMission={deleteMission} />

Using constants in React Components

I'm trying to print something to console when a list item is clicked. But the list is defined as a constant outside of the component using it. Obviously the onclick/logItem here is not working. How would you go about this?
Strangely, it logs every list item to the console when the component mounts.
My code:
import React, { Component } from 'react';
const LIST_ITEMS = require('./ListItems.json');
const myList =
LIST_ITEMS.data.Items.map((Item) => (
<li key={Item.id} onClick={logItem(Item.name)}>
{Item.name.toUpperCase()}
</li>
))
;
function logItem(name){
console.log(name);
}
class ItemList extends Component {
render() {
return (
<div id="item-list-wrapper">
<h3>Select an Item</h3>
<ul id="item-list">{myList}</ul>
</div>
);
}
}
export default ItemList;
You have to pass a function to onClick, not the result of calling logItem:
onClick={() => logItem(Item.name)}

Data response not able to map in react js app

I'm new to react and trying to create a eCommerce website. For that I have used a url endpoint to map the data.
http://149.129.128.3:3737/search/resources/store/1/categoryview/#top?depthAndLimit=-1,-1,-1,-1
Well, the second level of subcategories, I'm able to implement successfully, but for the third level of category, I'm not able to render perfectly
I'm sending the screenshot of the json response.
And this is what I have implemented till now, the screen shot.
As you can see, from the screenshot, I'm able to implement the top navigation category and even under it. But there is one more sub level of category-- e.g: under girls section, there are more sub categories which I'm unable to implement.
My code for the same:
topNavigation.js
import React, { Component } from 'react';
import axios from 'axios';
import SubMenu from './subMenu';
class Navigation extends Component {
state = {
mainCategory: []
}
componentDidMount() {
axios.get('http://localhost:3030/topCategory')
.then(res => {
console.log(res.data.express);
this.setState({
mainCategory: res.data.express.catalogGroupView
})
})
}
render() {
const { mainCategory } = this.state;
return mainCategory.map(navList => {
return (
<ul className="header">
<li key={navList.uniqueID}>
<a className="dropbtn ">
{navList.name}
<ul className="dropdown-content">
<SubMenu below={navList.catalogGroupView}/>
</ul>
</a>
</li>
</ul>
)
})
}
}
export default Navigation;
subMenu.js
import React, { Component } from 'react';
import SubListMenu from './subListMenu';
class SubMenu extends Component {
render() {
const {below} = this.props;
return below.map(sub => {
return (
<li key={sub.uniqueID}>
<a>
{sub.name}
<ul>
<SubListMenu subBelow={this.props.below}/>
</ul>
</a>
</li>
)
})
}
}
export default SubMenu;
subListMenu.js
import React, { Component } from 'react';
class SubListMenu extends Component {
render() {
const {subBelow} = this.props;
console.log(subBelow)
return subBelow.map(subl => {
return (
<li key={subl.uniqueID}> <a>{subl.name}</a></li>
)
})
}
}
export default SubListMenu;
Can anyone help me in resolving this issue. I don't know where I'm getting it wrong. I would grateful if someone could guide me on this.
You need is the recursive call to the component. So for example, for the third level you can again call the SubMenu something like this. this will again call the Submenu component and bind your third level (not just third 4,5... etc).
Note: I have not tested this code.
return subBelow.map(subl => {
return (
<React.Fragment>
<li key={subl.uniqueID}> <a>{subl.name}</a></li>
{subl.catalogGroupView !== undefined && <SubMenu below={subl.catalogGroupView} />}
</React.Fragment>
)
});

Dealing with an empty array when using .map() in React

I have a React.JS component that will map the notes variable to display.
However, I have run into the problem of having no notes and receiving an error. What is a proper way to approach this?
Here is the code:
import React, {Component} from 'react';
class List extends Component {
constructor(props){
super(props);
}
render(){
var notes = this.props.items.map((item, i)=>{
return(
<li className="listLink" key={i}>
<p>{item.title}</p>
<span>{item.content}</span>
</li>
)
});
return(
<div className='list'>
{notes}
</div>
);
}
}
export default List;
If you want to render the notes when at least one note exists and a default view when there are no notes in the array, you can change your render function's return expression to this:
return(
<div className='list'>
{notes.length ? notes : <p>Default Markup</p>}
</div>
);
Since empty arrays in JavaScript are truthy, you need to check the array's length and not just the boolean value of an array.
Note that if your items prop is ever null, that would cause an exception because you'd be calling map on a null value. In this case, I'd recommend using Facebook's prop-types library to set items to an empty array by default. That way, if items doesn't get set, the component won't break.
here is the simplest way to deal with
import React, {Component} from 'react';
class List extends Component {
constructor(props){
super(props);
}
render(){
var notes = this.props.items?.map((item, i)=>{
return(
<li className="listLink" key={i}>
<p>{item.title}</p>
<span>{item.content}</span>
</li>
)
});
return(
<div className='list'>
{notes}
</div>
);
}
}
export default List;
just try to add "?" for the array that you maped
You can just setting a fallback value for the this.props.item
render() {
const items = this.props.items || [] // if there's no items, give empty array
const notes = items.map((item, i) => {
return(
....
Doing .map() on empty array will not produces an error, but will return an empty array. Which is fine because empty array is a renderable item in react and won't produce error in render() and will not render the notes as there are no notes provided.
In your component, you can set the defaultProps property to set the initial value to your props:
static defaultProps = {
items: []
};

Displaying Multiple Properties of a component in React

I'm a beginner, learning React using this tutorial as a guide > http://survivejs.com/webpack_react/implementing_notes/
I'm having trouble understanding and figuring out how to make a shopping list with product name, sku, and price as the properties. I know how to pass one property from the tutorial, but multiple properties of a component i'm not sure what I'm doing wrong.
My problem is only the product name is showing on the page. I need the sku and price to show as well, but not understanding how those are not being passed along.
My best assumption is in item.jsx, it is only passing the product and not sku and price so how do I do that?
the export default ({product}) => <div>{product}</div>;
This is how my components are layed out.
APP Component > Lists Component > List Component
App.jsx
import uuid from 'node-uuid'
import React from 'react';
import Items from './Items.jsx';
export default class APP extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{
id: uuid.v4(),
product: 'apples',
sku: '1234',
price: '23.99'
},
{
id: uuid.v4(),
product: 'oranges',
sku: '2345',
price: '24.99'
},
{
id: uuid.v4(),
product: 'strawberries',
sku: '3456',
price: '25.99'
}
]
};
}
render() {
const items = this.state.items;
return (
<div>
<button onClick={this.addItem}>+</button>
<Items items={items} />
</div>
);
}
}
Items.jsx
import React from 'react';
import Item from './item.jsx';
export default ({items}) => {
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
product={item.product}
sku={item.sku}
price={item.price} />
</li>
)}</ul>
);
}
Item.jsx
export default ({product}) => <div>{product}</div>;
You are correctly passing the properties along - You can see this in your Items.jsx file here:
<Item
product={item.product}
sku={item.sku}
price={item.price}
/>
However, in your Item.jsx file, you're not printing the properties out. It may be a little easier to understand if you write your Item.jsx file as your have written your App.jsx file:
export default class Item extends React.Component {
render() {
return (
<div>
{this.props.product}, {this.props.sku}, {this.props.price}
</div>
)
}
}
Instead of passing it as a param of a function, you can reference the properties you pass in using this.props.{propName} - it's a more widely used way of writing React components, and it looks like the tutorial you're using goes on to using React props a little further down the page.
You're using a stateless function component, which is a function that receives props as a parameter. You can actually write it like this:
export default (props) => (
<div>
<span>{ this.props.product }</span>
<span>{ this.props.sku }</span>
<span>{ this.props.price }</span>
</div>;
);
However, since ES6 supports destructuring assignment on objects, we can assign the props object properties to variables:
export default ({ product, sku, price }) => (
<div>
<span>{ product }</span>
<span>{ sku }</span>
<span>{ price }</span>
</div>;
);

Resources