How to use map in React to create independent children elements - reactjs

I can successfully use map to create multiple elements from inside my array, but the button that I'm creating and the state I'm assigning to each child element seems to be tied back to the first mapping. Code here along with screen shot.
import React, {useState} from 'react';
import './Switch.css'
const Switch = () => {
const [status, setStatus] = useState(true);
const meats = [
"Chicken",
"Ground Beef",
"Sausage"
]
const meatSwitches = meats.map((meat) =>
<>
<div id="ingredientContainer" className={status === true ? 'containerYes' : 'containerNo'}>
<h2 id='ingredientLabel'>{meat}</h2>
<div id="underLabel">
<h3 id='yes'>Sounds Yummy! </h3>
<input
className="react-switch-checkbox"
id={`react-switch-new`}
type="checkbox"
onClick={() => setStatus(!status)}
/>
<label
className="react-switch-label"
htmlFor={`react-switch-new`}
>
<span className={`react-switch-button`} />
</label>
<h3 id='no'>No, thanks.</h3>
</div>
</div>
</>
);
How can I get each child element to have a functioning button, individual of the first and with independent states (in this case of 'true' and 'false').

There are 2 common approaches:
1. Create a Meat child component with its own single boolean state
and keep this state at the child level, independent of the parent component Switch. You can pass the status data back to parent if you want, via a callback function onStatusChange, for example with an useEffect().
const Meat = ({ meat, onStatusChange } ) => {
const [status, setStatus] = useState(true);
useEffect(() => onStatusChange(status), [status])
return (
<div
id="ingredientContainer"
className={status === true ? 'containerYes' : 'containerNo'}
>
// ... your jsx code here
</div>
}
And your Switch component will look like this:
const Switch = () => {
const meats = [
"Chicken",
"Ground Beef",
"Sausage"
]
const [statusList, setStatusList] = useState([]);
const handleStatusChange = (status) => {
// do what you want with the individual status,
// perhaps push it in a status array?
setStatusList((prev) => [...prev, status]);
};
const meatSwitches = meats.map((meat) =>
<Meat key={meat} meat={meat} onStatusChange={handleStatusChange} />
)
2. Use an array of meat types instead of a boolean state to store checkbox values, then convert it to boolean values later when needed. With this approach you don't have to create a new Meat child component.
const Switch = () => {
const [status, setStatus] = useState([]);
const meats = [
"Chicken",
"Ground Beef",
"Sausage"
];
const handleStatusChange = (event, meat) => {
if (event.target.checked) {
// push meat type to list
// when box is checked
setStatus(prev => [...prev, meat]);
} else {
// remove meat type from list
// when box is unchecked
setStatus(prev => return prev.filter(type => type !== meat));
}
};
const meatSwitches = meats.map((meat) =>
<div
{/* id and key need to be unique */}
id={`ingredientContainer-${meat}`}
key={meat}
{/* check if box is checked */}
className={status.includes(meat) ? 'containerYes' : 'containerNo'}
>
// code
<input
type="checkbox"
onClick={(event) => handleStatusChange(event, meat)}
/>
// ...
</div>
)
Let me know if this answers your question.

The code is expanded upon slightly and 'answered' below. Thank you #kvooak for your help. Turns out that the checkbox that was hidden in CSS in favor of an unresponsive button (literally just some CSS and no 'input' functionality) was what I needed. I made that checkbox visible, added in a function to add or remove items based on checked status to and from an array, and printed that array at the end in realtime to prove that the app was holding/dumping the right items from array allIngredients.
import React, {useState} from 'react';
import './Switch.css'
const Switch = () => {
const [status, setStatus] = useState([]);
const [checked, setChecked] = useState([]);
let meats = [
"Chicken",
"Ground Beef",
"Sausage",
"Pork Chops"
];
let starches = [
"White Rice", "Bread", "Pasta",
"Chips", "Idaho Potatoes", "Sweet Potatoes",
"Tortillas", "Brown Rice", "Red Potatoes"
];
const vegatables = [
"Broccoli", "Green Beans", "Squash",
"Corn", "Okra", "Carrots",
"Onions", "Okra", "Zucchini"
]
const spices = [
"Cayenne Pepper", "Cumin", "Garlic Powder",
"Onion Powder", "Tumeric", "Garam Masala",
"Chili Powder", "Crushed Red Pepper", "Oregano"
]
let allIngredients = [...checked];
const handleStatusChange = (event, ingredient) => {
if (event.target.checked) {
setStatus(prev => [...prev, ingredient]);
allIngredients = [...checked, event.target.value];
} else {
setStatus(prev => prev.filter(type => type !== ingredient));
allIngredients.splice(checked.indexOf(event.target.value), 1);
}
setChecked(allIngredients);
};
var checkedItems = checked.length
? checked.reduce((total, item) => {
return total + ", " + item;
})
: "";
const meatSwitches = meats.map((meat) =>
<>
<div id="ingredientContainer" className={status.includes(meat) ? 'containerYes' : 'containerNo'}>
<p key={meat} id='ingredientLabel'>{meat}</p>
<div id="underLabel">
<input
className="react-switch-checkbox"
value = {meat}
id={`react-switch-new`}
type="checkbox"
onClick={(event) => handleStatusChange(event, meat)}
/>
</div>
</div>
</>
);
const starchSwitches = starches.map((starch) =>
<>
<div id="ingredientContainer" className={status.includes(starch) ? 'containerYes' : 'containerNo'}>
<p key={starch} id='ingredientLabel'>{starch}</p>
<div id="underLabel">
<input
className="react-switch-checkbox"
value={starch}
id={`react-switch-new`}
type="checkbox"
onClick={(event) => handleStatusChange(event, starch)}
/>
</div>
</div>
</>
);
const vegatableSwitches = vegatables.map((vegatable) =>
<>
<div id="ingredientContainer" className={status.includes(vegatable) ? 'containerYes' : 'containerNo'}>
<p key={vegatable} id='ingredientLabel'>{vegatable}</p>
<div id="underLabel">
<input
className="react-switch-checkbox"
value={vegatable}
id={`react-switch-new`}
type="checkbox"
onClick={(event) => handleStatusChange(event, vegatable)}
/>
</div>
</div>
</>
);
const spiceSwitches = spices.map((spice) =>
<>
<div id="ingredientContainer" className={status.includes(spice) ? 'containerYes' : 'containerNo'}>
<p key={spice} id='ingredientLabel'>{spice}</p>
<div id="underLabel">
<input
className="react-switch-checkbox"
value={spice}
id={`react-switch-new`}
type="checkbox"
onClick={(event) => handleStatusChange(event, spice)}
/>
</div>
</div>
</>
);
return (
<>
{meatSwitches}
{starchSwitches}
{vegatableSwitches}
{spiceSwitches}
{checkedItems}
</>
);
};
export default Switch;

You can set the state to be an array of booleans, and have each button correspond to the index. Example:
const status, setStatus = useState([true, true, true])
const handleChange = (index) => {
const prev = [...status];
prev[index] = !prev[index];
setStatus(prev);
}
const meatSwitches = meats.map((meat,index) =>
<>
<div id="ingredientContainer" className={status === true ? 'containerYes' : 'containerNo'}>
<h2 id='ingredientLabel'>{meat}</h2>
<div id="underLabel">
<h3 id='yes'>Sounds Yummy! </h3>
<input
className="react-switch-checkbox"
id={`react-switch-new`}
type="checkbox"
onClick={() => handleChange(index)}
/>
<label
className="react-switch-label"
htmlFor={`react-switch-new`}
>
<span className={`react-switch-button`} />
</label>
<h3 id='no'>No, thanks.</h3>
</div>
</div>
</>
);

Related

I have an output and I want to display it in array upto five values

I have output in setSearchHistory(search);
and i am displaying it in History: {searchHistory}
Currently I am able to log current value and on changing out put updated value is getting displayed.
I want the previous output and new output to be displayed like an array and this goes up to five places.
const [city, setCity] = useState(null);
const [search, setSearch] = useState("Dehradun");
const [searchHistory, setSearchHistory] = useState([]);
useEffect ( () => {
const fetchApi = async () => {
const url = `https://api.openweathermap.org/data/2.5/weather?q=${search}&units=metric&appid=7938d9005e68d8b258a109c716436c91`
const response = await fetch(url);
const resJson = await response.json();
setCity(resJson.main);
};
fetchApi();
},[search] )
const handleClick = event => {
event.preventDefault();
setSearchHistory(search);
};
return(
<>
<div className="box">
<div className="inputData">
<input
id="btnc"
type="search"
value={search}
className="inputFeild"
onChange={ (event) => { setSearch(event.target.value) }}
/>
<button onClick={handleClick}>Click</button>
</div>
<input className="is"></input>
<h3 className="tempmin_max">
History: {searchHistory}
</h3>
{!city ? (
<p className="errorMsg">Enter City Name</p>
) : (
<div>
<div className="info">
<h2 className="location">
<i className="fa-solid fa-street-view"> </i>{search}
</h2>
<h1 className="temp">
{city.temp}
</h1>
<h3 className="tempmin_max">
Min : {city.temp_min} | Max : {city.temp_max}
</h3>
</div>
<div className="wave -one"></div>
<div className="wave -two"></div>
<div className="wave -three"></div>
</div>
) }
</div>
</>
)
}
export default Tempapp;```
you can do like this:
let arr = [];
const handleClick = event => {
event.preventDefault();
arr.push(search)
setSearchHistory(arr);
};
Now there is a array in searchHistory. You need to use the map function to display the search history.

Next JS - Checkbox Select All

I'm a beginner in Next Js. and I'm trying to implement select all on the checkboxes.
I following this reference https://www.freecodecamp.org/news/how-to-work-with-multiple-checkboxes-in-react/
what I expect is, if the checkbox select all is checked then sum all the prices.
but I don't know how to start it.
Here is my sandbox https://codesandbox.io/s/eager-feather-2ieme9
Any help with this would be greatly appreciated, been working on this for a while and exhausted all avenues!
You can check the below logic with some explanation
You also can check this sandbox for the test
import { useState } from "react";
import { toppings } from "./utils/toppings";
// import InputTopings from "./InputTopings";
const getFormattedPrice = (price) => `$${price.toFixed(2)}`;
export default function TopingApp() {
const [checkedState, setCheckedState] = useState(
new Array(toppings.length).fill(false)
);
// console.log(checkedState);
const [total, setTotal] = useState(0);
//Separate `updateTotal` logic for avoiding duplication
const updateTotal = (checkboxValues) => {
const totalPrice = checkboxValues.reduce((sum, currentState, index) => {
if (currentState === true) {
return sum + toppings[index].price;
}
return sum;
}, 0);
setTotal(totalPrice);
};
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
//update total
updateTotal(updatedCheckedState);
};
const handleSelectAll = (event) => {
//filled all checkboxes' states with `Check All` value
const updatedCheckedState = new Array(toppings.length).fill(
event.target.checked
);
setCheckedState(updatedCheckedState);
//update total
updateTotal(updatedCheckedState);
};
return (
<div className="App">
<h3>Select Toppings</h3>
<div className="call">
<input
type="checkbox"
name="checkall"
checked={checkedState.every((value) => value)}
onChange={handleSelectAll}
/>
<label htmlFor="checkall">Check All</label>
</div>
<ul className="toppings-list">
{toppings.map(({ name, price }, index) => {
return (
<li key={index}>
<div className="toppings-list-item">
<div className="left-section">
<input
type="checkbox"
// id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
<label>{name}</label>
</div>
<div className="right-section">{getFormattedPrice(price)}</div>
</div>
</li>
);
})}
<li>
<div className="toppings-list-item">
<div className="left-section">Total:</div>
<div className="right-section">{getFormattedPrice(total)}</div>
</div>
</li>
</ul>
</div>
);
}
You can lift the 'Check all' state to a parent object and on change of this state set all <input/> tags value to that state you can achieve this by dividing your app. First create a component for the items in the list like <Toppings/> and give props to this component like so <Toppings name={name} price={price} checkAll={checkAll}/> inside the toppings component create a state variable like this
const Toppings = ({name,price, checkAll}) => {
const [checked,setChecked] = useState(checkAll)
return (
<li key={index}>
<div className="toppings-list-item">
<div className="left-section">
<input
type="checkbox"
// id={`custom-checkbox-${index}`}
name={name}
value={checked}
onChange={setChecked(!checked)}
/>
<label>{name}</label>
</div>
</div>
</li>
)
}
Edit:
inside index.js:
const [checkAll, setCheckAll] = useState(false)
//when rendering inside return method
{toppings.map(({name,price},index) => <Toppings key={index} name={name} price={price} checkAll={checkAll}/> )

ToDo rendering data object

I was just testing around building a dummy todo list and was trying to figure out something. While setting the new state with the new task object that includes an id and a text. Well everything works well just my issue when I console.log(allTasks) it starts only to show the array of data after I have added the second task ?
const SearchInput = () => {
const [taskValue, setTaskValue] = useState("");
const [allTasks, setAllTasks] = useState([]);
const handleChange = (e) => {
setTaskValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (taskValue !== "") {
setAllTasks([
...allTasks,
{ id: allTasks.length + 1, text: taskValue.trim() },
]);
}
setTaskValue("");
console.log(allTasks);
};
return (
<>
<Form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Add a task..."
value={taskValue}
onChange={handleChange}
/>
<button>Submit the Task</button>
</Form>
<div>
{allTasks.length <= 0 ? (
<p>No tasks</p>
) : (
<ul>
{allTasks.map((task) => (
<li key={task.id}> {task.text} </li>
))}
</ul>
)}
</div>
</>
);
};
Here you get updated value and there is a conditional change I hope you will like.
Thanks
const SearchInput = () => {
const [taskValue, setTaskValue] = React.useState("");
const [allTasks, setAllTasks] = React.useState([]);
const handleChange = (e) => {
setTaskValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (taskValue !== "") {
setAllTasks([
...allTasks,
{ id: allTasks.length + 1, text: taskValue.trim() },
]);
}
setTaskValue("");
};
console.log(allTasks);
return (
<>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Add a task..."
value={taskValue}
onChange={handleChange}
/>
<button>Submit the Task</button>
</form>
<div>
{!allTasks.length && <p>No tasks</p>}
{!!allTasks.length &&
<ul>
{allTasks.map((task) => (
<li key={task.id}> {task.text} </li>
))}
</ul>
}
</div>
</>
);
};
According to the docs, setState is async in nature, which means it will execute only after execution of all synchronous code. And setState takes a callback as the second parameter which you can use to log it as expected.
setAllTasks([
...allTasks,
{ id: allTasks.length + 1, text: taskValue.trim() },
], ()=>{
console.log(alltasks)
});
Reference

Creating a clear function for budgeting app

I am working on creating a clear button that once clicked will clear all the transactions that have been added to the transaction list with localStorage. My button works but its buggy, once it gets clicked I get the following error about a separate function I have to get the balance. If I refresh the page afterwords though all the transactions will be cleared.
The error I am receiving ...
TypeError: amounts.reduce(...).toFixed is not a function
my component
import react, {useState, useEffect} from 'react'
import Transaction from './Transaction'
const Form = () => {
//initial state
const [transaction, setTransaction] = useState({
description: '',
amount: ''
})
const [list, setList] = useState(
JSON.parse(localStorage.getItem('list')) || []
)
const [balance, setBalance] = useState('')
const [income, setIncome] = useState(
JSON.parse(localStorage.getItem('income'))
)
const [expense, setExpense] = useState(JSON.parse(localStorage.getItem('expense')))
//updates based onChange value
const updateBalance = (e) => {
setTransaction({
...transaction,
[e.target.name]:
e.target.type == 'number' ? parseInt(e.target.value) : e.target.value
})
}
//identify if transaction is income/expense
const plusMinus = () => {
transaction.amount > 0
? setIncome(income + transaction.amount)
: setExpense(expense + transaction.amount)
}
// updates balance after transaction is added
const getBalance = () => {
const amounts = list.map(i => i.amount);
const money = amounts.reduce((acc, item) => (acc += item), 0).toFixed(2);
setBalance(money)
}
useEffect(() => {
getBalance()
localStorage.setItem('list', JSON.stringify(list))
localStorage.setItem('income', JSON.stringify(income))
localStorage.setItem('expense', JSON.stringify(expense))
}, [list])
//clear transaction list
const clearBudget = () => {
localStorage.clear();
}
const onSubmit = e => {
e.preventDefault();
setList([transaction, ...list])
plusMinus()
setTransaction({ description: '', amount: ''})
}
return (
<div>
<div className='totals'>
<h2 className='balance'> Current Balance </h2>
<h3> ${balance} </h3>
<h4> Income: ${income} Expense: ${expense} </h4>
</div>
< br />
< br />
< br />
<h2 className='trans-history'> Transaction History </h2>
{list.map(i => {
return (
<div className='trans'>
<ul key={i.description}>
{i.description} ${parseInt(i.amount)}
</ul>
</div>
)
})}
<br />
<br />
<h2 className='enter-item'> Enter an Item </h2>
<form onSubmit={onSubmit}>
<div>
<input
type='text'
className="input-trans"
placeholder='Enter Transaction'
value={Transaction.description}
name='description'
onChange={updateBalance}
>
</input>
</div>
<div>
<input
type='number'
className='input-trans'
placeholder='Enter Amount'
name='amount'
value={transaction.amount}
onChange={updateBalance}
>
</input>
</div>
<br/>
<div className='button-container'>
<button type='submit' className='button is-primary'> Submit </button>
<button className='button is-danger' onClick={clearBudget}> Clear </button>
</div>
</form>
</div>
)
}
export default Form
Looks like amounts.reduce is returning something that is not a number. You could check the type before to perform toFixed function.
E.g.:
const amounts = list.map((i) => i.amount).map(Number);
const money = amounts.reduce((acc, item) => (acc += item), 0)
if (typeof money === 'number') {
setBalance(money.toFixed(2))
} else {
setBalance(money)
}

How to split a simple HTML page into multiple React components and share states?

I am new to React Js. Could someone please explain how to translate this example to ReactJs? Specifically, if you have to organize the code into 3 components, how would you share the states between these component?
<label for="inputNumberOfCheckboxes">Enter number of checkboxes:</label> <input type="text" id ="inputNumberOfCheckboxes">
<ol id="listOfCheckboxes">
</ol>
<p id="printNumberOfSelectedCheckboxes">None</p>
<script type="text/javascript">
const inputNumberOfCheckboxes = document.getElementById('inputNumberOfCheckboxes');
const listOfCheckboxes = document.getElementById('listOfCheckboxes');
const printNumberOfSelectedCheckboxes = document.getElementById('printNumberOfSelectedCheckboxes');
inputNumberOfCheckboxes.addEventListener('change', () => {
const n = parseInt(inputNumberOfCheckboxes.value);
listOfCheckboxes.innerHTML = '';
for (let i = 0; i < n; i++) {
listOfCheckboxes.innerHTML += `
<li>
<input type="checkbox">
</li>`;
}
const refreshCount = () => {
const count = listOfCheckboxes.querySelectorAll('input:checked').length;
printNumberOfSelectedCheckboxes.innerHTML = `${count} checked`;
};
for (let checkbox of listOfCheckboxes.childNodes) {
checkbox.addEventListener('change', refreshCount);
}
});
</script>
In one component:
export default function App() {
const [inputValue, setInputValue] = React.useState("");
const [checkedItems, setCheckedItems] = React.useState([]);
const getRows = () => {
const length = inputValue > 0 ? inputValue : 0;
const arr = Array.from({ length }, (_, i) => i + 1);
return (
<ol>
{arr.map((item) => (
<li key={item}>
<input
onChange={() => {
const items = checkedItems.includes(item)
? checkedItems.filter((num) => num !== item)
: [...checkedItems, item];
setCheckedItems(items);
}}
type="checkbox"
checked={checkedItems.includes(item)}
/>
</li>
))}
</ol>
);
}
};
return (
<div className="App">
<label htmlFor="inputNumberOfCheckboxes">
Enter number of checkboxes:
</label>
<input
name="inputNumberOfCheckboxes"
type="number"
onChange={(e) => setInputValue(e.target.value)}
value={inputValue}
/>
<ol>{getRows()}</ol>
{inputValue > 0 ? null : <p>None</p>}
checked items: {checkedItems.length}
</div>
);
}
working example
Splitted:
Input.js
export default function Input({ name, value, onChange }) {
return (
<div>
<label htmlFor={name}>Enter number of checkboxes:</label>
<input
name={name}
type="number"
onChange={(e) => onChange(e.target.value)}
value={value}
/>
</div>
);
}
List.js
export default function List({ inputValue, checkedItems, setCheckedItems }) {
const length = inputValue > 0 ? inputValue : 0;
const arr = Array.from({ length }, (_, i) => i + 1);
return (
<ol>
{arr.map((item) => (
<li key={item}>
<input
onChange={() => {
const items = checkedItems.includes(item)
? checkedItems.filter((num) => num !== item)
: [...checkedItems, item];
setCheckedItems(items);
}}
type="checkbox"
checked={checkedItems.includes(item)}
/>
</li>
))}
</ol>
);
}
Summary.js
export default function Summary({ inputValue, checkedItems }) {
return (
<p>
{inputValue > 0 ? null : <p>None</p>}
checked items: {checkedItems.length}
</p>
);
}
App.js
export default function App() {
const [inputValue, setInputValue] = React.useState("");
const [checkedItems, setCheckedItems] = React.useState([]);
return (
<div className="App">
<Input onChange={setInputValue} value={inputValue} />
<List
name="inputNumberOfCheckboxes"
inputValue={inputValue}
checkedItems={checkedItems}
setCheckedItems={setCheckedItems}
/>
<Summary inputValue={inputValue} checkedItems={checkedItems} />
</div>
);
}
working example

Resources