Select Multiple Checkboxes with either Select All OR Individually - javascript

So I have a little situation that I am stuck with. I have an array of holidays that looks like so:
export const holidays = {
federal: [
{ name: "New Year's Day", selected: false },
{ name: 'Martin Luther King, Jr. Day', selected: false },
{ name: "George Washington's Birthday", selected: false },
{ name: 'Memorial Day', selected: false },
{ name: 'Independence Day', selected: false },
{ name: 'Labor Day', selected: false },
{ name: 'Columbus Day', selected: false },
{ name: 'Veterans Day', selected: false },
{ name: 'Thanksgiving Day', selected: false },
{ name: 'Christmas Day', selected: false }
],
other: [
{ name: 'Black Friday', selected: false },
{ name: 'Christmas Eve', selected: false },
{ name: "New Year's Eve", selected: false }
]
};
My goal is to render 2 lists of holidays - one with a Federal Holidays label, and the other with a Other Holidays label. I need to be able to select all the checkboxes with an Add all options, and I also need to be able to select the check boxes individually.
Right now, only my Add all functionality is working. Here is my code:
// CheckboxList component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import camelCase from 'lodash';
import Checkbox from 'common/components/Checkbox';
class CheckList extends Component {
static propTypes = {
data: PropTypes.array,
id: PropTypes.string.isRequired
};
...
get checkboxList() {
const { data } = this.props;
const { checked } = this.state;
return data.map(item => (
<Checkbox
id={camelCase(item)}
key={camelCase(item)}
checked={checked}
label={item}
/>
));
}
handleSelectAllCheckboxToggle() {
this.setState(({ checked }) => ({ checked: !checked }));
}
render() {
const { id } = this.props;
return (
<div className={this.baseClass}>
<Checkbox
id={id}
label="Add all"
onChange={this.handleSelectAllCheckboxToggle}
/>
{this.checkboxList}
</div>
);
}
}
The above is a reusable component that basically renders checkboxes with labels depending on how many items are inside the data prop (array) that is passed into the component. This reusable component is then passed into a parent component which is going to render the checkbox list:
// HolidaySchedule component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import camelCase from 'lodash/camelCase';
import { holidays } from 'modules/utilities/constants';
import CheckboxList from './CheckboxList';
import Panel from 'common/components/Panel';
class HolidaySchedule extends Component {
get holidaysList() {
return Object.keys(holidays).map(holiday => (
<CheckboxList
key={camelCase(holiday)}
data={this.holidaysData(holiday)}
id={`${holiday}Holidays`}
/>
));
}
holidaysData(type) {
return holidays[type].map(holiday => holiday.name);
}
render() {
return (
<Panel>
{this.holidaysList}
</Panel>
);
}
}
This all ends up rendering a list of checkboxes with the holidays as their labels:
Sorry for the kinda long and in-depth post, I just wanted to make sure I didn't leave anything out. The functionality when clicking Add all for each group of checkboxes (Federal or Other) works fine.
However, I do not know what to do in order to also add functionality where I can select the checkboxes individually. I have only gotten as far as being able to select only one checkbox at a time, and it deselects the previous one selected. I would like to be able to Add all to select all checkboxes, click one of the checkboxes to uncheck it and deselect the Add all checkbox, and also just select one checkbox at a time. Stumped!
If anyone took the time to go through all this, thank thank you already!! If anyone has any advice or direction, then thank you a million times more!!!!

Give a man a fish, and you feed him for a day. Teach a man to fish, and you feed him for a lifetime
Are you looking for something like this ? I suppose this is the logic that you are looking for.
I've wrote a checkbox list demo with select all function for you. Its just logic, nothing looking fancy.
import React from "react";
import ReactDOM from "react-dom";
const App = () => {
const [holidays, setHolidays] = React.useState({
federal: [
{ name: "New Year's Day", selected: false },
{ name: "Martin Luther King, Jr. Day", selected: false },
{ name: "George Washington's Birthday", selected: false },
{ name: "Memorial Day", selected: false },
{ name: "Independence Day", selected: false },
{ name: "Labor Day", selected: false },
{ name: "Columbus Day", selected: false },
{ name: "Veterans Day", selected: false },
{ name: "Thanksgiving Day", selected: false },
{ name: "Christmas Day", selected: false }
],
other: [
{ name: "Black Friday", selected: false },
{ name: "Christmas Eve", selected: false },
{ name: "New Year's Eve", selected: false }
]
});
const handleOnChange = (e, type) => {
const { name, checked } = e.target;
const newHoliday = [...holidays[type]];
const index = newHoliday.findIndex(h => h.name === name);
if (index > -1) {
newHoliday[index] = { name, selected: checked };
}
setHolidays(h => ({ ...h, [type]: newHoliday }));
};
const handleOnSelectAll = (e, type) => {
const { checked } = e.target;
let newHoliday = [...holidays[type]];
if (!checked) {
newHoliday = newHoliday.map(opt => ({ ...opt, selected: false }));
} else {
newHoliday = newHoliday.map(opt => ({ ...opt, selected: true }));
}
setHolidays(h => ({ ...h, [type]: newHoliday }));
};
const renderCheckboxList = (options, type) =>
options.map(opt => (
<div>
<label>
<input
type="checkbox"
name={opt.name}
onChange={e => handleOnChange(e, type)}
checked={opt.selected}
/>
{opt.name}
</label>
</div>
));
const renderSelectAllCheckbox = type => (
<div>
<label>
<input
type="checkbox"
onChange={e => handleOnSelectAll(e, type)}
checked={holidays[type].every(opt => opt.selected)}
/>
{`Select All ${type}`}
</label>
</div>
);
return (
<section style={{ display: "flex", justifyContent: "space-around" }}>
<div>
<div>
<fieldset>
Federal Holidays
{renderSelectAllCheckbox("federal")}
{renderCheckboxList(holidays.federal, "federal")}
</fieldset>
</div>
<div>
<fieldset>
Other Holidays
{renderSelectAllCheckbox("other")}
{renderCheckboxList(holidays.other, "other")}
</fieldset>
</div>
</div>
<div>
State:
<pre>{JSON.stringify(holidays, null, 2)}</pre>
</div>
</section>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Some tips for you, if you want the final value to be in array form, you can use filter and map to get the name of the option.
Here is a working example: https://codesandbox.io/s/react-controlled-checkbox-list-f4t7g?fontsize=14&hidenavigation=1&theme=dark
Update (11/14/2019)
I've recreated another sandbox demo using React class. This time, I've created a component similar to yours (CheckboxList) that has name and onChange callback. Using these props allow you to update your parent holidays state.
You can find the working example here: https://codesandbox.io/s/react-class-controlled-checkbox-list-ejlfn?fontsize=14&hidenavigation=1&theme=dark
I think what you are missing is the onChange callback to update your parent state everytime there are changes made to the checkboxes. Your code are missing some important stuffs.

Related

Working with state on recursive components

I'm writing a component that renders itself inside recursively and is data-driven
Attaching my sandbox snippet, as it will be easier to see there.
This is my data:
var builderStructureData = [
{
id: 123,
value: 3,
children: []
},
{
id: 345,
value: 5,
children: [
{
id: 4123,
value: 34,
children: [
{
id: 342342,
value: 33,
children: []
}
]
},
{
id: 340235,
value: 3431,
children: [
{
id: 342231342,
value: 3415,
children: []
}
]
}
]
}
];
and it renders like this:
This is my App.js:
import { useState } from "react";
import "./App.css";
import Group from "./components/group";
import builderStructureData from "./components/builderStructureData";
function App() {
const [builderStructure, setBuilderStructure] = useState(
builderStructureData
);
return (
<div className="App">
{builderStructure.map((x) => {
return <Group key={x.id} children={x.children} value={x.value} />;
})}
</div>
);
}
export default App;
And this is my recursive component:
import React from "react";
export default function Group(props) {
let childrenArray = [];
if (props.children) {
props.children.map((x) => childrenArray.push(x));
}
return (
<div className="group" draggable>
<p>this is value: </p>
<input value={props.value} readOnly={true}></input>
<button>Add Group</button>
{childrenArray.map((x) => {
return <Group key={x.id} children={x.children} value={x.value} />;
})}
</div>
);
}
I can render the components based on the data, and it seems to be handling recursion fine. I need to store the state on the App.js page and be able to change it from within child components. For example, if I update the "value" field of the component with ID = 342342, I want it to update that corresponding object in the state no matter how deeply nested it is, but not sure how to do that as it is not as simple as just passing a prop.
Am I taking the right approach with my code snippet? How can I do the state update?
I would advise the state normalization approach - here is an example for redux state - https://redux.js.org/usage/structuring-reducers/normalizing-state-shape - but you can use this approach with your state. So - your state will look like this:
state = {
items: {
[123]: {
id: 123,
value: 3,
childrenIds: []
},
[345]: {
id: 345,
value: 5,
childrenIds: [4123, 340235]
},
[4123]: {
id: 4123,
value: 34,
parentId: 345,
childrenIds: [342342]
},
[342342]: {
id: 342342,
value: 33,
parentId: 4123,
childrenIds: []
},
[340235]: {
id: 340235,
value: 3431,
parentId: 345,
childrenIds: [342231342]
},
[342231342]: {
id: 342231342,
value: 3415,
parentId: 340235
childrenIds: []
}
}
}
Here the field "childrenIds" is an optional denormalization for ease of use, if you want - you can do without this field. With this approach, there will be no problem updating the state.
You are thinking this in a wrong way, it should be very easy to do what you want.
The most imported thing is to make a little small changes in Group
Please have a look
import React from "react";
export default function Group(props) {
const [item, setItem] = React.useState(props.item);
let childrenArray = [];
if (item.children) {
item.children.map((x) => childrenArray.push(x));
}
const updateValue = ()=> {
// this will update the value of the current object
// no matter how deep its recrusive is and the update will also happen in APP.js
// now you should also use datacontext in app.js togather with state if you want to
// trigger somethings in app.js
item.value =props.item.value= 15254525;
setState({...item}) // update the state now
}
return (
<div className="group" draggable>
<p>this is value: </p>
<input value={item.value} readOnly={true}></input>
<button>Add Group</button>
{childrenArray.map((x) => {
return <Group item={x} />;
})}
</div>
);
}
The code above should make you understand how easy it is to think about this as an object instead of keys.
Hop this should make it easy for you to understand

Changing state of object nested in array of objects

As stated in the title I'm trying to change the state of an object nested in an array of objects. I just can not get this to work. I've added a sample of what I'm trying to do in a codesandbox.
I'm using a Material-UI component call ToggleButton (link to demo ToggleButton). I want to change the state to toggle the buttons in the group. I was able to get this working for a create function but can not get it working for my update function.
Trying to change the values of the object in the array is just not working for me. Below are some things I've tried to no success. I want to change the IsTrue: so I can toggle the button to display the users selection.
setAppetizer(true);
setRecipeObjectState({
...recipeObjectState,
Category: [
...recipeObjectState.Category,
{
IsTrue: appetizer,
category: "Appetizer",
},
],
});
This just adds more buttons to my button group.
setAppetizer(true);
setRecipeObjectState((prevState) => ({
...prevState,
Category: [
{
IsTrue: appetizer,
category: "Appetizer",
},
],
}));
I'm just lost now at this point. I just want to also state that this is my first React project that is not just a sandbox for learning the framework and I'm also a jr. developer trying to learn. I have search stackoverflow and nothing has helped me out. I hope I have included enough information for someone to help out.
Your state and app appear to be very convoluted, but the general idea when updating nested array state is to shallowly copy the state at each level where an update is being made. Use array::map to map the Category property to a new array object reference and when the category matches toggle the IsTrue "selected" property.
setRecipeObjectState((prevState) => ({
...prevState,
Category: prevState.Category.map((category) =>
category.category === newCategory // <-- newCategory value from toggled button
? {
...category,
IsTrue: !category.IsTrue
}
: category
)
}));
Since your "selected" calculation is selected={Boolean(item.IsTrue)} you'll want to ensure your IsTrue element values are actually togglable, i.e. just store the boolean value right in the array.
const recipeObject = {
AuthorId: authorId,
BookAuthor: bookAuthor,
BookTitle: bookTitle,
Calories: parseInt(calories),
Category: [
{
IsTrue: false,
category: "Appetizer"
},
{
IsTrue: false,
category: "Breakfast"
},
{
IsTrue: false,
category: "Soup / Salad"
},
{
IsTrue: false,
category: "Vegetarian"
},
{
IsTrue: true,
category: "Meat (Beef, Pork, Chicken)"
},
{
IsTrue: false,
category: "Fish"
},
{
IsTrue: false,
category: "Dessert"
}
],
Description: description,
DurationInMinCook: parseInt(durationInMinCook),
DurationInMinPrep: parseInt(durationInMinPrep),
ImageUrl: imageUrl,
Ingredients: addedIngredients, // array
Instructions: addedInstructions, // array
IsRecipe: true,
Likes: 0,
RecipeId: selectedRecipeId,
ServingSize: parseInt(servingSize),
Title: title,
YouTubeUrl: youTubeUrl
};
You mutating the same reference, you need to render a copy or the component won't render (shallow comparison):
Try updating state like this.
const oldState = recipeObjectState;
oldState.Category = [
{
IsTrue: appetizer,
category: "Appetizer"
}
];
setRecipeObjectState(oldState);
I didn't try your component because it's huge.
Updating a single object property in an array of objects seems to be a very common use-case. Here is generally how you do that. Suppose id is a unique identifier of each object and that we want to toggle selected:
import React, { useState } from "react";
import "./styles.css";
import faker from "faker";
const array = [];
for (let id = 1; id < 10; id++) {
array.push({
id,
name: faker.name.firstName(),
age: faker.random.number(100),
selected: false
});
}
export default function App() {
const [list, setList] = useState(array);
const onClick = (id) => (event) => {
setList((list) =>
list.map((item) =>
item.id === id ? { ...item, selected: !item.selected } : item
)
);
};
return (
<div className="App">
Click on a item:
<ul>
{list.map(({ id, name, age, selected }) => (
<li key={id} onClick={onClick(id)} className="item">
{name} {age}
{selected ? " ✅" : null}
</li>
))}
</ul>
</div>
);
}
https://codesandbox.io/s/xenodochial-river-9sq6g?file=/src/App.js:0-825

reset value when using react-select

I am using react-select for my select dropdown. The issue I am having is that there is no empty option to reset the dropdown value if the user changes their mind.
Currently I am taking the options and manually adding an empty string, but I feel there must be something already in the library to handle this? I cannot find anything in the docs.
My code looks like the below, and there is a code sandbox here.
import React from "react";
import Select from "react-select";
const App = () => {
const options = [
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
];
return <Dropdown options={options} />;
}
const Dropdown = ({ options }) => {
const optionsWithEmptyOption = [{ value: "", label: "" }, ...options];
return <Select options={optionsWithEmptyOption} />;
};
Plase check this out
https://codesandbox.io/s/zow1c?module=/example.js
import React, { Component } from 'react';
import CreatableSelect from 'react-select/creatable';
import { colourOptions } from './docs/data';
export default class CreatableSingle extends Component<*, State> {
handleChange = (newValue: any, actionMeta: any) => {
console.group('Value Changed');
console.log(newValue);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
};
handleInputChange = (inputValue: any, actionMeta: any) => {
console.group('Input Changed');
console.log(inputValue);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
};
render() {
return (
<CreatableSelect
isClearable
onChange={this.handleChange}
onInputChange={this.handleInputChange}
options={colourOptions}
/>
);
}
}
Empty Unicode
I add line to options, and write between the apostrophes empty unicode like this: ⠀⠀⠀⠀⠀⠀⠀⠀ .. you can mark it but dont see it.
const options = [
{ value: "", label: "⠀" },
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
];
And I change this:
return <Select options={options} />;
what about const optionsWithEmptyOption = [{ value: null, label: "Select..." }, ...options];
I'm not really good with explanations, but #NicoHaase is right, so here it goes...
as far as I know, you must give a value to value null (if nothing) or string ... same for the label, #1 because of the user UX and second so react-select knows what to display. But if you really need to leave it in black, and you can try to modify in the styles, in order to have the same height as the other options.

How to set multiple dropdown values to each dynamic element Semantic UI React

I'm having trouble figuring out how to set a dynamic dropdown component with multiple-value selections to each rendered element in a feature I'm working on. I think I'm really close but ultimately need a bit of guidance.
Here's the component:
import React, { Component } from 'react'
import { List, Dropdown, Label } from 'semantic-ui-react'
const directions = [
{key: "0.0", text: "0.0", value: "0.0"},
{key: "22.5", text: "22.5", value: "22.5"},
{key: "45.0", text: "45.0", value: "45.0"},
{key: "67.5", text: "67.5", value: "67.5"},
{key: "90.0", text: "90.0", value: "90.0"}
]
const channels = [
{ch: 65, callsign: "TEST1"},
{ch: 49, callsign: "TEST2"},
{ch: 29, callsign: "TEST3"}
]
export default class DirectionalSelection extends Component {
constructor(props) {
super(props)
this.state = {
channels,
directions,
currentValues: {}
}
}
handleDropdownChange = (e, index, { value }) => {
this.setState(({ currentValues }) => {
currentValues[index] = value
return currentValues
})
}
handleDirAddition = (e, index, { value }) => {
this.setState(({ directions }) => {
directions[index] = [{ text: value, value }, ...this.state.directions]
return directions
})
}
render() {
const { channels, currentValues, directions } = this.state
return (
<div>
<List>
{channels.map((el, index) => (
<List.Item key={index}>
<Label>{el.ch}</Label>
<Dropdown
search
multiple
selection
scrolling
allowAdditions
options={directions}
value={currentValues[index]}
placeholder='Choose directions'
onAddItem={this.handleDirAddition.bind(this, index)}
onChange={this.handleDropdownChange.bind(this, index)}
/>
</List.Item>
))}
</List>
</div>
)
}
}
Right now every time I select dropdown values on any channel, currentValues returns as [object Object]: ["22.5", "45.0"]. I want to set the ch key in channels as the key and the dropdown values array as the value and append them to currentValues.
I hope I've clarified the question enough to understand. Here is a link to Semantic-UI-React docs with the original component I'm using: https://react.semantic-ui.com/modules/dropdown#dropdown-example-multiple-allow-additions. Thanks for the help!
I figured it out! It was so simple, just had to switch the params in handleDropdownChange = (e, index, { value }) to handleDropdownChange = (index, e, { value }). It was setting the event function as the object key.

Multi-level Select Input

I think an example will best illustrate my question. Suppose I have a form to select books and their pages. The user will have one multi-select dropdown to select the books. Then, from each book, the user can select any number of pages. In the end, the data will look like a two-dimensional array. The top level is the array of books chosen, and the second level is the pages selected for each book:
books: {
"Book 1": ["page1", "page2", "page3"],
"Book 2": ["page1", "page2"]
}
The dropdown for books will be automatically populated. The options for the dropdown to select the pages for a book will be populated on loading.
Here is a hard coded example just to see the basic layout: http://codepen.io/alanqthomas/pen/JbVGzW?editors=1000
I'm doing this in React. I can't figure out a way to set these values in the component's state and display them dynamically. I'm using react-select for the multi-select selection inputs.
Sure. Here is what you want. As you want nested pages in books then the setState I did will need modifying but here's a start for you.
If you need help with it just ask.
class Form extends React.Component {
constructor() {
super();
}
render() {
return (
<form>
<div>
<Books />
</div>
</form>
);
}
}
class Books extends React.Component {
constructor() {
super();
this.state = {
books: {
Book1: {
pages: [
{
text: Page 1,
value: Page1
},
{
text: Page 2,
value: Page2
}],
text: Book 1,
value: Book1
},
Book2: {
pages: [
{
text: Page 1,
value: Page1
},
{
text: Page 2,
value: Page2
}],
text: Book 2,
value: Book2
}
}
}
this.state.currentBookKey = this.state.books.Book1.value;
}
selectOnChange = (event) => {
this.setState((state) => ({ currentBookKey: event.target.value }))
}
render() {
return (
<div>
<label>Book</label>
<select multiple name="book" onChange={this.selectOnChange}>
{this.state.books.map(book => <Book value={book.value}}>{book.text}</Book>)}
</select>
<label>Pages to select</label>
<select multiple name="pages">
{this.state.books[this.state.currentBookKey].pages.map(page => <Page value={page.value}}>{page.text}</Page>)}
</select>
</div>
)
}
}
Stateless components:
//React.children.only gives us an exception if more than one child
const Book = ({children, value}) => (
<option value={value}>{React.Children.only(children)}</option>
);
const Page = ({children, value}) => (
<option value={value}>{React.Children.only(children)}</option>
);

Categories

Resources