Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactJS: dynamic checkboxes from State

Tags:

reactjs

I have a collection of elements which is dynamic, so I do not know up front what the content of the collection is. I want to dynamically render these checkboxes, and have them by default all checked. You should be able to uncheck the boxes, which triggers another map function in render. (so basically it are my filters)

I am struggling on the state setup. I was thinking of the following, where I want to set the initialState during ComponentWillMount by an API call:

scenario 1 (arrays): 

this.state {
    checkboxes: [value1, value2, value3],
    checkedcheckboxes: [value1, value2]
}

Then I can use the react.addons.update function to change checkcheckboxes state. The downside is that during render of my checkboxes, I have to loop through the checkcheckboxes array with indexOf on every checkbox to see if it should be checked.

scenario 2 (object):

this.state {
    checkboxes: {
       value1: true,
       value2: true,
       value3: false
}

This way I have dynamic keys, which makes it harder to work (I guess), since I have to use computed fields in my setState. I was also under the impression that this could have negative impact on my performance.

Is there any best practice on this pattern?

Thank you!

like image 683
user3611459 Avatar asked Jun 28 '16 16:06

user3611459


1 Answers

I'd probably set your state to contain an array of objects (representing your checkboxes), and a string that represents your filter. Each checkbox object would contain two properties, the text for the label, and the value of the checkbox.

For your initial state, you'd want this array to be empty, then during the componentDidMount lifecycle, you'd perform your API call and set your state to include the information you received.

constructor(props) {
    super(props);

    this.state = {
        checkboxes: [],
        filter: 'ALL'
    };

    //each checkbox would take the form of {label: 'some label', checked: true}
}

componentDidMount() {
    getData(response => { //some API call
        this.setState({
            checkboxes: response.body
        });
    });
}

In order for your component to render these checkboxes, it needs to understand how your checkbox objects in your state relate to actual DOM elements. You might create a helper function that leverages .map to do this. You can also leverage .filter to apply your filter to each object in your state, and get rid of the ones that don't match your filter.

function renderCheckboxes() {
    const {checkboxes, filter} = this.state;

    return checkboxes
        .filter(checkbox =>
            filter === 'ALL' ||
            filter === 'CHECKED' && checkbox.checked ||
            filter === 'UNCHECKED' && !checkbox.checked
        )
        .map((checkbox, index) =>
            <div>
                <label>
                    <input
                        type="checkbox"
                        checked={checkbox.checked}
                        onChange={toggleCheckbox.bind(this, index)}
                    />
                    {checkbox.label}
                </label>
            </div>
        );
}

Notice how each checkbox has an onChange attribute mapped to some function? You'll need to provide this to every checkbox that you render with the checked attribute, since you'll be creating a Controlled Component, and your state will be controlling whether or not each individual component is checked. Your toggleCheckbox function may look something like this:

function toggleCheckbox(index) {
    const {checkboxes} = this.state;

    checkboxes[index].checked = !checkboxes[index].checked;

    this.setState({
        checkboxes
    });
}

It also might be useful to create a function that sets your filter to a different string:

function updateFilter(filter) {
    this.setState({
        filter
    });
}

Finally, you can use these functions in your render function to display your checkboxes as well as links to update your filter status (I also added some non-breaking spaces to separate each filter link):

render() {
    return (
        <div>
            {renderCheckboxes.call(this)}
            <div>
                <h4>Filters ({this.state.filter})</h4>
                <a href="#" onClick={updateFilter.bind(this, 'ALL')}>ALL</a>
                &nbsp;|&nbsp;
                <a href="#" onClick={updateFilter.bind(this, 'CHECKED')}>CHECKED</a>
                &nbsp;|&nbsp;
                <a href="#" onClick={updateFilter.bind(this, 'UNCHECKED')}>UNCHECKED</a>
            </div>
        </div>
    );
}

Here's an example of this working - minus the API call:

https://jsbin.com/bageyudaqe/edit?js,output

Hope this helps!

like image 197
Michael Parker Avatar answered Nov 12 '22 09:11

Michael Parker