I am a few days into learning React JS. It's been very interesting and simplifies JavaScript, at least how I view it. My goal at the moment is to create a button that when clicked will create a new card with a random number generated in the card.
I can't figure out how to add the onClick= functionality for it to work the way I'm envisioning it. At the moment I have the code for the button written on my index.js file. index.js is my main file, it is where my App component is located. I see people use a file named App.js, not sure why, but that's another question for later.
Currently my buttons are stylized the following way.
const App = () => {
return (
<body>
<header>
<div className="ui buttons">
<button className="ui button mb-1 mt-1">
<i className="plus icon"></i>
Add Card
</button>
<div className="or mb-1 mt-1"></div>
<button className="ui positive button mb-1 mt-1">
<i className="sort numeric down icon"></i>
Sort All
</button>
</div>
</header>
They are located in a header container at the top of my page. It looks like this following image.
I want to be able to click the Add Card button and then for it to create a new card in my card container. As picture below.
The current card you see in the above image is hard coded at the moment. I created a MainCard.js component that houses the code for the card including the functionality of the randomly generated number and the styling.
import "bootstrap/dist/css/bootstrap.css";
import React from "react";
import { Card, Button } from "react-bootstrap";
let getRandomNumber = function (min, max) {
let getRandom = Math.floor(Math.random() * max + min);
return getRandom;
};
const MainCard = () => {
return (
<Card>
<Button
className="ui mini red basic icon button"
style={{
position: "absolute",
top: "0",
right: "0",
}}
>
<i
className="red x icon"
style={{
position: "relative",
top: "0",
right: "0",
}}
></i>
</Button>
<Card.Body>{getRandomNumber(0, 101)}</Card.Body>
</Card>
);
};
export default MainCard;
Previously I was doing the onClick functionality in my AddCard.js but for some reason the Alert I created was both being generated when the page was being reloaded as well as when the button was clicked. I couldn't figure out the reasoning for this and it has become a wall that I haven't been able to climb over yet. I'm pretty sure I'm missing something simple, but I just can't figure out what that is. Any ideas how I can achieve this?
You need to use useState().
For example.
import { useState } from "react";
export default function App() {
const [{items}, setItems] = useState({ items: [] });
const addItem = () => {
items.push(<div key={items.length}>Hello</div>);
setItems({ items: [...items] });
};
return (
<div>
<button onClick={addItem} />
{items}
</div>
);
}
This creates a persistant storage that you can set values into that remain between component rebuilds. The object in storage has a single key "items" with an array containing the items. It is convenient to put your state in an object with a key that describes the contents. This allows you to tell what state object you are looking at if you need to debug your app.
The addItem() callback will add a new item into the object in state. Importantly, the item has a key attribute that should be unique. This helps react monitor the state of elements in an array.
You can see a demo of the above code here
As stated on Charles Bamford's response, you'll have to use the useState method. The useState method creates a variable and a method to set the value of this variable. Everytime you change a state (using it's setter) React will check the code and re-render everywhere it was used. You will need something like this:
const App = () => {
const [cards, setCards] = useState([]); // instantiate cards as a empty Array
const addCard = () => {
// create a new array with the old values and the new random one
const newCards = [...cards, Math.floor(Math.random() * 100)];
setCards(newCards);
};
const removeCard = (cardIndex) => {
// create a new array without the item that you are removing
const newCards = cards.filter((card, index) => index !== cardIndex);
setCards(newCards);
};
return (
<body>
<header>
<div className="ui buttons">
<button className="ui button mb-1 mt-1" onClick={() => addCard()}>
<i className="plus icon"></i>
Add Card
</button>
<div className="or mb-1 mt-1"></div>
<button
className="ui positive button mb-1 mt-1"
onClick={() => addCard()}
>
<i className="sort numeric down icon"></i>
Sort All
</button>
</div>
</header>
<main>
{cards.map((cardNumber, index) => (
<MainCard number={cardNumber} onRemove={() => removeCard(index)} />
))}
</main>
</body>
);
};
With an array like this [10, 15, 33], the cards.map will do something like this for you:
<main>
<MainCard number={cards[0]} onRemove={() => removeCard(0)} /> // 10
<MainCard number={cards[1]} onRemove={() => removeCard(1)} /> // 15
<MainCard number={cards[2]} onRemove={() => removeCard(2)} /> // 33
</main>
We are passing the "number" and the "onRemove" function from the App component to the MainCard component. Now we just have to get it from our props, like this:
const MainCard = (props) => {
const { onRemove, number } = props
return (
<Card>
<Button
className="ui mini red basic icon button"
style={{
position: "absolute",
top: "0",
right: "0",
}}
onClick={() => onRemove()}
>
<i
className="red x icon"
style={{
position: "relative",
top: "0",
right: "0",
}}
></i>
</Button>
<Card.Body>{number}</Card.Body>
</Card>
);
};
export default MainCard;
Related
I know about props in React and how to pass props but I'm struggling to pass props to a Component that is just an object.
VideoGameList.js
const VideoGameList = [
{
id: 1,
title: "Fire Emblem Engage",
releaseDate: "01/20/2023",
price: 59.99,
quantity: 0,
},
];
My VideoGameList component is holding this array that is just an object of newly released video games. Inside each object is a quantity value that will increase from a function in another component.
VideoGame.js
const VideoGame = () => {
const [quantity, setQuantity] = useState(0);
const addToCart = () => {
setQuantity(quantity + 1);
};
return (
<>
<Header />
<div>
{VideoGameList.map(
({ title, releaseDate, price, quantity }, index) => (
<div>
<img key={src} src={src} alt={title} />
<div>
<p>{title}</p>
<p>{releaseDate}</p>
</div>
<div>
<p>${price}</p>
<button onClick={addToCart}>
Add to Cart
</button>
</div>
</div>
)
)}
</div>
</>
);
};
Inside this component you'll notice a few things. The first is I'm using the map method to display each video game in its own div. Inside the div is a picture of the game cover, the price, the release date and I have the quantity. My addToCart function increases the useState of quantity and I have a button with an onClick to increase it.
My problem is I can't seem to figure out how to use the addToCart function from my VideoGame component to increase the quantity in my VideoGameList component. The reason I'm doing this is because I have another page that will only show games that have a quantity of > 0. Please advice.
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.
I'm using react-days-picker for accessibility reasons.
I would like to replace the current span tags inside the navbar with button tags.
I could extend it with the navbarElement prop but then my custom component keeps re-rendering each time I click or press a key to change months. It would lose focus.
To change a month, I could only press the key down key once or I had to .press the tab key each time to get the focus again
I'm even following the code included in the documentation (https://react-day-picker.js.org/examples/elements-navbar) but the result is the same:
function Navbar({
nextMonth,
previousMonth,
onPreviousClick,
onNextClick,
className,
localeUtils,
}) {
const months = localeUtils.getMonths();
const prev = months[previousMonth.getMonth()];
const next = months[nextMonth.getMonth()];
const styleLeft = {
float: 'left',
};
const styleRight = {
float: 'right',
};
return (
<div className={className}>
<button style={styleLeft} onClick={() => onPreviousClick()}>
← {prev.slice(0, 3)}
</button>
<button style={styleRight} onClick={() => onNextClick()}>
{next.slice(0, 3)} →
</button>
</div>
);
}
export default function Example() {
return (
<div>
<DayPicker weekdayElement={<Weekday />} navbarElement={<Navbar />} />
</div>
);
}
I'm creating a todo list with React and found out that we use states in react unlike innerHTML or appendChild() in Javascript.
Here's where I'm facing the problem:
When a user clicks a Button, a simple todo is added to the parent Div, and I mean 'Added not Replaced.
However, when using react hooks useState(), It's just replacing the element, but I want to Add it to the div. Here's the code:
export default function TodoContainer() {
let [item, setItem] = useState('Nothing to show...');
function handleClick() {
setItem(item += <TodoItems/>)
}
return (
<div className="wholeContainer">
<div className="tododiv">
<span className="todos">Todos: </span>
<hr/>
{item}
</div>
<button className="add" onClick={handleClick}>
<i className="fas fa-plus"></i>
Add a Todo
</button>
</div>
);
}
So, when using setItem(item + <TodoItem/>), it just shows : [object Object]
Please help me, I don't know almost nothing about react as I just recently started learning it.
By the way, the <TodoItems/> returns a div with a todo and its detail inside of it.
Thanks.
Yes, when you item += <TodoItems/> you are appending a JSX literal to a (possibly) string, and the result seems to be interpreted as a Javascript object. Objects are not valid JSX and renderable.
You may want an array of todos for the state, and when adding a new todo add only the data, not the JSX. When adding a todo to the array you need to create a new array reference and shallow copy the previous state. You'll map the state to JSX in the render return.
Example:
export default function TodoContainer() {
const [todos, setTodos] = useState([]);
function handleClick() {
setTodos(todos => [...todos, "new todo"]);
}
return (
<div className="wholeContainer">
<div className="tododiv">
<span className="todos">Todos: </span>
<hr/>
{todos.length
? todos.map(todo => <TodoItems key={todo} todo={todo} />)
: 'Nothing to show...'
}
</div>
<button className="add" onClick={handleClick}>
<i className="fas fa-plus"></i>
Add a Todo
</button>
</div>
);
}
I have a list consisting of React components:
<div className="card-list">
{cards.map((card, i) => (
<Card card={card} key={card.cardId} index={i}/>
))}
</div>
The problem is that clicking on a card should open a modal window for the card that was clicked. To call the modal window I use the following code inside the Card component:
const Card = ({card, key, index}) => {
const dispatch = useDispatch();
const showModal = useSelector(state => state.modal.showContentModal);
return (
<div>
//this is card view
<div onClick={() => dispatch(showContentModal())}>
<h3>{card.name}</h3>
</div>
//this is modal window
<div id={`modal-overlay-container-${key}`} className={`modal-overlay ${showModal && "active"}`}>
<div id={`modal-div-${key}`} className={`modal ${showModal && "active"}`}>
<p className="close-modal" onClick={() => dispatch(hideContentModal())}>
<svg viewBox="0 0 20 20">
<path fill="#8e54e9" d="..."/>
</svg>
</p>
<div className="modal-content">
<CardContent card={card} index={index}/>
</div>
</div>
</div>
</div>
)
}
export default Card;
The showСontentModal() action changes the Boolean flag, which makes the modal window active, and hideModalContent() does the opposite. But because all the components in the list are linked to a single action, clicking on any card opens a modal window for all of them. Is there any way to trigger an action only for a specific card?
I see two ways you could solve this.
Use local state inside card component instead of redux state
Use card id instead of boolean in state.modal.showContentModal
If you don't need state.modal.showContentModal in other components then I would prefer first option.
const [showModal, setShowModal] = useState(false)
const handleOpenModal = () => setShowModal(true)
<div onClick={handleOpenModal}>
<h3>{card.name}</h3>
</div>