DEV Community

Cover image for Writing A Nested Checkbox Component with React / React-Admin
Ahmed Zeno
Ahmed Zeno

Posted on

Writing A Nested Checkbox Component with React / React-Admin

so i was searching for a quick plugin to show nested checkbox for my react project and unfortunately most of what iv'e found wasn't as i was expecting, at-least for my needs.

Alt Text

i wanted to create a checkbox with network name and its has instances as children checkboxes , if u click on the network it will work as a toggle button to select all instances within this network, while you still have the option to select instances individually and if all instances were selected change the network to checked u know the usual toggle behaviour.

Alt Text

so i decide to write a quick one and thought it might be handy for some of you guy.

ill show 2 nested checkbox here one using react with redux and the 2nd will use a react-admin component

first ill create some dummy data:-
this is an array of objects that have network and instances

const networks = [{
        name: "Network_A",
        id: 1,
        instances: [{
                id: 0,
                instanceName: "Instance_1"
            },
            {
                id: 1,
                instanceName: "Instance_2"
            }
        ]
    },
    {
        name: "Network_B",
        id: 33,
        instances: [{
                id: 0,
                instanceName: "Instance_1",
            },
            {
                id: 1,
                instanceName: "Instance_2",
            },

            {
                id: 2,
                instanceName: "Instance_3",
            }
        ]
    }
]
Enter fullscreen mode Exit fullscreen mode

ok cool now what ?

lets write our class and call it CheckboxesGroup
the class will use some ready controllers from material-ui

like this

import React from 'react'
import FormLabel from '@material-ui/core/FormLabel'
import FormControl from '@material-ui/core/FormControl'
import FormGroup from '@material-ui/core/FormGroup'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import FormHelperText from '@material-ui/core/FormHelperText'
import Checkbox from '@material-ui/core/Checkbox'
import {PropTypes} from 'prop-types'
import { Field } from 'redux-form'

class CheckboxesGroup extends React.Component {
    static propTypes = {
        name: PropTypes.string.isRequired,
        instances: PropTypes.array.isRequired
    }

    constructor(props) {
        super(props)

        this.classes = {
            root: {
                display: 'flex'
            },
            formControl: {
                margin: '3px',
                float: 'left'
            }
        }
        const networkName = props.name

        const instances = props.instances.map(item => {
            return {name: item.instanceName, value: false}
        })

        this.onChange=props.onChange

        this.state = {
            networkName,
            checkedAll: false,
            instances
        }
        this.handleChange.bind(this)
    }

    render() {
        const {checkedAll} = this.state
        const checkboxes = this.state.instances.map(i => {
            const instanceName=i.name            
            return (
                <FormControlLabel
                    style={{width: '200px'}}
                    control={
                        <Field name={`${instanceName}`} type="checkbox" component={renderInnerCheckboxField} label="instances" checked={checkedAll || i.value} onChange={this.handleChange(i.name)} value={i.value.toString()}/>

                    }
                    label={i.name}
                    key={i.name + i.value}
                > </FormControlLabel>
            )
        })
        const networkName=this.state.networkName
        return (
            <div className={this.classes.root.toString()}>
                <br />
                <FormControl
                    component="fieldset"
                    className={this.classes.formControl.toString()}
                >
                    <FormLabel
                        component="legend"
                        style={{fontWeight: 'bold', fontSize: '20px'}}
                    >
                        {this.state.networkName}
                        <FormControlLabel
                            label="Select All"
                            control={
                                <div>
                                    &nbsp;&nbsp;&nbsp;
                                    <Field name={`network ${networkName}`} type="checkbox" checkboxes={checkboxes} component={renderCheckboxField} label="Sellect all in" checked={checkedAll} onChange={event => {
                                        this.setState({
                                            checkedAll: event.target.checked
                                        })
                                    }}/>
                                </div>
                            }
                        />
                    </FormLabel>
                    <FormGroup style={{display: 'flow-root'}}>
                        {checkboxes}
                    </FormGroup>
                    <FormHelperText>
                        --------------------------------------------------------------------------------
                    </FormHelperText>
                </FormControl>
            </div>
        )
    }

    handleChange(name) {
        const _this = this
        return function(event) {            
            const instances = _this.state.instances.map(i => {
                if (i.name === name) {
                    console.log(event.target.checked)
                    return {name, value: event.target.checked}
                }

                return i
            })

            _this.setState({
                ..._this.state,
                instances
            })
            setTimeout(
                () => {                    
                    _this.onChange(_this.state)
                },
                500
            )
        }
    }
}


const renderCheckboxField = (props) => {
    const { input, label, meta} = props
    console.log("...custom   ",props)
    return (
        <Checkbox
            label={label}
            {...input}

        />
    )}
const renderInnerCheckboxField = ({ input, label, meta: { touched, error }, ...custom }) => {

    return (
        <Checkbox
            label={label}
            error={!!(touched && error)}
            helperText={touched && error}
            {...input}
            {...custom}
        />
    )}
export default CheckboxesGroup
Enter fullscreen mode Exit fullscreen mode

now you can call it inside you form or any render component
in my case i put it inside a FormTab like this

<FormTab label="Networks & Instances">   
   {networks.map(network =>
       (<CheckboxesGroup {...network} source="networks" key={network.name} />)
   )}

</FormTab>

Enter fullscreen mode Exit fullscreen mode

but after this one i realised that i was complexing things up so , idid a quicker one with simple components from react admin

Alt Text

and here is the one for react-admin

import React from 'react'
import {CheckboxGroupInput} from 'react-admin'i
import {FormSpy , useForm} from 'react-final-form'
import {BooleanInput} from 'react-admin'

const Instance = ({record}) => {
    return (
        <div key={record.instanceName} className="instances">
            {record.instanceName + ' - ' + record.name}
        </div>
    )
}


const SelectAllBtn = props => {
    const {network} = props
    const form = useForm()
    return (
        <BooleanInput
            label={network.name}
            key={network.id}
            source={`network.n_${network.id}`}
            onChange={e => {
                let instances = []
                if (e) {
                    instances = network.instances.map(i => i.id)
                }

                form.change('networks.n_' + network.id, instances)
            }}
        />
    )
}

export const NetworkInstances = ({gameNetworks}) => {
    if (gameNetworks) {
        return gameNetworks.map(network => {
            if (network.instances.length > 1) {
                return (
                    <div key={network.name}>
                        <FormSpy>
                            {props => {
                                return (
                                    <SelectAllBtn
                                        network={network}
                                        form={props}
                                    />
                                )
                            }}
                        </FormSpy>

                        <CheckboxGroupInput
                            source={`networks.n_${network.id}`}
                            choices={network.instances}
                            optionText={<Instance />}
                            key={network.id}
                            id={`n_${network.id}`}
                            label={network.name}
                        />
                    </div>
                )
            }

            return (
                <CheckboxGroupInput
                    source={`networks.n_${network.id}`}
                    choices={network.instances}
                    optionText={<Instance />}
                    key={network.id}
                    id={`n_${network.id}`}
                    label={network.name}
                />
            )
        })
    }

    return <div />
}
Enter fullscreen mode Exit fullscreen mode

and just like the previous one you can use it directly in the render
function with passing the source or the api call

<FormTab label="Networks & Instances" path="networks">
                        <NetworkInstances
                            gameNetworks={networks}                            
                        />
</FormTab>
Enter fullscreen mode Exit fullscreen mode

if you have any questions feel free to ask me

Alt Text

Top comments (1)

Collapse
 
priya0607jain profile image
Priya Jain

Can you share a working snippet of the same ?