How to toggle between one checkbox and a whole group of checkboxes? - javascript

My purpouse here is to create a group of checboxes. "Search everywhere" is default checked, if you check something else "Search everywhere" automatically unchecked, you can check as many different checkboxes as you want, until you check "search everywhere" again, if you do that all other checkboxes will unchecked.
I want to create it in function component with Hooks in React.
View: Image how it looks in browser
Everything is ready, but I stuck a little bit with toggle between one checkbox and group of checkboxes. I've tried useState and useEffect to controll useState callback. Thanks for help.
const ButtonCategory = (props) => {
const [state, setState] = useState({
normalCheckbox: false,
specialCheckbox: true
});
const { id, name, special, products } = props;
const toggleOthers = () => {
if (state.specialCheckbox) {
setState({
...state,
normalCheckbox: false // ofc its bad
});
} else if (state.normalCheckbox) {
setState({
...state,
specialCheckbox: false // ofc its bad
});
}
};
const toggleNormal = () => {
setState({
...state,
normalCheckbox: !state.normalCheckbox
});
};
const toggleSpecial = () => {
setState({
...state,
specialCheckbox: !state.specialCheckbox
});
};
useEffect(() => {
toggleOthers();
}, [state.specialCheckbox, state.normalCheckbox]);
return (
<>
<Label>
<StyledInput
type="checkbox"
id={id}
checked={special ? state.specialCheckbox : state.normalCheckbox}
onChange={special ? () => toggleSpecial() : () => toggleNormal()}
onClick={(e) => {
/* do something */
}}
/>{" "}
<div>
{" "}
{name} {special ? null : `(${products})`}
</div>
</Label>
</>
);
};

I believe you want something like this:
import React, { useState } from "react";
export const Checkboxes = () => {
const [checkedIds, setCheckedIds] = useState(new Set(["everywhere"]));
const handleCheck = ({ id, checked }) => {
if (checked) {
if (id === "everywhere") {
checkedIds.clear();
} else {
checkedIds.delete("everywhere");
}
checkedIds.add(id);
} else {
checkedIds.delete(id);
}
setCheckedIds(new Set(checkedIds));
};
return (
<form>
<label>
<input
id="everywhere"
type="checkbox"
checked={checkedIds.has("everywhere")}
onChange={(e) => handleCheck(e.target)}
/>{" "}
Search everywhere
</label>
<label>
<input
id="option-1"
type="checkbox"
checked={checkedIds.has("option-1")}
onChange={(e) => handleCheck(e.target)}
/>{" "}
Option 1
</label>
<label>
<input
id="option-2"
type="checkbox"
checked={checkedIds.has("option-2")}
onChange={(e) => handleCheck(e.target)}
/>{" "}
Option 2
</label>
</form>
);
};
Test case at codesandbox.io

May be this could be helpful
import React from "react";
import "./style.css";
export const App = () => {
const _checkboxes = [
{
id: "id1",
name: "111",
value: "111",
label: "111",
checked: true
},
{
id: "id2",
name: "222",
value: "222",
label: "222",
checked: false
},
{
id: "id3",
name: "333",
value: "333",
label: "333",
checked: false
}
];
const [checkboxes, setCheckboxes] = React.useState(_checkboxes);
const handleChange = id => e => {
setCheckboxes(checkboxes => {
const firstId = "id1";
const temp = checkboxes.map(c => {
if (firstId === id) {
c.checked = c.id === firstId ? !c.checked : false;
} else {
if (c.id === id) {
c.checked = !c.checked;
} else {
if (c.id === firstId) {
c.checked = false;
}
}
}
return c;
});
return [...temp];
});
};
return (
<div>
{checkboxes.map(checkbox => (
<div key={checkbox.id}>
<input
type="checkbox"
onChange={handleChange(checkbox.id)}
value={checkbox.value}
name={checkbox.name}
id={checkbox.id}
checked={checkbox.checked}
/>
<label htmlFor={checkbox.id}>{checkbox.label}</label>
</div>
))}
</div>
);
};
https://stackblitz.com/edit/react-rtxxfp?file=src%2FApp.js

Related

How to filter by ingredients on React

Currently I can filter by 1 ingredient, but when i try to select multiple ingredients (checkboxes), My recipes disappear as "ingredient1, ingredient2" is being passed to my searchfield rather than ingredient 1 & ingredient2.
My code for my search/filter is
import React, { useState } from "react";
import DisplayFoodItems from "./DisplayFoodItems";
import { ingredients } from "../data/Ingredients";
function Search({ details }) {
const [searchField, setSearchField] = useState("");
const [checked, setChecked] = useState([]);
const all = [...checked];
const filtered = details.filter((entry) => {
//Search box
if (entry.name.toLowerCase().includes(searchField.toLowerCase()) ||
entry.catagory.toLowerCase().includes(searchField.toLocaleLowerCase())) {
return (
entry.name.toLowerCase().includes(searchField.toLowerCase()) ||
entry.catagory.toLowerCase().includes(searchField.toLocaleLowerCase())
)
}
//Filter checkboxes
for (var i = 0; i < entry.ingredients.length; i++) {
console.log(searchField)
if (entry.ingredients[i].toLowerCase().includes(searchField.toLocaleLowerCase())) {
return (
entry.ingredients[i].toLowerCase().includes(searchField.toLocaleLowerCase())
);
}
}
}
);
const handleToggle = c => () => {
// return the first index or -1
const clickedBox = checked.indexOf(c);
if (clickedBox === -1) {
all.push(c);
console.log(all)
setSearchField(all.toString())
} else {
all.splice(clickedBox, 1);
setSearchField("")
}
console.log(all);
setChecked(all);
};
return (
<>
<div>
<input id="search"
className="form-control"
type="text"
placeholder="Search by recipe name or catagory"
onChange={(e) => setSearchField(e.target.value)}
/>
</div>
<div>
{ingredients.map((ingredient, index) => (
<label key={index} className="form-check-label">
<input
onChange={handleToggle(ingredient.name)}
type="checkbox"
className="mr-2"
/>
{ingredient.name}</label>
))}
<hr></hr>
</div>
<div class="flex">
<DisplayFoodItems foodList={filtered} />
</div>
</>
);
}
export default Search;
Here is a picture of my screen if it helps at all
All checkboxes work individually but for example, if salt and oil when checked together it should return Bacon wrapped chicken and Smashed potatoes, however it returns blank at present.
I have tried looping the all array and sending that to setSearchField, but again, I cannot get it to work.
I have tried looping through the array of checked ingredients and sending that to the setSearchField. Im expecting recipes to appear if they contain an ingredient that has been checked.
If I understand correctly you want something like this?
const foodList = [
{ name: "Food 1", ingredients: ["Salt"] },
{ name: "Food 2", ingredients: ["Oil"] },
{ name: "Food 3", ingredients: ["Milk"] },
{ name: "Food 4", ingredients: ["Salt", "Oil"] },
{ name: "Food 5", ingredients: ["Oil", "Milk"] },
{ name: "Food 6", ingredients: ["Oil", "Salt", "Milk"] },
];
const ingredientList = ["Salt", "Oil", "Milk"]
const Example = () => {
const [ingredients, setIngredients] = useState([]);
const filtered = foodList.filter(food => {
return ingredients.length === 0
|| food.ingredients.length > 0 && ingredients.every(selectedIngredient =>
food.ingredients.some(foodIngredient => foodIngredient.toLowerCase() === selectedIngredient.toLowerCase()));
});
return (
<div>
<h2>Filter</h2>
{
ingredientList.map((ingredient, i) => {
const checked = ingredients.some(selectedIngredient => ingredient.toLowerCase() === selectedIngredient.toLowerCase());
return (
<label key={`ingredient-${i}`}>
<input
type="checkbox"
checked={checked}
onChange={() => {
if (checked) {
setIngredients(ingredients.filter(selectedIngredient => ingredient.toLowerCase()!== selectedIngredient.toLowerCase()))
}
else {
setIngredients([...ingredients, ingredient]);
}
}} />
{ingredient}
</label>
);
})
}
<br />
<br />
<h2>Food list</h2>
<pre>{JSON.stringify(filtered, null, 4)}</pre>
</div>
);
}

react update array of object with checked field in state

const data = [
{
id: "1",
checked: false,
},
{
id: "2",
checked: false,
},
{
id: "3",
checked: false,
},
{
id: "4",
checked: false,
},
];
const [state, setState] = useState(data)
{state.map((item, index) => (
<div key={index}>
<input checked={item.checked} type="checkbox" />
<span>{item}</span>
</div>
))}
How could I update the checked whenever the checkboxes are tick and untick?
Some examples, but I would like the items checked are to be an array like
[{id: "1",checked: true,},{id: "2",checked: true,}];
You can add onClick to the input and update your state data based on the selected item.
const handleChange = (id) => {
const clonedData = [...state];
setState(
clonedData.map((d) => (d.id === id ? { ...d, checked: !d.checked } : d))
);
};
//// Your remaining code
<input
checked={item.checked}
type="checkbox"
onClick={() => handleChange(item.id)}
/>;
Attached is a sandbox for reference.
Given the data you gave us as the state, you can create a copy of the state and toggle the one you have tiggered the checking.
const [state, setState] = useState(data)
const handleChange = (id) = {
const newState = state.map(item => item.id === id? {...item, state: !item.checked} : item)
setState(newState)
}
{state.map((item, index) => (
<div key={index}>
<input checked={item.checked} type="checkbox" onChange={(e) => handleChange(item.id)}/>
<span>{item}</span>
</div>
))}
You can create a new function and on change update the previous state
export default function App() {
const data = [
{
id: '1',
checked: false,
},
{
id: '2',
checked: false,
},
{
id: '3',
checked: false,
},
{
id: '4',
checked: false,
},
];
const [state, setState] = React.useState(data);
function updateState(id) {
const newVal = state.map((item) => {
if (item.id === id) {
return {
...item,
checked: !item.checked,
};
} else {
return {
...item,
};
}
});
setState(newVal);
}
return (
<div>
{state.map((item, index) => (
<div key={index}>
<input
checked={item.checked}
type="checkbox"
onChange={() => updateState(item.id)}
/>
<span>{item.id}</span>
</div>
))}
</div>
);
}

How to customize Ant table rowselection

I used Ant table to show some information.
https://codesandbox.io/s/proud-architecture-lsb85?file=/src/index.js
I want to customize the position of the checkbox for row selection.
In this application, you can see the header in the following order of checkbox, Name, Age, Address but I want to swap checkbox and Name.
You can add checkbox columns and customize render and titleRender of it to checkbox and then handle the events. if you incounter performance issue you have to add some memoization on columns or evenet handlers.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import { Table, Button, Checkbox } from "antd";
const data = [];
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`
});
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRowKeys: [], // Check here to configure the default column
loading: false,
allChecked: false
};
this.columns = [
{
title: "Name",
dataIndex: "name"
},
{
dataIndex: "checked",
title: () => {
return (
<Checkbox
checked={this.state.allChecked}
onChange={(e) => this.selectAll(e)}
></Checkbox>
);
},
render: (text, rec, index) => {
return (
<Checkbox
checked={
this.state.selectedRowKeys.includes(rec.key) ||
this.state.allChecked
}
onChange={(e) => this.onChange(e, rec)}
></Checkbox>
);
}
},
{
title: "Age",
dataIndex: "age"
},
{
title: "Address",
dataIndex: "address"
}
];
}
start = () => {
this.setState({ loading: true });
// ajax request after empty completing
setTimeout(() => {
this.setState({
selectedRowKeys: [],
loading: false
});
}, 1000);
};
onChange = (e, rec) => {
const checked = e.target.checked;
if (checked) {
this.setState((state) => ({
...state,
selectedRowKeys: [...state.selectedRowKeys, rec.key]
}));
} else {
this.setState((state) => ({
...state,
selectedRowKeys: [
...state.selectedRowKeys.filter((item) => item !== rec.key)
]
}));
}
};
selectAll = (e) => {
const checked = e.target.checked;
if (checked) {
this.setState((state) => ({
...state,
allChecked: true
}));
} else {
this.setState((state) => ({
...state,
allChecked: false
}));
}
};
onSelectChange = (selectedRowKeys) => {
console.log("selectedRowKeys changed: ", selectedRowKeys);
this.setState({ selectedRowKeys });
};
render() {
const { loading, selectedRowKeys } = this.state;
const hasSelected = selectedRowKeys.length > 0;
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
onClick={this.start}
disabled={!hasSelected}
loading={loading}
>
Reload
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} items` : ""}
</span>
</div>
<Table columns={this.columns} dataSource={data} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));

Conditionally disable React Checkbox

I am trying to conditionally disable the checkbox in react, based on the count. Passing the value through props whether it is checked and greater than the number. I am saving the name in the state to further process it to send to in the backend database.
Here is my react code.
class CheckboxComponent extends Component {
constructor(props) {
super(props);
this.state = {
checkedItems: {}
};
}
handleChange = (event, formKey) => {
const {checkedItems} = this.state;
const checkedValues = {...checkedItems};
checkedValues[event.target.name] = event.target.checked;
this.setState((prevState, currState) => {
return {
...prevState,
checkedItems: checkedValues
}
});
};
render = () => {
const {checkedItems} = this.state;
const checkedValues = {...checkedItems};
const checkedCount = Object.values(checkedValues).length;
const checked = Object.values(checkedValues);
const disabled = checkedCount >= 3;
return (
<div>
{checkboxes.map((item, index) => (
<label className={`form__field__input__label`} key={item.key}>
<Input
type={`checkbox`}
name={item.name}
checked={this.state.checkedItems[item.name] || false}
onChange={this.handleChange}
formKey={'subjects'}
disabled={(!checked[index] && checked.length > 3)}
/>
{item.name}
</label>
))}
</div>
)
This is the Array that I am passing to render the values in the checkbox
const checkboxes = [
{
name: "Math and economics",
key: "mathsandeconomics",
label: "Math and economics"
},
{
name: "Science",
key: "Science",
label: "Science"
},
The below code snippet will work fine for you. And you can sent object to the backend having maximum of only 3 properties set to true. Get the full code from codesandbox link https://codesandbox.io/s/emmeiwhite-0i8yh
import React from "react";
const checkboxes = [
{
name: "Math and economics",
key: "mathsandeconomics",
label: "Math and economics",
},
{
name: "Science",
key: "science",
label: "Science",
},
{
name: "history",
key: "history",
label: "history",
},
{
name: "literature",
key: "literature",
label: "literature",
},
];
class CheckboxComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
checkedItems: {},
count: 0,
};
}
handleChange = (event, formKey) => {
const { name, checked } = event.target;
const updatedCheckedItems = { ...this.state.checkedItems, [name]: checked };
this.setState({
checkedItems: updatedCheckedItems,
count: Object.values(updatedCheckedItems).filter((value) => value).length,
});
};
render = () => {
const checkedValues = { ...this.state.checkedItems };
const checkedCount = Object.values(checkedValues).filter((value) => value)
.length;
console.log(this.state.checkedItems);
return (
<div>
{checkboxes.map((item, index) => (
<label className={`form__field__input__label`} key={item.key}>
<input
type={`checkbox`}
name={item.name}
checked={this.state.checkedItems[item.name] || false}
onChange={this.handleChange}
disabled={!checkedValues[item.name] && checkedCount > 2}
/>
{item.name}
</label>
))}
</div>
);
};
}
export default CheckboxComponent;
Your checked.length counts all touched boxes, not checked only. If you uncheck an input, it still will be counted. Count only true, for example Object.values(checkedValues).filter(value => value).length.
Use names instead of indexes: disabled={!checkedValues[item.name] && checkedCount > 3}
You can see full solution here: https://codesandbox.io/s/confident-http-vlm04?file=/src/App.js
event.target.getAttribute('name');
try this to get name attribute, pretty sure event.target.name is 'undefined'
I see one use case is not taken care of. checkedCount should count the number of true values only.
const checkedCount = Object.values(checkedValues).length; // existing
const checkedCount = Object.values(checkedValues).filter(item=>item==true).length //replace with this line
This would solve the problem.
Here is the code and as well as codesandbox link
Codesandbox Link
import React from "react";
export class CheckboxComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
checkedItems: {},
checkedCount: 0
};
}
handleChange = (event, formKey) => {
const { checkedItems } = this.state;
const checkedValues = { ...checkedItems };
checkedValues[event.target.name] = event.target.checked;
this.setState((prevState, currState) => {
return {
...prevState,
checkedItems: checkedValues,
checkedCount: event.target.checked
? prevState.checkedCount + 1
: prevState.checkedCount - 1
};
});
};
render = () => {
const { checkboxes } = this.props;
const { checkedCount } = this.state;
const disabled = checkedCount >= 3;
return (
<div>
<p></p>
{checkboxes.map((item, index) => (
<label className={`form__field__input__label`} key={item.key}>
<input
type={`checkbox`}
name={item.name}
checked={this.state.checkedItems[item.name] || false}
onChange={this.handleChange}
disabled={!this.state.checkedItems[item.name] ? disabled : false}
/>
{item.name}
</label>
))}
</div>
);
};
}

Check all checkboxes, and uncheck if any of them deselected in React without jQuery

I would like to select all checkboxes. But if user deselect any checkbox, i want also uncheck "select all" checkbox.
In given example below, How can achieve this?
Live Demo: https://react-f3tcbc.stackblitz.io
Live Editor: https://stackblitz.com/edit/react-f3tcbc
Some samples I looked refer checked as boolean parameter in items. However my items object comes from ajax response as json and they have no value such as checked.
I want to do this in React way. Not jQuery. Any ideas?
One way of going about it is to add an extra property called e.g. isChecked to the data you get from the network request, and use that to control all the
checkboxes.
Example
const posts = [
{
id: 1,
name: "Text 1"
},
{
id: 2,
name: "Text 2"
},
{
id: 3,
name: "Text 3"
}
];
class App extends React.Component {
state = {
name: "React",
posts: [],
isAllChecked: false
};
componentDidMount() {
setTimeout(() => {
this.setState({
posts: posts.map(post => ({ ...post, isChecked: false }))
});
}, 1000);
}
handleSelect = id => {
this.setState(prevState => {
const posts = prevState.posts.map(post =>
post.id === id ? { ...post, isChecked: !post.isChecked } : post
);
const isAllChecked = posts.every(post => post.isChecked);
return { posts, isAllChecked };
});
};
handleSelectAll = () => {
this.setState(prevState => {
const isAllChecked = !prevState.isAllChecked;
const posts = prevState.posts.map(post => ({
...post,
isChecked: isAllChecked
}));
return { posts, isAllChecked };
});
};
render() {
const { posts, isAllChecked } = this.state;
return (
<div>
{posts.map(fx => (
<TableItem
key={fx.id}
id={fx.id}
name={fx.name}
checked={fx.isChecked}
onChange={() => this.handleSelect(fx.id)}
/>
))}
<div>
<label>
<input
type="checkbox"
checked={isAllChecked}
onChange={this.handleSelectAll}
/>
Select all
</label>
</div>
</div>
);
}
}
class TableItem extends React.Component {
render() {
const { checked, onChange, name } = this.props;
return (
<tr>
<td>
<input type="checkbox" checked={checked} onChange={onChange} />
</td>
<td>{name}</td>
</tr>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Categories