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}/> )
Related
I'm trying to pass data to the parent component Top.js using props from a child component TagsInput.js where I can add tags but
I don't understand what is causing the error...
What I want to achieve
I want to pass "tags" to the parent component Top.js from TagsInput.js in the child component with props.
I got the error like
props.setTagsinput is not a function
TagsInput.js
import React from "react";
const TagsInput = (props) => {
//1, Define the tags variable to store the entered tags. (I want to pass the value of the tags variable to the parent component Top.js)
const [tags, setTags] = React.useState([]);
//2, Put formText in the function received from the parent component and return it.
props.setTagsinput(tags);
console.log(props)
let tag_list = []
tag_list.push(tags);
const addTags = event => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
event.target.value = "";
}
};
const removeTags = index => {
setTags([...tags.filter(tag => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={event => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
Top.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import Student from './Student';
import TagsInput from "./TagsInput";
const Top = () => {
const [ posts, setPosts] = useState([]);
const [ allPosts, setAllPosts] = useState([]);
let tag_list = []
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword)
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get('xxx.com')
.then(result => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult()
}
})},
[searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword)
const result = allPosts.filter((output, index) => {
return output.firstName.toLowerCase().includes(searchKeyword.toLowerCase())||output.lastName.toLowerCase().includes(searchKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword)
const result = allPosts.filter((output, index) => {
return output.lastName.toLowerCase().includes(searchTagKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
return (
<div>
<TagsInput setTagsinput={setTagsinput}/>
<div>
<input className="search-box" placeholder="" value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
<input className="search-box" placeholder="" value={searchTagKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
{searchKeyword &&
<p>{searchKeyword} Search</p>
}
{posts ?
<>
{posts.map((data, i) =>
<Student data={data} />
)}
</>
:
<div>
<p>Not Found!</p>
</div>
}
</div>
</div>
);
}
export default Top;
Student.js
import React, {useState} from 'react';
import TagsInput from './TagsInput';
const Student = (props) => {
const [show, setShow] = useState(false)
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function(score) {
sum += Number(score);
});
let ave = sum / grades.length
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">{props.data.firstName} {props.data.lastName}</p>
<button className="button" onClick={() => setShow(!show)}>
{show? <div className="button_p">-</div>:<div className="button_p">+</div>}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show &&
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
}
<TagsInput />
</div>
</div>
</div>
);
}
export default Student;
You can not directly use one component hook declaration in another component, you need to have a callback function to update that state. I modified your code to use the top page setTagsinput in student tag input
Top.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import Student from "./Student";
import TagsInput from "./TagsInput";
const Top = () => {
const [posts, setPosts] = useState([]);
const [allPosts, setAllPosts] = useState([]);
let tag_list = [];
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword);
const [tags_from_tagsinput, setTagsinput] = useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get("xxx.com").then((result) => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult();
}
});
}, [searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword);
const result = allPosts.filter((output, index) => {
return (
output.firstName.toLowerCase().includes(searchKeyword.toLowerCase()) ||
output.lastName.toLowerCase().includes(searchKeyword.toLowerCase())
);
});
console.log(result);
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword);
const result = allPosts.filter((output, index) => {
return output.lastName
.toLowerCase()
.includes(searchTagKeyword.toLowerCase());
});
console.log(result);
setPosts(result);
};
const setTagsFromStudent = (tags) => {
setTagsinput(tags);
};
return (
<div>
<div>
<input
className="search-box"
placeholder=""
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
<input
className="search-box"
placeholder=""
value={searchTagKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
{searchKeyword && <p>{searchKeyword} Search</p>}
{posts ? (
<>
{posts.map((data, i) => (
<Student data={data} setStudentTags={setTagsFromStudent} />
))}
</>
) : (
<div>
<p>Not Found!</p>
</div>
)}
</div>
</div>
);
};
export default Top;
Student.js
import React, { useState } from "react";
import TagsInput from "./TagsInput";
const Student = (props) => {
const [show, setShow] = useState(false);
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function (score) {
sum += Number(score);
});
let ave = sum / grades.length;
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">
{props.data.firstName} {props.data.lastName}
</p>
<button className="button" onClick={() => setShow(!show)}>
{show ? (
<div className="button_p">-</div>
) : (
<div className="button_p">+</div>
)}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show && (
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
)}
{/*pass settag from topTag component*/}
<TagsInput setStudentTags={props.setStudentTags} />
</div>
</div>
</div>
);
};
export default Student;
TagsInput.js
import React from "react";
const TagsInput = (props) => {
const [tags, setTags] = React.useState([]);
let tag_list = [];
tag_list.push(tags);
const addTags = (event) => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
// call function pass down from toptag
props.setStudentTags(tags);
event.target.value = "";
}
};
const removeTags = (index) => {
setTags([...tags.filter((tag) => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={(event) => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
You should consider exploring React context -https://reactjs.org/docs/context.html, its built exactly for something like this.
You are getting this error because, like you mentioned, TagsInput component is used in Student component but it doesn’t pass the state setter setTagsInput function to the TagsInput component.
Now, assuming you need tags created inside Student and displayed in Top, also assuming that both are rendered in the same parent component, you can create a state for tags in the parent component. This component will pass a state setter function to Student which passes the setter to TagsInput and the state itself to Top to use the list of tags.
Something like:
const App = () => {
const [tags,setTags] = useState([]);
return (<div>
<Top tags={tags} />
<Student setTags={setTags} />
</div>);
}
Your Student component can then pass it to TagsInput like:
const Student = (props) => {
return (<div>
{/* everything else */}
<TagsInput setTagsinput={props.setTags} />
</div>)
}
In your Top component you can create a function that updates your tags_from_tagsinput hook then pass it as props to the child component
import TagsInput from "./TagsInput";
const Top = () => {
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
const getTag = (value) => {
setTagsinput(value);
};
return (
<div>
<TagsInput getTag={getTag} />
</div>
);
}
export default Top;
Now from your TagsInput component you can call this function to update tags_from_tagsinput of Top, let's suppose that you want to updated when the user click on a button
import React from "react";
const TagsInput = (props) => {
return (
<div className="tags-input">
...
<button onClick={()=>{props.getTag(tags)}}>updated parent component</button>
</div>
);
};
export default TagsInput;
import React, { useState, useEffect } from "react";
import "./style.css";
function App() {
const [movieData, setMovieData] = useState();
const [directorData, setDirectorData] = useState();
const [listData, setListData] = useState([]);
const submitHandler = () => {
setListData([
...listData,
{
movie: movieData,
director: directorData,
},
]);
setMovieData("");
setDirectorData("");
};
const removeHandler = (id) => {
const newlist = listData.filter((remId) => {
return remId !== id;
});
setListData(newlist)
};
return (
<div className="App">
<div>
<h1>Todo App</h1>
</div>
<div className="form">
<div>
<label>Movie</label>
<input
type="text"
placeholder="type items"
value={movieData}
onChange={(e) => setMovieData(e.target.value)}
/>
</div>
<div>
<label>Director</label>
<input
type="text"
placeholder="type items"
value={directorData}
onChange={(e) => setDirectorData(e.target.value)}
/>
</div>
<div>
<button onClick={submitHandler}>Submit</button>
</div>
</div>
<div>
{listData.map((item, index) => {
return (
<li key={index}>
<span>{item.movie}</span>, <span>{item.director}</span>{" "}
<span style={{ marginLeft: "2rem" }}>
<button onClick={removeHandler(index)} style={{ color: "red" }}>
X
</button>
</span>
</li>
);
})}
</div>
</div>
);
}
export default App;
As soon as I added removeHandler, I am getting too many renders onSubmit. It's working fine once I remove removeHandler.
Is there any other method to remove items from the list apart from filter and splice(just mention the function name)?
I have even tried using useEffect but the same problem persists
Change to onClick={() => removeHandler(index)}
and you also have to remove 'return' in your array.filter
remId !== id;
});
This happens because you're not passing an arrow function to onClick.
You're writing onClick={removeHandler(index)} instead of onClick={() => removeHandler(index)}.
It's because functionName(param) syntax is generally used to call functions so when you write that, it causes infinite renders.
use this function some new changes
const removeHandler = (id) => { const newlist = [...listData];
newlist.splice(id, 1); setListData(newlist); };
and change onclick event
onClick={() => removeHandler(index)}
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>
</>
);
I am having trouble creating a shared component so I can reuse it somewhere else if needed. My shared component would take in props(array of lets say names) and displays a group of checkboxes with the names to the right on each one. How would I approach this?
Here you go:
you pass your props to your component like this:
import React from "react";
import "./styles.css";
import Checkboxes from "./Checkboxes"
const data = [ "name 1", "name 2", "name3"]
export default function App() {
return (
<div className="App">
<Checkboxes data={data} />
</div>
);
}
And on your component you map through that array, like this:
import React, { useState } from "react";
const Checkboxes = (props) => {
const [selected, setSelected] = useState([]);
const handleChange = (item) => {
if (selected.includes(item)) {
const newSelected = [...selected];
newSelected.splice(selected.indexOf(item), 1);
return setSelected(newSelected);
}
const newSelected = [...selected];
newSelected.push(item);
return setSelected(newSelected);
};
return (
<form>
{props.data.map((item) => {
return (
<React.Fragment key={Math.random()}>
<label>
<input
type="checkbox"
checked={selected.includes(item) ? true : false}
onChange={() => handleChange(item)}
name={item}
value={item}
/>
{item}
</label>
<br />
</React.Fragment>
);
})}
</form>
);
};
export default Checkboxes;
Here is the exemple sandbox
Assuming you have a constant NAMES defined:
const NAMES = ["one" , "two", "three"];
your render method of React component would look like this:
return (
<div>
{NAMES.map(el => (
<div key={el} >
<input type="checkbox" name={el} value={el}/>
<label htmlFor={el}> {el}</label>
</div>
))}
</div>
)
Explanation:
NAMES is an array. By mapping over this array, you are returning an array of JSX elements (those 4 lines starting with <div ... ). Each of this element is your checkbox. So by using map function, you save code and you create as much elements as you need, algoritmically.
Each element of the array has to have unique key prop. Also the name of the checkbox is displayed in the label tag.
Here is an example using function:
export default function CheckboxComponent({ arr }) {
return (
<>
<p>{arr}</p>
{arr.map((item) => (
<div key={item}>
<input type="checkbox" name={item} value={item} />
<label htmlFor={item}>{item}</label>
</div>
))}
</>
);
}
And to call your component, just import and you'll be able to use it normally:
import CheckboxComponent from "./components/checkbox-component";
export default function App() {
return (
<div className="App">
<CheckboxComponent arr={["Banana", "Apple"]} />
<CheckboxComponent arr={["Pineapple", "Watermelon"]} />
<CheckboxComponent arr={["Blueberry", "Strawberry"]} />
</div>
);
}
Here is the sandbox
You can do something like this:
const Item = React.memo(function Item({
label,
id,
checked,
onChange,
}) {
return (
<li>
<label>
<input
type="checkbox"
checked={checked}
onChange={() => onChange(id, !checked)}
/>
{label}
</label>
</li>
);
});
const ItemList = React.memo(function ItemList({
items,
onChange,
Item,
}) {
return (
<ul>
{items.map(({ id, label, checked }) => (
<Item
key={id}
id={id}
label={label}
checked={checked}
onChange={onChange}
/>
))}
</ul>
);
});
const CheckBoxList = React.memo(function CheckBoxList({
items,
onChange,
List = ItemList,
ListItem = Item,
}) {
return (
<List
items={items}
onChange={onChange}
Item={ListItem}
/>
);
});
const App = () => {
const [data, setData] = React.useState([
{ id: 1, label: 'item 1', checked: false },
{ id: 2, label: 'item 2', checked: false },
]);
const onChange = React.useCallback(
(id, checked) =>
setData((data) =>
data.map((item) =>
item.id === id ? { ...item, checked } : item
)
),
[]
);
return <CheckBoxList items={data} onChange={onChange} />;
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Here is another way of doing it:
const Item = React.memo(function Item({
value,
onChange,
checked,
}) {
return (
<li>
<label>
<input
type="checkbox"
checked={checked}
onChange={() => onChange(value, !checked)}
/>
{value}
</label>
</li>
);
});
const createItemContainer = (Component = Item) => ({
value,
onChange,
selected,
}) => (
<Component
value={value}
onChange={onChange}
checked={selected.includes(value)}
/>
);
const ItemList = React.memo(function ItemList({
values,
onChange,
Item,
selected,
}) {
return (
<ul>
{values.map((value) => (
<Item
key={value}
value={value}
onChange={onChange}
selected={selected}
/>
))}
</ul>
);
});
const DefaultItem = createItemContainer();
const CheckBoxList = React.memo(function CheckBoxList({
values,
onChange,
selected,
List = ItemList,
ListItem = DefaultItem,
}) {
return (
<List
values={values}
onChange={onChange}
Item={ListItem}
selected={selected}
/>
);
});
const App = () => {
const data = React.useMemo(() => ['A', 'B', 'C'], []);
const [selected, setSelected] = React.useState([]);
const onChange = React.useCallback(
(item, checked) =>
setSelected((selected) =>
checked
? selected.concat(item)
: selected.filter((s) => s !== item)
),
[]
);
return (
<div>
<div>
<CheckBoxList
values={data}
onChange={onChange}
selected={selected}
/>
</div>
<div>
<h4>Items selected</h4>
<pre>{JSON.stringify(selected)}</pre>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
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