I'm relatively new to React and am trying to set the state of an object inside the child component(SportTile) for the onclick event. I'm comfortable updating the state of a single variable by using individual useState() hooks but, that is a cumbersome task when you have a lot of variables. So I used an object named : isClicked which stores a boolean value for various sports on whether they are selected by the user or not.
The functionality that I'm looking for is, whenever a SportTile gets clicked on, its isClicked["sportname"] value is set to true and the rest are set to false. Only one sport can be true at once.
Upon console logging the isClicked object in the onclick() function, I got the desired values but they weren't updated in the Parent component's(Booking) h1 tag
import React from 'react';
import SportTile from '../SportTile';
import './booking.css';
import { useState } from 'react';
import SportsSoccerRoundedIcon from '#mui/icons-material/SportsSoccerRounded';
import SportsBasketballRoundedIcon from '#mui/icons-material/SportsBasketballRounded';
import SportsVolleyballIcon from '#mui/icons-material/SportsVolleyball';
import SportsTennisIcon from '#mui/icons-material/SportsTennis';
const Booking = () => {
const [isClicked, setIsClicked] = useState({
Football: false,
Basketball: false,
Volleyball: false,
Tennis: false,
});
return (
<div className='booking'>
<div className='booking__body'>
<div className='booking__left'>
<h1>SELECT SPORT</h1>
<SportTile
sportname={'Football'}
icon={<SportsSoccerRoundedIcon />}
clicked={setIsClicked}
isClicked={isClicked}
// test={setTestclicked}
// istestClicked={istestClicked}
/>
<SportTile
sportname={'Basketball'}
icon={<SportsBasketballRoundedIcon />}
clicked={setIsClicked}
isClicked={isClicked}
/>
<SportTile
sportname={'Volleyball'}
icon={<SportsVolleyballIcon />}
clicked={setIsClicked}
isClicked={isClicked}
/>
<SportTile
sportname={'Tennis'}
icon={<SportsTennisIcon />}
clicked={setIsClicked}
isClicked={isClicked}
/>
<h1>Football : {isClicked.Football.toString()} </h1>
<h1>Basketball : {isClicked.Basketball.toString()} </h1>
<h1>Volleyball : {isClicked.Volleyball.toString()} </h1>
<h1>Tennis : {isClicked.Tennis.toString()} </h1>
</div>
<div className='booking__right'>Right Side</div>
</div>
</div>
);
};
export default Booking;
import { Button } from '#mui/material';
import React from 'react';
import './sportTile.css';
const SportTile = ({
sportname,
icon,
clicked,
isClicked,
}) => {
function onclick() {
Object.keys(isClicked).map((key) => {
if (key == sportname) {
isClicked[key] = true;
} else {
isClicked[key] = false;
}
});
clicked(isClicked);
console.log(isClicked);
}
return (
<Button className='sportname__button' onClick={onclick}>
<div className='sportname__buttonColumn'>
{icon}
{sportname}
</div>
</Button>
);
};
export default SportTile;
Maybe I'm missing the obvious but would appreciate anyone who could point me in the right direction
You should never pass the original setState method.
create a new method in the Booking component:
const setIsClickedWrapper = (sportKey) = {
setIsClicked((isClicked) => Object.keys(isClicked).map((key) => sportKey === key)
}
and in the SportTile component just call:
const onclick = () => { setIsClickedWrapper(sportname) }
but I think it will be better if isClicked will be just a string of the current clicked sport key and then it's enough:
const setIsClickedWrapper = (sportKey) = {
setIsClicked(sportKey)
}
Related
Why the input only taking inputs from second input only?
import React, { useState } from "react";
import Item from "./Components/Item";
import "./ToDo.css";
function ToDo() {
let toDoIs = document.getElementById("toDoInput");
const [ToDo, setToDoIs] = useState("d");
const [ToDoArray, setToDoArray] = useState([]);
return (
<div>
<h1>ToDo</h1>
<input
id="toDoInput"
onChange={() => {
setToDoIs(toDoIs.value);
}}
type="text"
/>
<button
onClick={() => {
setToDoArray([...ToDoArray, { text: ToDo }]);
toDoIs.value = "";
}}
>
Add
</button>
<Item push={ToDoArray} />
</div>
);
}
export default ToDo;
Why the second input only works, which means whenever I use submit the value from second input only stored and displayed. I don't know why this happens.
There's a few problems here...
Don't use DOM methods in React. Use state to drive the way your component renders
Your text input should be a controlled component
When updating state based on the current value, make sure you use functional updates
import { useState } from "react";
import Item from "./Components/Item";
import "./ToDo.css";
function ToDo() {
// naming conventions for state typically use camel-case, not Pascal
const [toDo, setToDo] = useState("d");
const [toDoArray, setToDoArray] = useState([]);
const handleClick = () => {
// use functional update
setToDoArray((prev) => [...prev, { text: toDo }]);
// clear the `toDo` state via its setter
setToDo("");
};
return (
<div>
<h1>ToDo</h1>
{/* this is a controlled component */}
<input value={toDo} onChange={(e) => setToDo(e.target.value)} />
<button type="button" onClick={handleClick}>
Add
</button>
<Item push={toDoArray} />
</div>
);
}
export default ToDo;
so I am trying to create add to faviourate button with icon.
so far I could make a logic if a user clicked the empty heart icon that it turns to be full heart icon and I was able to locate the item it was clicked on.
So far so good, my issue starts when products object recieves only the most recent item that is picked and loses the other items that are previously picked.
so for example If I want to click on 3 items to add them to faviourate, I see that the console.log(favProduct) only preserves the most recent item which is in my case number 3 and loses number 1 and 2.
My Question is How to get all Items I clicked on and not only the most recent one.
Edit This is where I get the product from, check the code below.
import React , { useState, useEffect } from 'react'
import { Row , Col } from 'react-bootstrap'
import Product from '../components/Product'
import axios from 'axios'
const HomeScreen = () =>{
const [products , setProducts] = useState([])
useEffect(()=>{
let componentMounted = true
const fetchProducts = async () =>{
const {data} = await axios.get('http://172.30.246.130:5000/api/products')
if( componentMounted){
setProducts(data)
}
}
fetchProducts()
return () =>{
componentMounted = false
}
},[])
console.log('products' , products)
return(
<>
<h2 className='my-3'>Latest Products</h2>
<Row>
{
products.map((product)=>(
<Col key={product._id} sm={12} md={6} lg={4} xl={3}>
<Product product={product} rating = {product.rating} reviews={product.numReviews}/>
</Col>
))
}
</Row>
</>
)
}
export default HomeScreen
import React, { useState } from 'react'
const Fav = ({products}) => {
let [checked , setChecked] = useState(false)
let[favProduct ,setFavProduct] = useState([])
const toggle = ()=>{
(!checked) ? setChecked(true) : setChecked(false)
setFavProduct([...favProduct,products]) // problem is here
}
console.log(favProduct)
return (
<>
<span onClick={toggle}>
{
<i className={(checked) ? "fas fa-heart" : "far fa-heart"}></i>
}
</span>
</>
)
}
export default Fav
At the moment each of your Fav components looks like they're trying to manage the state for all of the favourites which is not a good idea.
The general idea is to make most UI components as dumb as possible (ie. just return the bare minimum given the props they're given). They can control their own state but usually you want to a parent to control the state by lifting state up, and have them pass down a handler that the dumb component can call when their listener is triggered.
In this example Fav accepts an id, a favoured, and a handleClick listener, and then just returns some JSX.
The parent component does all the state management.
const { useEffect, useState } = React;
// Accept some props
// Render the class based on the `favoured` prop
function Fav({ id, favoured, handleClick }) {
return (
<div className="icon">
<i
data-id={id}
className={favoured ? 'fa-solid fa-heart' : 'fa-regular fa-heart'}
onClick={handleClick}
> {id}</i>
</div>
);
}
function Example() {
// The parent component manages the state
const [ favourites, setFavourites ] = useState([]);
// When a favourite icon is clicked, get its id
// and if it's in the state, remove it, otherwise add it
function handleClick(e) {
const { id } = e.target.dataset;
const found = favourites.includes(id);
if (found) {
setFavourites(favourites.filter(fav => fav !== id));
} else {
setFavourites([...favourites, id]);
}
}
// In this example I'm using a loop to generate the
// the favourites, checking if the state includes the id
// (I have to coerce it to a string because that's what
// the data attributes return, and `i` will always be a number
// Pass down the handler that the favourite button
// will use to update the state
function getFavs() {
const favs = [];
for (let i = 0; i < 5; i++) {
const isFavoured = favourites.includes(i.toString());
const fav = (
<Fav
id={i}
favoured={isFavoured}
handleClick={handleClick}
/>
);
favs.push(fav);
}
return favs;
}
return (
<div>
{getFavs()}
</div>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/all.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I am currently working on a react.js to do list. The code below is what i've done so far which are relevant to the question
Main File (App.js)
import './App.css';
import Navbar from './components/Header'
import Container from './components/Container.js';
import Input from './components/Input';
import { useState } from 'react';
function App() {
let todo =[
{id:Math.random()*10000,
title: "Walk the dog",
completed:false},
{id:Math.random()*10000,
title: "Wash Dishes",
completed:false},
]
const [list,setList] = useState(todo)
function addTodo(e) {
let value = e.target.previousElementSibling.value;
let newtodo = [...todo,{id: Math.random()*10000, title: value, completed:false}]
setList(newtodo)
console.log(todo)
}
return (
<div className="body">
<Navbar className="Navbar"/>
<Input method = {addTodo} />
<Container list={newtodo}/>
</div>
);
}
export default App;
Container Component
const Container =({list , method})=> {
return(
<div className="container">
{list.map(element => {
return(
<TodoComponent className="todo-list" data={element}/>
)
})}
</div>
)
}
export default Container
Todo List Component
import React from "react";
const TodoComponent = ({data})=> {
return(
<div className="todo-list" >
<h3 key={data.id} >{data.title}</h3>
<i className="fas fa-trash"></i>
</div>
)
}
export default TodoComponent
Form Input
import React from "react"
const Input = ({method}) => {
return (
<div className="form">
<input placeholder="add task.."></input>
<button onClick= {method}>
Add
</button>
</div>
)
}
export default Input
The problem is, whenever I click the button next to the input field, the "todo" array updates, but the updates do not get rendered in the to do list. How do i fix this? Thanks in advance. If this was too confusing, please let me know.
Your addTodo function is incorrect. instead ..todo it should be ...list. try this
function addTodo(e) {
let value = e.target.previousElementSibling.value;
let newtodo = [...list,{id: Math.random()*10000, title: value, completed:false}]
setList(newtodo)
console.log(todo)
}
and you are not passing list state to <Container/> component
insted of
<Container list={newtodo}/>
it should be
<Container list={list}/>
First verify if you have the desired value in your addTodo function. The problem is you are updating the todo array which doesn't change because it is not a state variable. Try:
function addTodo(e) {
let value = e.target.previousElementSibling.value;
setList(list => [...list, {id: Math.random()*10000, title: value, completed:false})
}
I am new to React.js, and so far, I am loving it. I am still confused on the concept of stateful components, although. I am using Bootstrap tables to build my table, and my GET request for its data grab worked flawlessly. I am using the material-ui lib for my switch component as well (no need to reinvent the wheel here!)
Although, I am now trying to integrate a new column that will be a switch for each row in my table, and that, when toggled, changes the boolean of said switch to true/false, which will then send a PUT request down to my backend. I have not built my PUT request yet, as I cannot get this UI portion functioning. Here is my code so far, and the dumby UI works, but I don't know how to integrate the stateful render I defined in NodeTableContainer at <SwitchState/> and SwitchState(), into my definition at selectionRenderer: Switches in my NodeTable component. The stateful render does render a toggle switch under the table, essentially as its own independent component. But I want to integrate that toggle switch component in const selectRow = {mode: 'checkbox', clickToSelect: true,selectionRenderer: Switches}. Here is my code, and I hope my I have explained my issue well. I have Googled endlessly, but I believe my own ignorance has blocked my from discovering the answer I need.
Table Component (NodeTable)
import React from 'react';
import {
Row,
Col,
Card,
CardBody,
} from 'reactstrap';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { Search, CSVExport, ColumnToggle } from 'react-bootstrap-table2-toolkit';
import paginationFactory from 'react-bootstrap-table2-paginator';
import 'chartjs-plugin-colorschemes';
import Switches from './Switch'
const columns = OMIT
const defaultSorted = [
{
dataField: 'id',
order: 'asc',
},
]
const TableWithSearch = (props) => {
const { SearchBar } = Search;
const { ExportCSVButton } = CSVExport;
const selectRow = {
mode: 'checkbox',
clickToSelect: true,
selectionRenderer: Switches
}
return (
<Card>
<CardBody>
<h4 className="header-title">OMIT</h4>
<p className="text-muted font-14 mb-4">OMIT</p>
<ToolkitProvider
bootstrap4
keyField="fqn"
data={props.data}
columns={columns}
columnToggle
search
exportCSV={{ onlyExportFiltered: true, exportAll: false }}>
{props => (
<React.Fragment>
<Row>
<Col>
<SearchBar {...props.searchProps} />
</Col>
<Col className="text-right">
<ExportCSVButton {...props.csvProps} className="btn btn-primary">
Export CSV
</ExportCSVButton>
</Col>
</Row>
<BootstrapTable
{...props.baseProps}
bordered={false}
defaultSorted={defaultSorted}
pagination={paginationFactory({ sizePerPage: 5 })}
selectRow={selectRow}
wrapperClasses="table-responsive"
/>
</React.Fragment>
)}
</ToolkitProvider>
</CardBody>
</Card>
);
};
export default TableWithSearch;
Switch Component
// #flow
import React from 'react';
import 'chartjs-plugin-colorschemes';
import './Switch.css'
import Switch from '#material-ui/core/Switch';
export default function Switches({ isOn, handleToggle }) {
return (
<div>
<Switch
checked={isOn}
onChange={handleToggle}
name="checkedA"
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>
</div>
);
}
Parent Component (NodeTableContainer)
import axios from 'axios';
import React, { Component, useState } from 'react';
import Switch from './Switch';
import App from './index';
export default class MainComp extends React.Component {
state = {
nodesData: [],
chartRef: [],
conn: [],
switchstate: [],
}
componentDidMount() {
axios.get('OMIT')
.then(res => {
const nodestate = res.data.map(x => x.nodestate);
for (var i = 0; i < nodestate.length; i++) {
if (nodestate[i] == 'up') {
nodestate[i] = true;
}
else {
nodestate[i] = false;
}
}
this.setState({ nodesData: res.data, switchstate: nodestate });
})
}
render() {
return (
< >
<App data={this.state.nodesData} checked={this.state.switchstate} />,
<SwitchState />
</>
)
}
}
function SwitchState() {
const [value, setValue] = useState(false);
console.log(value)
return (
<div className="app">
<Switch
isOn={value}
onColor="#EF476F"
handleToggle={() => setValue(!value)}
/>
</div>
);
}
Also, my SwitchState component is in a dumby form as you will see, until I can see the log showing its boolean state changing. Also, nodestate in the NodeTableContainer was my pathetic try at pulling data via the same state data. That is nonfunctional as you will also see. I will build the state properly once I can get this figured out, or you wonderful individuals aid me in this as well. Again, I am showing my ignorance here, so if there is an easier way, or if I am using an odd flavor of libs for this, please let me know. I want to learn and thrive. If you have a solution of your own, that's a completely different flavor, I plea to you to share it! Thank you all!
I figured this out for react-bootstrap. I fat arrowed in the formatter, and passed the state to formatExtraData. I then pass state from my component that holds all state, and it works flawlessly. Time to integrate my PUT request in with the event handler!
Below are my changes in code:
Table Component
export default class TableComp extends React.Component
formatter: (cell, row, index, extra) => {
if (cell === 'up') {
cell = true
}
else {
cell = false
}
return (
<div>
<Switch
checked={cell}
onChange={extra.handle}
name={row.name}
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>
</div>
)
},
formatExtraData: { handle: this.props.handleClick }
Parent Component (Holds all state)
handleClick = (e) => {
var indexFound = this.state.data.findIndex(y => y.name === e.target.name.toString())
let data= [...this.state.data];
let item = { ...data[indexFound] }
if (item.state === 'up') {
item.state = 'down'
}
else {
item.state = 'up'
}
data[indexFound] = item
this.setState({ data})
}
setState() doesn't work on first click ! the state value gets updated only on second , third ....clicks. i used proper contexts and imports to handle the states!
I'll quickly summarize what im doing top nav bar has two buttons , home and cart.
Side nav bar has three hero buttons, on click renders the respective hero store which has tshirts , socks and shoes with + and - buttons to increase or decrease the quantity.
on each click the value of span that displays the quantity increases correctly but the cart buttons shows the quantity excluding the first clicks. Like when i increment the tshirts value to 1 , the cart button doesn't show any value ,as i increment the tshirts value to 2 the cart button shows 1
cartButton uses the state CartValue
tshirts,socks,shoes use the state HeroGoods
(live demo) click here to see the what im talking about
i'm not sure if im allowed to post all the components and external links like github here. but anyways if you guys cant see where i went wrong from the code below , here's link to the github repo
import React , {useState,useEffect}from 'react';
import Navmenu from './Navmenu'
import SideNav from './SideNav'
import ActionDiv from './ActionDiv'
import ActionHeroStore from './ActionHeroStore'
import ActionCart from './ActionCart'
import '../css/main.css'
export const HeroContext=React.createContext()
const emptyGood={
tshirts:0,
shoes:0,
socks:0,
}
const emptyCart={
batman:{
tshirts:0,
shoes:0,
socks:0,
},
superman:{
tshirts:0,
shoes:0,
socks:0,
},
greenlantern:{
tshirts:0,
shoes:0,
socks:0,
},
}
function empty()
{
return null
}
function App() {
const [hero,setHero]=useState(null)
const [cartValue,setCartValue]=useState(emptyCart)
const [batmanGoods,setBatmanGoods]=useState(emptyGood)
const [supermanGoods,setSupermanGoods]=useState(emptyGood)
const [greenLanternGoods,setGreenLanternGoods]=useState(emptyGood)
const [showCart,setShowCart]=useState(false)
function handleUpdateGoods(hero,obj){
hero=='batman'?
setBatmanGoods(prevState=>{
return {...prevState,...obj}
}):
hero=='superman'?
setSupermanGoods(prevState=>{
return {...prevState,...obj}
}):
hero=='greenlantern'?
setGreenLanternGoods(prevState=>{
return {...prevState,...obj}
}):
empty()
}
function handleHeroSelect(name){
setHero(prevState=>prevState=name)
}
function handleCartValue(value)
{
setCartValue(value)
}
function handleShowCart(status)
{
setShowCart(status)
}
function giveHeroGoods(hero,element)
{
return (
hero=='batman'?batmanGoods[element]:
hero=='superman'?supermanGoods[element]:
hero=='greenlantern'?greenLanternGoods[element]:empty()
)
}
function handleUpdateCart(name){
name=='batman'?
setCartValue(prevState=>{
return {...prevState,batman:{...batmanGoods}}
}):
name=='superman'?
setCartValue(prevState=>{
return {...prevState,superman:{...supermanGoods}}
}):
name=='greenlantern'?
setCartValue(prevState=>{
return {...prevState,greenlantern:{...greenLanternGoods}}
}):
empty()
}
const heroContextValue={
handleHeroSelect,
handleCartValue,
handleUpdateGoods,
giveHeroGoods,
handleUpdateCart,
handleShowCart
}
return (
<>
<HeroContext.Provider value={heroContextValue}>
<Navmenu cartValue={cartValue}/>
<div className="mainContent">
<SideNav cartValue={cartValue}/>
{hero==null && !showCart &&<ActionDiv/>}
{hero!==null && !showCart && <ActionHeroStore hero={hero}/>}
{showCart && <ActionCart cartValue={cartValue}/>}
</div>
</HeroContext.Provider>
</>
)
}
export default App;
import React ,{useContext} from 'react'
import {HeroContext} from './App'
export default function Navmenu(props) {
const {cartValue}=props
const {handleHeroSelect,handleShowCart}=useContext(HeroContext)
function giveGoodsSum(obj)
{
return obj.tshirts+obj.socks+obj.shoes
}
function giveCartValue(cartValue){
let sum=0
for(let key in cartValue)
{
sum=sum+giveGoodsSum(cartValue[key])
}
return(
sum!==0?sum:null
)
}
return (
<div
className="navMenu"
>
<button
className="homeButton"
onClick={()=>{
handleHeroSelect(null)
handleShowCart(false)
}}
>
Home
</button>
<button
className="cartButton"
onClick={()=>{
handleHeroSelect(null)
handleShowCart(true)
}}
>
cart
<span
>
{giveCartValue(cartValue)}
</span>
</button>
</div>
)
}
import React ,{useContext} from 'react'
import {HeroContext} from './App'
export default function SideNav() {
const {handleHeroSelect}=useContext(HeroContext)
return (
<div className="sideNav">
<div
className="batman"
onClick={()=>handleHeroSelect('batman')}
/>
<div
className="superman"
onClick={()=>handleHeroSelect('superman')}
/>
<div
className="greenlantern"
onClick={()=>handleHeroSelect('greenlantern')}
/>
</div>
)
}
import React from 'react'
import ActionHeroStoreGoods from './ActionHeroStoreGoods'
export default function ActionHeroStore(props) {
const {hero}=props
return (
<div className={`actionHeroStore ${hero}div`}>
<h3>{hero}</h3>
<div className="actionHeroStore_goods">
<ActionHeroStoreGoods hero={hero}/>
</div>
</div>
)
}
import React, { Fragment,useContext } from 'react'
import {HeroContext} from './App'
export default function ActionHeroStoreGoods({hero}) {
const {giveHeroGoods,
handleUpdateGoods,
handleUpdateCart
}=useContext(HeroContext)
const goods=['tshirts','shoes','socks'];
const goodsElement=goods.map((element,index) => {
return <Fragment key={index}>
<div className="soloGood">
<span>{element}</span>
<button
onClick={
()=>decrement(hero,element)
}>-</button >
<span>{giveHeroGoods(hero,element)}</span>
<button onClick={
()=>{
increment(hero,element)
handleUpdateCart(hero)
}
}>+</button>
</div>
</Fragment>
})
function increment(hero,element){
let updateObj={};
updateObj[element]=giveHeroGoods(hero,element)+1
handleUpdateGoods(hero,updateObj)
}
function decrement(hero,element){
if(giveHeroGoods(hero,element)>0)
{
let updateObj={};
updateObj[element]=giveHeroGoods(hero,element)-1
handleUpdateGoods(hero,updateObj)
}
}
return (
<>
{goodsElement}
</>
)
}
The problem is not in setState. The problem in the code. handleUpdateCart() function is called before the *Goods states are changed. So It works with old data. If you will add in the your 'App.js' file the following fragment:
...
...
function giveHeroGoods(hero,element)
{
return (
hero=='batman'?batmanGoods[element]:
hero=='superman'?supermanGoods[element]:
hero=='greenlantern'?greenLanternGoods[element]:empty()
)
}
// FROM HERE
React.useEffect(() => {
handleUpdateCart('batman');
}, [
batmanGoods
]);
React.useEffect(() => {
handleUpdateCart('superman');
}, [
supermanGoods
]);
React.useEffect(() => {
handleUpdateCart('greenlantern');
}, [
greenLanternGoods
]);
// TILL HERE
function handleUpdateCart(name){
...
...