How to use checkbox to achieve multiple selection and single selection? - javascript

Through the official documentation of antd we can know how to use the checkbox to complete the switch between multiple selection and single selection.
https://ant.design/components/checkbox/
My question is, if my checkbox data comes from a backend service, how should I maintain my data? It's accurate to say when I save the data in state of class so that the changes to the UI can be affected by changes in the data like the official documentation.
Now I try to traverse the back-end data when rendering the Dom, the following example code:
import { Checkbox } from 'antd';
const CheckboxGroup = Checkbox.Group;
class App extends React.Component {
state = {
indeterminate: true,
checkAll: false,
};
render() {
return (
<div>
<div style={{ borderBottom: '1px solid #E9E9E9' }}>
<Checkbox
indeterminate={this.state.indeterminate}
onChange={this.onCheckAllChange}
checked={this.state.checkAll}
>
Check all
</Checkbox>
</div>
<br />
{
this.renderDomFunction(data)
}
</div>
);
}
// data is from back-end server
renderDomFunction = (data) => {
let plainOptions = []
let defaultCheckedList = []
let dom
data.map(item => {
plainOptions.push(
{
label: <div>this is Orange</div>,
value: 'Orange',
disabled: false
},
{
label: <div>this is Apple</div>,
value: 'Apple',
disabled: false
},
)
defaultCheckedList.push('Orange','Apple')
})
return (
dom = <li>
<CheckboxGroup
options={plainOptions}
value={defaultCheckedList}
onChange={this.onChange}
/>
</li>
)
}
onChange = () => {
// code...
// I can't change the state of the checkbox by changing the data now, because isn't maintained in the state of Class.
}
}
ReactDOM.render(<App />, mountNode);
I also tried to put the setstate() function into the renderDomFunction but this would cause an infinite loop.
Thank you!

Related

dynamically generated Toggle (switches) in react js not working

There are two types of switch status in my project. One is default and the other is generated from API.When the item is changed toggle switch on/off won't work.
constructor(props) {
super(props);
this.state = {
switch_status: [true, false],
items: [{title:toyota}, {title:bmw}]
}
}
There is a function, Which get data from API and set into items:
changeItems = () => {
this.setState({ items: [{title:toyota, switch_status: true},
{title:porche, switch_status: true},
{title:bmw, switch_status: false}]
});
}
on/off not working, When Items changed:
//Switch on/off function
handleChange = (event, id) => {
const isChecked = event;
this.setState(
({switch_status}) => ({
switch_status: {
...switch_status,
[id]: isChecked,
}
})
);
}
//Loop Items
this.state.items.map((item, index) => (
<Switch
className="custom-switch custom-switch-primary"
checked={this.state.switch_status[index]}
id={index}
onChange={event => handleChange(event, index)}
/>
))
There is nothing wrong in your state handling logic really but your componentDidUpdate() is getting called infinite times because the check inside is not working and it overwrites your toggle state even when you don't need to.
Change you componentDidUpdate() to:
componentDidUpdate(previousProps, previousState) {
if (
JSON.stringify(previousProps.mediaTypes.items) !==
JSON.stringify(this.props.mediaTypes.items)
) {
this.dataListRender();
this.setState({
customMediaTypesItems: this.props.mediaTypes.items.custom_media_types
});
}
}
First of all; you are passing a new reference to a component as prop on every render and that causes needless DOM updates
Second is that you initialise the state with a different structure than when you are setting state. I assume that
{
items: [
{ title: toyota, switch_status: true },
{ title: porche, switch_status: true },
{ title: bmw, switch_status: false }
];
}
Is your actual state because you use that to render. You can do the following:
const Switch = React.memo(
//use React.memo to create pure component
function Switch({ label, checked, toggle, id }) {
console.log("rendering:", label);
// prop={new reference} is not a problem here
// this won't re render if props didn't
// change because it's a pure component
// if any of the props change then this needs to re render
return (
<label>
{label}
<input
type="checkbox"
checked={checked}
onChange={() => toggle(id)}
/>
</label>
);
}
);
class App extends React.PureComponent {
state = {
items: [
{ title: "toyota", switch_status: true },
{ title: "porche", switch_status: true },
{ title: "bmw", switch_status: false }
]
};
toggle = _index =>
this.setState({
items: this.state.items.map((item, index) =>
_index === index // only toggle the item at this index
? { ...item, switch_status: !item.switch_status }
: item // do not change the item
)
});
render() {
//please note that using index is not a good idea if you
// change the order of the state.items, add or remove some item(s)
// if you plan to do that then give each item a unique id
return (
<div>
{this.state.items.map((item, index) => (
<Switch
label={item.title}
checked={item.switch_status}
toggle={this.toggle}
id={index}
key={index}
/>
))}
</div>
);
}
}
//render app
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 believe there is an issue while getting the checked state.
In your current implementation, you have written const isChecked = event; in the handleChange method which will always be true since the event object is always available.
It should be const isChecked = event.target.checked; for it to set the toggled checkbox state correctly.

Prevent rerender on function prop update

I have a form with several layers of child components. The state of the form is maintained at the highest level and I pass down functions as props to update the top level. The only problem with this is when the form gets very large (you can dynamically add questions) every single component reloads when one of them updates. Here's a simplified version of my code (or the codesandbox: https://codesandbox.io/s/636xwz3rr):
const App = () => {
return <Form />;
}
const initialForm = {
id: 1,
sections: [
{
ordinal: 1,
name: "Section Number One",
questions: [
{ ordinal: 1, text: "Who?", response: "" },
{ ordinal: 2, text: "What?", response: "" },
{ ordinal: 3, text: "Where?", response: "" }
]
},
{
ordinal: 2,
name: "Numero dos",
questions: [
{ ordinal: 1, text: "Who?", response: "" },
{ ordinal: 2, text: "What?", response: "" },
{ ordinal: 3, text: "Where?", response: "" }
]
}
]
};
const Form = () => {
const [form, setForm] = useState(initialForm);
const updateSection = (idx, value) => {
const { sections } = form;
sections[idx] = value;
setForm({ ...form, sections });
};
return (
<>
{form.sections.map((section, idx) => (
<Section
key={section.ordinal}
section={section}
updateSection={value => updateSection(idx, value)}
/>
))}
</>
);
};
const Section = props => {
const { section, updateSection } = props;
const updateQuestion = (idx, value) => {
const { questions } = section;
questions[idx] = value;
updateSection({ ...section, questions });
};
console.log(`Rendered section "${section.name}"`);
return (
<>
<div style={{ fontSize: 18, fontWeight: "bold", margin: "24px 0" }}>
Section name:
<input
type="text"
value={section.name}
onChange={e => updateSection({ ...section, name: e.target.value })}
/>
</div>
<div style={{ marginLeft: 36 }}>
{section.questions.map((question, idx) => (
<Question
key={question.ordinal}
question={question}
updateQuestion={v => updateQuestion(idx, v)}
/>
))}
</div>
</>
);
};
const Question = props => {
const { question, updateQuestion } = props;
console.log(`Rendered question #${question.ordinal}`);
return (
<>
<div>{question.text}</div>
<input
type="text"
value={question.response}
onChange={e =>
updateQuestion({ ...question, response: e.target.value })
}
/>
</>
);
};
I've tried using useMemo and useCallback, but I can't figure out how to make it work. The problem is passing down the function to update its parent. I can't figure out how to do that without updating it every time the form updates.
I can't find a solution online anywhere. Maybe I'm searching for the wrong thing. Thank you for any help you can offer!
Solution
Using Andrii-Golubenko's answer and this article React Optimizations with React.memo, useCallback, and useReducer I was able to come up with this solution:
https://codesandbox.io/s/myrjqrjm18
Notice how the console log only shows re-rendering of components that have changed.
Use React feature React.memo for functional components to prevent re-render if props not changed, similarly to PureComponent for class components.
When you pass callback like that:
<Section
...
updateSection={value => updateSection(idx, value)}
/>
your component Section will rerender each time when parent component rerender, even if other props are not changed and you use React.memo. Because your callback will re-create each time when parent component renders. You should wrap your callback in useCallback hook.
Using useState is not a good decision if you need to store complex object like initialForm. It is better to use useReducer;
Here you could see working solution: https://codesandbox.io/s/o10p05m2vz
I would suggest using life cycle methods to prevent rerendering, in react hooks example you can use, useEffect. Also centralizing your state in context and using the useContext hook would probably help as well.
laboring through this issue with a complex form, the hack I implemented was to use onBlur={updateFormState} on the component's input elements to trigger lifting form data from the component to the parent form via a function passed as a prop to the component.
To update the component's input elelment, I used onChange={handleInput} using a state within the compononent, which component state was then passed ot the lifting function when the input (or the component as a whole, if there's multiple input field in the component) lost focus.
This is a bit hacky, and probably wrong for some reason, but it works on my machine. ;-)

How to handle state on array of checkboxes?

Is there a way to handle the checked state of an array of checkboxes?
I have this array:
const CheckboxItems = t => [
{
checked: true,
value: 'itemsCancelled',
id: 'checkBoxItemsCancelled',
labelText: t('cancellations.checkBoxItemsCancelled'),
},
{
checked: true,
value: 'requestDate',
id: 'checkboxRequestDate',
labelText: t('cancellations.checkboxRequestDate'),
},
{
checked: true,
value: 'status',
id: 'checkboxStatus',
labelText: t('cancellations.checkboxStatus'),
},
{
checked: true,
value: 'requestedBy',
id: 'checkboxRequestedBy',
labelText: t('cancellations.checkboxRequestedBy'),
},
];
And I am using it here:
class TableToolbarComp extends React.Component {
state = {
isChecked: true,
};
onChange = (value, id, event) => {
this.setState(({ isChecked }) => ({ isChecked: !isChecked }));
};
render() {
const { isChecked } = this.state;
return (
{CheckboxItems(t).map(item => (
<ToolbarOption key={item.id}>
<Checkbox
id={item.id}
labelText={item.labelText}
value={item.value}
checked={isChecked}
onChange={this.onChange}
/>
</ToolbarOption>
))}
)
}
}
The problem I am having is that every time I unchecked one, the rest of them get unchecked too. I need to manage the state separately to send some information to other components through a redux action.
EDIT:
This is the UI library I am using
You're using the container's isChecked as the state for all of your checkboxes, using a method on your container to flip that one flag that it applies to all of them (isChecked).
Instead, either:
Give the checkboxes themselves state, rather than making them simple objects, or
Maintain a state map in the container keyed by the checkbox item (or perhaps its name)
I would lean toward #1, which I think would look like this with that library:
class TableToolbarComp extends React.Component {
state = {
items: CheckboxItems(t) // Your code seems to have a global called `t`
};
onChange = (value, id, event) => {
this.setState(({ items }) => {
// Copy the array
items = items.slice();
// Find the matching item
const item = items.find(i => i.id === id);
if (item) {
// Update its flag and set state
item.checked = !item.checked;
return { items };
}
});
};
render() {
const { items } = this.state;
return (
{items.map(item => (
<ToolbarOption key={item.id}>
<Checkbox
id={item.id}
labelText={item.labelText}
value={item.value}
checked={item.checked}
onChange={this.onChange}
/>
</ToolbarOption>
))}
)
}
}
Changes:
Call CheckboxItems once, keep the result as state.
In onChange, find the relevant checkbox by id (the lib passes the id) and flip its checked flag
In render, get the items from state and for each item, use its checked flag, not your `isChecked (which I've removed entirely

react select not recognizing default value

I have a react select component that isn't recognizing the default value option.
The code looks like this:
renderPlans(){
if(this.props.plans){
let list = this.props.plans.map(item=>{
return ({label:item.description, value:item.id})
});
return(
<Select
name= "tile-plans"
value= {this.state.selected}
classNamePrefix='react-select-container'
options={list}
defaultValue={list[0]}
onChange={(e) => { e ? this.setState({selected: e.value}) : this.setState({selected: ''}) }}
/>
)
}
}
from everything I can find on its docs this is the format to give it. Basically I just want the first option to always be the default choice as there will be times when there is only 1 option and it doesn't make sense for someone to need to select a drop down. I also have a graph that's being loaded underneath so if an option is selected the graph won't load.
This isn't a duplicate question as I know you can do it like this:
value= {this.state.selected ? this.state.selected:list[0].label}
but it's not working. The input remains blank on load.
The documentation states that "If you don't provide these props, you can set the initial value of the state they control", referencing among others the value prop you provide.
You can set the selected to the first element in list when the component is created instead.
Example
class App extends React.Component {
state = {
selected: this.props.list[0]
};
handleChange = selected => {
this.setState({ selected });
};
render() {
const { selected } = this.state;
return (
<Select
value={selected}
onChange={this.handleChange}
options={this.props.list}
/>
);
}
}
ReactDOM.render(
<App
list={[
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
]}
/>,
document.getElementById("root")
);

Use Buttons to trigger filter function on react-table in React

I don't know how to word this. I am learning React and I have data loaded into React-Table via fetch. I tried using React-Table and just custom plain divs and tables.
I want to create a touch buttons of the alphabet from A, B, C, D ... Z. Those buttons should call the filter for the letter that is in the button. So, for example the buttons are the following.
// In Directory.js
class FilterButtons extends React.Component {
alphaFilter(e) {
console.log(e.target.id);
// somehow filter the react table
}
render() {
return (
<div>
<button onClick={this.alphaFilter} id="A" className="letter">A</button>
<button onClick={this.alphaFilter} id="B" className="letter">B</button>
<button onClick={this.alphaFilter} id="C" className="letter">C</button>
</div>
);
}
}
const BottomMenu = props => (
<div className="btm-menu">
<div className="toprow">
<div className="filter-keys">
<FilterButtons />
</div>
</div>
</div>
);
// I have a class Directory extends Component that has the BottomMenu in it
// I also have a DataGrid.js with the React Table in there
class DataGrid extends React.Component {
constructor() {
super();
this.state = {
data: [],
};
}
componentWillMount() {
fetch('http://localhost:3000/rooms.json').then((results) => results.json()).then((data) => {
console.log(data.room);
this.setState({
data: data.room
})
})
}
render() {
const { data } = this.state;
return (
<div>
<ReactTable
data={data}
filterable
defaultFilterMethod={(filter, row) =>
String(row[filter.id]) === filter.value}
columns={[
{
Header: "Name",
accessor: "dName",
filterMethod: (filter, row) =>
row[filter.id].startsWith(filter.value)
},
{
Header: "Department",
accessor: "dDept"
},
{
Header: "Room",
accessor: "dRoom"
},
{
Header: "Map",
accessor: "dRoom",
id: "over",
}
]
}
defaultPageSize={14}
className="-striped -highlight"
/>
<br />
</div>
);
}
}
export default DataGrid;
At this point I am unsure what to do to get the button click of one of the A, B, C letters to filter the React Table. I do not want the Input field option that is always used because I want only buttons as the user will not have a keyboard, only touch.
Basically, React Table or just any table that can be filtered by clicking buttons with a letter that gets passed back to the filter. If I was using JQuery I would use a button click and then filter that way. I still haven't learned all the ins and outs of React and how to get this done. I also want to use external buttons to sort but that should be easier, hopefully.
Thanks.
Sorry if all of this doesn't make sense, I am just trying to lay it out. Again, no keyboard, only touch on a touch screen so the input field isn't going to work for me.
For React-Table filter to be controlled externally by buttons, you should take a look at the Controlled Table example. Then the table component becomes a controlled component.
There, you can set the state of the table filter externally and use both of the props:
<ReactTable ...(your other props)
filtered={this.state.filtered}
onFilteredChange={filtered => this.setState({ filtered })}
/>
The filtered prop will monitor change in the state. So whenever you update its value through your letter buttons via e.g.
this.setState({filtered: { id: "dName", value: "A"}})
the table's filter will get updated. Also, onFilteredChange should work the other direction, namely the embedded filtering of the react-table can update the local state. So that you can monitor it and use its value within your DataGrid component.
One other option could be to avoid using local states and implement it in redux, though. Because states hanging around components are eventually becoming source of errors and increasing complexity of debugging.
UPDATE -- As per the question owner's comment for more details:
In order to use FilterButtons and DataGrid together, you can define a container component that encapsulates both FilterButtons and DataGrid.
The container component keeps the shared state, and the filtering function operates on the state function. But data will still reside within the datagrid.
class DataGridWithFilter extends React.Component {
// you need constructor() for binding alphaFilter to this.
// otherwise it cannot call this.setState
constructor(props) {
super(props);
this.alphaFilter = this.alphaFilter.bind(this);
}
// personally i do not use ids for passing data.
// therefore introduced the l parameter for the letter
alphaFilter(e,l) {
console.log(e.target.id);
this.setState({filtered: { id: "dName", value: l}});
}
render(){
return <div>
<DataGrid filtered={this.state.filtered}
filterFunc={this.alphaFilter}
</DataGrid>
<BottomMenu filtered={this.state.filtered}
filterFunc={this.alphaFilter} />
</div>
}
}
Also this above thing requires the use of prop filterFunc from within BottomMenu and FilterButtons components.
class FilterButtons extends React.Component {
render() {
const {props} = this;
return (
<div>
<button onClick={(e) => props.filterFunc(e, "A")} id="A" className="letter">A</button>
<button onClick={(e) => props.filterFunc(e, "B")} id="B" className="letter">B</button>
<button onClick={(e) => props.filterFunc(e, "C")} id="C" className="letter">C</button>
</div>
);
}
}
const BottomMenu = props => (
<div className="btm-menu">
<div className="toprow">
<div className="filter-keys">
<FilterButtons filterFunc = {props.filterFunc} />
</div>
</div>
</div>
);
class DataGrid extends React.Component {
constructor() {
super();
this.state = {
data: [],
};
}
componentWillMount() {
fetch('http://localhost:3000/rooms.json').then((results) => results.json()).then((data) => {
console.log(data.room);
this.setState({
data: data.room
})
})
}
render() {
const { data } = this.state;
return (
<div>
<ReactTable
data={data}
filterable
defaultFilterMethod={(filter, row) =>
String(row[filter.id]) === filter.value}
columns={[
{
Header: "Name",
accessor: "dName",
filterMethod: (filter, row) =>
row[filter.id].startsWith(filter.value)
},
{
Header: "Department",
accessor: "dDept"
},
{
Header: "Room",
accessor: "dRoom"
},
{
Header: "Map",
accessor: "dRoom",
id: "over",
}
]
}
defaultPageSize={14}
className="-striped -highlight"
filtered = {this.props.filtered}
onFilteredChange = {filtered => this.props.filterFunc({filtered})}
/>
<br />
</div>
);
}
}
I have not checked against typo etc but this should work.
In React, you can update components when state changes. The only way to trigger is to use this.setState()
So I would change my state object something like this:
state = {
date: [],
filter: ""
};
so here is the new file:
// In Directory.js
class FilterButtons extends React.Component {
alphaFilter = (word) => {
this.setState({
filter: word
});
};
render() {
return (
<div>
<button onClick={() => this.alphaFilter("A")} id="A" className="letter">A</button>
<button onClick={() => this.alphaFilter("B")} id="B" className="letter">B</button>
<button onClick={() => this.alphaFilter("C")} id="C" className="letter">C</button>
</div>
);
}
}
const BottomMenu = props => (
<div className="btm-menu">
<div className="toprow">
<div className="filter-keys">
<FilterButtons />
</div>
</div>
</div>
);
right after you call alphaFilter() your component will update. And when you put your filter function in it it's going display as expected. So I would choose the .map() function of array. You can either use filter() or return after comparing data in map()
For react-table 7 you can listen for input changes outside and call setFilter:
useEffect(() => {
// This will now use our custom filter for age
setFilter("age", ageOutside);
}, [ageOutside]);
See https://codesandbox.io/s/react-table-filter-outside-table-bor4f?file=/src/App.js

Categories