Passing an object array to child component - javascript

I am working on the front-end of a bootcamp project and I am having an issue when passing an object array to a child component.
For now, I am creating a variable array to test components. When using a map on the array in the parent component it works fine, but when passing the array to the child component and then using map function on it I get an error : arr.map is not a function.
Parent component :
import React from 'react'
import BottomNav from '../components/BottomNav'
import RecipeListContainer from '../components/RecipeListContainer'
const UserHome = () => {
let recipeSample = [{id: 1,title: "Crispy pork belly lechon", category: "Filipino classics", author:"Alex"}, {id: 2,title: "Beef brocolli with scallions", category: "Chinese classics", author:"Wu Han"}];
return (
<div>
<RecipeListContainer title="Featured recipes" recipeSample={recipeSample}/>
<BottomNav selected="explore"/>
</div>
)
}
export default UserHome
Child component
import React from 'react'
import classes from './recipeListContainer.module.scss'
const RecipeListContainer = (title, recipeSample) => {
return (
<div className={classes.container}>
<h1 className={classes.listTitle}>{title}</h1>
{recipeSample.map((recipe) => (
<div key={recipe.id} className={classes.recipeCard}>
<div className={classes.recipeImage}></div>
<div className={classes.recipeDescription}>
<h4>{recipe.title}</h4>
<p>{recipe.category}</p>
<p>Created by : {recipe.author}</p>
<button type="button">Remove from menu</button>
</div>
</div>
))}
</div>
)
}
export default RecipeListContainer
I am not sure what I am doing wrong, and when comparing to a working versions of a similar feature (from a different project) it seems I am not missing anything.

Need to decompose the props. change this
const RecipeListContainer = (title, recipeSample) => {
to
const RecipeListContainer = ({title, recipeSample}) => {

Related

Data not being passed from parent to child. (ReactJS)

I am currently making a Trello Clone. It has been going well so far and I've had a lot of help from everyone here, so thank you!
My current issue is that I am trying to pass the state of modalData in App.js to <ModifyModal />.
I have tried researching and Googling, and even re-writing functions and creating new ones. However, nothing had worked. I know that the state is being updated with the correct text since I made the title from Trello Clone! to {modalData} and it worked. I want the data of modalData to be passed from App.js to <ModifyModal />.
Edit: Made a functional component and it is still showing undefined for the data.
App.js:
import React, { Component } from 'react';
import './App.css';
import Todobox from './Todobox';
import ModifyModal from './ModifyModal';
import Item from './Item';
const Widget = ({parentCallback2}) => <Todobox parentCallback2={parentCallback2}/>
const Widget2 = () => <ModifyModal />
class App extends Component {
constructor(props){
super(props);
this.handleCallback = this.handleCallback.bind(this);
this.state={
elements: [],
modal: [],
modalData: null
}
}
// Creates new element box
handleNewElement = () => {
const newElement = [...this.state.elements, Widget];
this.setState({
elements: newElement
});
}
handleCallback = (itemWidget, itemData) =>{
const newModal = [...this.state.modal, itemWidget];
const newData = itemData;
this.setState({
modal: newModal,
modalData: newData
});
}
render() {
const { elements, modal, modalData } = this.state;
return (
<>
<div className='page-container'>
<div className='header'>
<a className='header-title'>{modalData}</a>
<a className='header-button' onClick={this.handleNewElement.bind(this)}>Create a list</a>
</div>
<div className='element-field'>
{elements.length !== 0 &&
elements.map((Widget, i) => <Widget key={i} parentCallback2={this.handleCallback}/>)}
</div>
</div>
{modal.length !== 0 &&
modal.map((Widget2, i) => <Widget2 key={i} itemDataToChild={modalData} />)}
</>
);
}
}
export default App;
ModifyModal.jsx:
import React from "react";
import { useState } from "react";
import trash from './trash_can.png';
import './App.css'
function ModifyModal({ itemDataToChild }){
const [hideModal, setHideModal] = useState(false);
const [content, setContent] = useState(itemDataToChild);
const handleCancel = () =>{
setHideModal(true);
}
return(
<>
<div className={`modify-modal-container ${hideModal ? 'modify-modal-container-hide' : ''}`}>
<div className='modify-modal'>
<a className='modify-title'>{content}</a>
<textarea className='modify-input' />
<div className='modify-buttons'>
<a className='modify-btn' id='modify-update-btn'>Update</a>
<a className='modify-btn' id='modify-cancel-btn' onClick={handleCancel}>Cancel</a>
<img src={trash} id='modify-delete'/>
</div>
</div>
</div>
</>
)
}
export default ModifyModal;
Any help is appreciated since I am new to this. :)
The problem is when you declared and initialized Widget2.
const Widget2 = () => <ModifyModal />
What is actually happening under the hood is that Widget2 received a function which returns a JSX.Element, it didn't actually become ModifyModal, the functional component.If you look at the line above is actually doing right.
const Widget = ({parentCallback2}) => <Todobox parentCallback2={parentCallback2}/>
There is 2 solution for this.
you can do just as Widget.
const Widget2 = ({itemDataToChild}) => <ModifyModal itemDataToChild={itemDataToChild}/>
Which I think should be the best approach since you can just rename your imports if was exported as default, and deleting the line const Widget2 = () => <ModifyModal />
import Widget2 from './ModifyModal';
Keeping in mind that the second approach would result error if used for Named Exports. Imports Reference.
For broad your understanding of JSX element and functional component I recommend take a look at their official documentation.
JSX, Components and Props

React setState is not updating my react app

I am working on a simple react app. My task is to update the product price as the quantity updates. I have an array of products in my App.js file.
And also two functions for increment and decrement of the quantity of the product.
import "./App.css";
import Navbar from "./components/navbar";
import ProductList from "./components/ProductList";
import Footer from "./components/Footer";
import React, { useState } from "react";
function App() {
let productList = [
{
price: 99999,
name: "Iphone 10S Max",
quantity: 0,
},
{
price: 999,
name: "Realme 9 Pro",
quantity: 0,
},
];
let [products, setState] = useState(productList);
// console.log(useState(productList));
const incrementQty = (inx) => {
let newProductList = [...products];
newProductList[inx].quantity++;
console.log(newProductList);
setState(newProductList);
};
const decrementQty = (inx) => {
let newProductList = [...products];
newProductList[inx].quantity > 0
? newProductList[inx].quantity--
: (newProductList[inx].quantity = 0);
setState(newProductList);
};
return (
<React.StrictMode>
<Navbar />
<div className="mt-5">
<ProductList
productList={productList}
incrementQty={incrementQty}
decrementQty={decrementQty}
/>
</div>
<Footer />
</React.StrictMode>
);
}
export default App;
I pass the function and the productList array to my ProductList component.
This is the ProductList.js file.
import React from "react";
import Product from "./Product";
export default function ProductList(props) {
return props.productList.map((product, i) => {
return (
<Product
product={product}
key={i}
incrementQty={props.incrementQty}
decrementQty={props.decrementQty}
index={i}
></Product>
);
});
}
and then I pass the function and product index to Product component which will render out my products. This is the Product.js file
import React from "react";
export default function Product(props) {
return (
<div className="container">
<div className="row">
<div className="col-lg-4 col-sm-12 col-md-6">
<h2>
{props.product.name}
<span className="badge bg-success">₹ {props.product.price}</span>
</h2>
</div>
<div className="col-lg-4 col-md-6 col-sm-12">
<div className="btn-group" role="group" aria-label="Basic example">
<button
type="button"
className="btn btn-success"
onClick={() => {
props.incrementQty(props.index);
}}
>
+
</button>
<button type="button" className="btn btn-warning">
{props.product.quantity}
</button>
<button
type="submit"
className="btn btn-danger"
onClick={() => {
props.decrementQty(props.index);
}}
>
-
</button>
</div>
</div>
<div className="col-lg-4 col-sm-12 col-md-6">
<p>{props.product.quantity * props.product.price}</p>
</div>
</div>
</div>
);
}
As you can see, I am using onClick event listener for my increase and decrease buttons. When I click the button, the quantity is updated, but that's not reflected in my DOM.
How can I fix this?
In addition to the other suggestions which should fix your issues (ie: using productList={products}), you're also currently modifying your state directly, which down the line can lead to other UI problems.
When you do:
let newProductList = [...products];
you're creating a shallow copy of the products array, but the objects within that array are still the original references to your state and are not copies. As a result, you need to create a new object within your new array when you want to update it to avoid rerender issues. One possible way is to use:
const newProductList = [...products];
const toUpdate = newProductList[inx];
newProductList[inx] = {...toUpdate, quantity: toUpdate.quantity+1};
setState(newProductList);
The same applies to your decrement logic. Here I've provided a slightly different example where I'm using the state setter function, but you can also use the above approach. I've created a copy of the state in the arguments and then used Math.max() instead of the ternary:
setState(([...products]) => {
const toUpdate = newProductList[inx];
products[inx] = {...toUpdate, quantity: Math.max(0, toUpdate.quantity-1)
return products;
});
Always treat your state as read-only/immutable to avoid unexpected bugs with your UI. Other ways of achieving the above is to use .map() to create a new array and then replace your object when your map reaches the object that matches your inx value.
In you App.js, change
productList={productList}
to
productList={product}
Just noticed the actual problem. You are passing down the default value for the state and not the state's value.

React child callback not being executed after being passed down twice

I am working on the following project https://github.com/codyc4321/react-udemy-course section 11 the videos app. The udemy course is found at https://www.udemy.com/course/react-redux/learn/lecture/12531374#overview.
The instructor is passing a callback down to multiple children and calling it in the lowest videoItem and the code is supposed to console log something out. I have no console log in my browser even though I've copied the code as written and double checked for spelling errors.
At the main level is App.js:
import React from 'react';
import youtube from '../apis/youtube';
import SearchBar from './SearchBar';
import VideoList from './VideoList';
class App extends React.Component {
state = {videos: [], selectedVideo: null};
onTermSubmit = async term => {
const response = await youtube.get('/search', {
params: {
q: term
}
});
// console.log(response.data.items);
this.setState({videos: response.data.items});
};
onVideoSelect = video => {
console.log('from the app', video);
}
render() {
return (
<div className="ui container">
<SearchBar onFormSubmit={this.onTermSubmit} />
<VideoList
onVideoSelect={this.onVideoSelect}
videos={this.state.videos} />
</div>
)
}
}
export default App;
videoList.js
import React from 'react';
import VideoItem from './VideoItem';
const VideoList = ({videos, onVideoSelect}) => {
const rendered_list = videos.map(video => {
return <VideoItem onVideoSelect={onVideoSelect} video={video} />
});
return <div className="ui relaxed divided list">{rendered_list}</div>;
};
export default VideoList;
the videoItem.js
import React from 'react';
import './VideoItem.css';
const VideoItem = ({video, onVideoSelect}) => {
return (
<div onClick={() => onVideoSelect(video)} className="item video-item">
<img
src={video.snippet.thumbnails.medium.url}
className="ui image"
/>
<div className="content">
<div className="header">{video.snippet.title}</div>
</div>
</div>
);
}
export default VideoItem;
The code that isn't running is
onVideoSelect = video => {
console.log('from the app', video);
}
My guess is that it has something to do with a key prop not being present in the map - I'm not super well versed with class components but I can't find anything else funky so maybe try adding a unique key prop in the map.
When rendering components through a map react needs help with assigning unique identifiers to keep track of re-renders etc for performance, that also applies to knowing which specific instance called a class method.
If you don't have a unique ID in the video prop you can use an index in a pinch, although ill advised, it can be found as the second parameter in the map function. The reason it's ill advised to use an index is if there are multiple children with the same index in the same rendering context, obviously the key parameter could be confused.
Okay-ish:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={index} onVideoSelect={onVideoSelect} video={video} />});
Better:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={video.id} onVideoSelect={onVideoSelect} video={video} />});

Render component stored in record in React with dynamic string key

This is something that is easy to work around, but I was wondering if this is possible. Is it achievable at all to render a React component by accessing an object with a dynamic key?
Trying the below shows that the expected way of doing it is invalid syntax in JSX. I understand I could store the active object in the map in the state or conditionally map the object entries, but I couldn't seem to find any questions regarding this and was hoping to see if anyone has any experience with this.
Thanks for your help.
Here's the setup I have:
import React, { useState } from "react"
import {ComponentOne, ComponentTwo, ComponentThree} from "../directory"
const map = {
k1 = { name="Component 1", child=ComponentOne }
k2 = { name="Component 2", child=ComponentTwo }
k3 = { name="Component 3", child=ComponentThree }
}
const myComponent = () => {
const [active, setActive] = useState("k1")
return (
<>
<div>
{
Object.entries(map).map(([k, v]) =>
<h1 onClick={() => setActive(k)}>{ v.name }</h1>
)
}
</div>
<div>
< map[active].child />
</div>
<>
)
}
All components in the end are functions or classes that you can get a reference to and therefore access dynamically through any object.
JSX is simply a unique syntax to call that function. So first get a reference to the React component and then use JSX to render the component.
Solution Code:
import React, { useState } from "react";
import { ComponentOne, ComponentTwo, ComponentThree } from "../directory";
const map = {
k1: { name: "Component 1", child: ComponentOne },
k2: { name: "Component 2", child: ComponentTwo },
k3: { name: "Component 3", child: ComponentThree },
};
const myComponent = () => {
const [active, setActive] = useState("k1");
// Store the reference to the component you want to render in a variable
const ActiveChild = map[active].child;
return (
<React.Fragment>
<div>
{Object.entries(map).map(([k, v]) => (
<h1 onClick={() => setActive(k)}>{v.name}</h1>
))}
</div>
<div>
{/* Since the variable holds reference to a React component, you can render it JSX syntax */}
<ActiveChild />
</div>
</React.Fragment>
);
};

React and PropTypes

Some time ago I tried to make my first steps in React. Now I'm trying to create simple ToDo list app. One of my colleagues told me that it will be nice to use PropTypes with shape and arrayOf. I have one component which pass data to another and here is my issue(List.js and ListItem.js). I'm not sure when, where and how to use propTypes with shape and arrayOf. Which approach is right? Define PropTypes in parent component or in child component? Maybe there is a better idea? Please find my code below, it will be nice if you can clarify me how to use PropTypes. I tried to search something in Web, but I still don't get it.
Main App.js file:
import React from "react";
import "./styles/App.scss";
import List from "./components/List/List";
import AppHeader from "./components/AppHeader/AppHeader";
function App() {
return (
<div className="App">
<AppHeader content="TODO List"></AppHeader>
<List></List>
</div>
);
}
export default App;
AppHeader.js file:
import React from "react";
import "./AppHeader.scss";
export default function AppHeader(props) {
return (
<div className="header">
<h1 className="header-headline">{props.content}</h1>
</div>
);
}
List.js file:
import React from "react";
import "./List.scss";
import ListItem from "../ListItem/ListItem";
import _ from "lodash";
const initialList = [
{ id: "a", name: "Water plants", status: "done" },
{ id: "b", name: "Buy something to eat", status: "in-progress" },
{ id: "c", name: "Book flight", status: "in-preparation" }
];
const inputId = _.uniqueId("form-input-");
// function component
const List = () => {
const [value, setValue] = React.useState(""); // Hook
const [list, setList] = React.useState(initialList); // Hook
const handleChange = event => {
setValue(event.target.value);
};
// add element to the list
const handleSubmit = event => {
// prevent to add empty list elements
if (value) {
setList(
list.concat({ id: Date.now(), name: value, status: "in-preparation" })
);
}
// clear input value after added new element to the list
setValue("");
event.preventDefault();
};
// remove current element from the list
const removeListItem = id => {
setList(list.filter(item => item.id !== id));
};
// adding status to current element of the list
const setListItemStatus = (id, status) => {
setList(
list.map(item => (item.id === id ? { ...item, status: status } : item))
);
};
return (
<div className="to-do-list-wrapper">
<form className="to-do-form" onSubmit={handleSubmit}>
<label htmlFor={inputId} className="to-do-form-label">
Type item name:
</label>
<input
type="text"
value={value}
onChange={handleChange}
className="to-do-form-input"
id={inputId}
/>
<button type="submit" className="button to-do-form-button">
Add Item
</button>
</form>
<ul className="to-do-list">
{list.map(item => (
<ListItem
name={item.name}
status={item.status}
key={item.id}
id={item.id}
setListItemStatus={setListItemStatus}
removeListItem={removeListItem}
></ListItem>
))}
</ul>
</div>
);
};
export default List;
And ListItem.js file:
import React from "react";
import PropTypes from "prop-types";
import "./ListItem.scss";
const ListItem = props => {
return (
<li className={"list-item " + props.status} key={props.id}>
<span className="list-item-icon"></span>
<div className="list-item-content-wrapper">
<span className="list-item-text">{props.name}</span>
<div className="list-item-button-wrapper">
<button
type="button"
onClick={() => props.setListItemStatus(props.id, "in-preparation")}
className="button list-item-button"
>
In preparation
</button>
<button
type="button"
onClick={() => props.setListItemStatus(props.id, "in-progress")}
className="button list-item-button"
>
In progress
</button>
<button
type="button"
onClick={() => props.setListItemStatus(props.id, "done")}
className="button list-item-button"
>
Done
</button>
<button
type="button"
onClick={() => props.removeListItem(props.id)}
className="button list-item-button"
>
Remove
</button>
</div>
</div>
</li>
);
};
ListItem.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
name: PropTypes.string,
status: PropTypes.string,
removeListItem: PropTypes.func,
setListItemStatus: PropTypes.func
};
export default ListItem;
Props are per component.
The purpose of prop types is to let you make some type checking on a component's props.
It seems like you did it correctly in the ListItem component.
Basically, you just list all of the component's props and what type they should be.
Like you did here:
ListItem.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
name: PropTypes.string,
status: PropTypes.string,
removeListItem: PropTypes.func,
setListItemStatus: PropTypes.func
};
I am not exactly sure where will the propTypes shape and arrayOf be used, but generally, PropTypes are useful when you are trying to render a child component within a parent component, and you want to carry out TypeChecking on the props of the relative child component.
In your scenario, arrayOf and shape can be used in one of the props within your component whereby the prop is an array of a certain type (arrayOf), and an object of a certain type(shape, or exact).

Categories

Resources