I'm currently working on react state updates.
The data that I'm trying to update is a certain key-value pair in an array of objects.
When I click the thumbs-up button, the value in the 'likes' key must increase.
The problem I am facing is that the likes button will increase by 2 from the second click.
How should I handle this problem?
The code I was working on is:
import React, {useState} from 'react';
import './App.css';
function App() {
const [data, setData] = useState([{id: 0, title: '남자 코트 추천', date: '2월 17일', likes: 0}, {id: 1, title: '여자 코트 추천', date:'2월 18일', likes:0}, {id: 2, title: '강남 맛집 추천', date:'2월 19일', likes:0},]);
const handleLikesClick = (event) => {
event.stopPropagation()
setData((prevState)=>{
const updateData = [...prevState]
const id = parseInt(event.target.getAttribute("value"))
updateData[id].likes += 1;
return updateData
})
}
return (
<div className="App">
<div className="black-nav">
<div>개발 Blog</div>
</div>
{data.map(post => (
<div className="list" key={post.id}>
<h3>{post.title} <span onClick={handleLikesClick} value={post.id}>👍</span>{post.likes}</h3>
<p>{post.date}</p>
<hr/>
</div>
))}
</div>
);
}
export default App;
The problem was that I had Strict Mode on in index.js, which makes my component to render twice.
I omitted Strict Mode in index.js and the problem was solved.
ReactDOM.render(<App />,
document.getElementById('root')
);
Related
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}) => {
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.
So im following along with a book "The Road To React" By: Robin Wieruch. The book has you do everything inside the App.js file...which is extremely disappointing because this is obviously terrible practice. Anyway, ive extracted everything else to their own component files and i cant figure out how to extract the search to its own file. Ive attached an img showing the folder structure.
In App.js im trying to extract everything, leaving a return that returns the components like it should be. As it is the App.js file looks like this:
import React, { useState } from 'react';
import './App.css';
import Greeting from '../Greeting/Greeting';
import Search from '../Search/Search';
import List from '../List/List';
const useSemiPersistentState = (key, initialState) => {
const [value, setValue] = React.useState(
localStorage.getItem(key) || initialState
);
React.useEffect(() => {
localStorage.setItem(key, value);
}, [value, key]);
return [value, setValue]
};
// App component
const App = () => {
const stories = [
{
title: 'React',
url: 'https://reactjs.org/',
author: 'Jordan Walke',
num_comments: 3,
points: 4,
objectID: 0,
},
{
title: 'Redux',
url: 'https://redux.js.org/',
author: 'Dan Abramov, Andrew Clark',
num_comments: 2,
points: 5,
objectID: 1,
},
];
// Set state on searchTerm, setSearchTerm with custom hook
const [searchTerm, setSearchTerm] = useSemiPersistentState(
'search',
'React'
);
// Get the value of search input
const handleSearch = (event) => {
setSearchTerm(event.target.value);
};
// Check if user input matches stories array
// toLowerCase() both values
const searchedStories = stories.filter((story) => story.title.toLowerCase().includes(searchTerm.toLowerCase())
);
// Render
return (
<div className="App">
<Greeting name="Colin" age="28" occupation="Front-end developer" />
<InputWithLabel
id="search"
value={searchTerm}
isFocused
onInputChange={handleSearch}
>
<strong>Search:</strong>
</InputWithLabel>
<hr />
<List list={searchedStories} />
</div>
);
}
// Search bar
// Destructure props search, onSearch
const InputWithLabel = ({ id, value, type = "text", onInputChange, isFocused, children, }) => {
const inputRef = React.useRef();
React.useEffect(() => {
if (isFocused && inputRef.current) {
inputRef.current.focus();
}
}, [isFocused]);
return (
<>
<label htmlFor={id}>{children}</label>
<input
ref={inputRef}
id={id}
type={type}
value={value}
autoFocus={isFocused}
onChange={onInputChange}
/>
</>
)
}
export default App;
The List component which is a parent of the Item component looks like so:
import React from 'react';
import Item from '../Item/Item';
function List({ list }) {
return (
<ul>
{list.map(({ objectID, ...item }) => (
<Item key={objectID} {...item} />
))}
</ul>
);
}
export default List;
The Item component looks like so:
import React from 'react';
function Item({ title, url, author, num_comments, points }) {
return (
<div>
<li>
<span>
<a href={url}>{title}</a>
</span>
<span> {author}</span>
<span> {num_comments} comments,</span>
<span> {points} points.</span>
</li>
</div>
);
}
export default Item;
The Greeting Component and Helpers folder are unrelated in any way so i wont post them.
Just as a note all of the code in here works... and at the time im not really interested in refactoring this unless you care to. Im just trying to extract all of that nonsense that relates to what should be a separate Search component out of App.js and into Search.js. Have been trying and ive hit a wall with this as im still new with react.
Here are the errors shown once i move all of the Search content related code from App.js to Search.js and attempt to import to the Search component into App.js
****Failed to compile****
src/Components/App/App.js
Line 60:8: 'InputWithLabel' is not defined react/jsx-no-undef
Line 62:16: 'searchTerm' is not defined no-undef
Line 64:24: 'handleSearch' is not defined no-undef
Line 69:19: 'searchedStories' is not defined no-undef
Search for the keywords to learn more about each error.
I have 2 buttons and 1 h1 I need to count how many times the button is clicked and display the button name with no. of count it clicked in h1 with the name of the button. Right now I am getting the name of button correctly but not able to display the click count for the button. please help
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'
export default class App extends Component {
state={
button:"",
countClick:0
}
buttonClick=(e)=>{
e.preventDefault();
this.setState({button:e.target.name, countClick:this.state.countClick+1});
}
render() {
return (
<Fragment>
<h1>
{this.state.button} clicked {this.state.countClick} times
</h1>
<button name="Button 1" type="button" onClick={this.buttonClick}>Button 1</button>
<button name="Button 2" type="button" onClick={this.buttonClick}>Button 2</button>
</Fragment>
)
}
}
ReactDOM.render(<App />,document.getElementById('root'));
Thank You
Some notice points:
build an Array to store each button's click amount status.
optionally use {id, value} structure for the array items, since we need to display the clicked button with its click amount.
use map() to prevent from repeating yourself about <button />.
pass the identity index as a param inside the nested handler function.
Full code:
import React from "react";
import "./styles.css";
const amount = 2;
export default function App() {
const [clickList, setClickList] = React.useState(
[...Array(amount).keys()].map(x => ({ id: `Button ${x + 1}`, value: 0 }))
);
const [activeButton, setActiveButton] = React.useState("");
const buttonClick = idx => e => {
const result = [...clickList];
result[idx].value += 1;
setClickList(result);
setActiveButton(e.target.name);
};
return (
<>
<h1>
{`${activeButton} clicked ${clickList.find(x => x.id === activeButton)?.value ?? 0} times`}
</h1>
{clickList.map((x, idx) => (
<button name={x.id} type="button" onClick={buttonClick(idx)}>
{x.id}
</button>
))}
</>
);
}
setState can take an object or a callback with previous state as parameter.
Change your setState like this:
this.setState(prevState => ({button:e.target.name, countClick: prevState + 1}));
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).