3

It's more of an architectural question than a technical one. I am working on application which has the following structure (Standard Redux).

UserFormContainer->UserForm->(Fields, RoleField, Group Field). Now all of my fields are dumb components with the exception of Role and Group, they are dependent on data from server to be populated. According to standard redux my state has a one way flow and all the data goes through the main container, dumb components only dispatch Actions.

My Question is, since the role and group fields are dependent on it's parent component for the data how do I make these reusable? What is the best practice ?

Here is one way flow of my basic component.

enter image description here

2 Answers 2

1

RoleField should be a dumb component, which gets the data from UserForm component. UserForm should get the data from the store and pass it on as props to the RoleField. This way RoleField will be reusable, because they can display the data that is passed on to them from the parent component.

UDPATE

You can also make UserForm a wrapper container which could accept custom form fields as children. See example code below

render() {
  <UserForm>
    <RoleField customData={ dataFromStore }/>
  </UserForm>
}
Sign up to request clarification or add additional context in comments.

5 Comments

But here UserForm receives data from UserFormContainer, and if I use the RoleField somewhere else I have to pass the data data from another parent component, would it not make it more verbose?
@AftabNaveed It is more verbose, but if you want UserForm to be a dumb component, then this is the way to go. Passing props down 2 levels is not that uncommon.
@AftabNaveed I have added an example where you would pass props from UserFormContainer directly to RoleField.
I understand, and if it is 2 levels then I agree with you, but what if it's more than that? If you look at the redux-form example it doesn't matter how nested your form is. The reduxForm decorates the component to pass in all it's data, which makes me think that it might be ok to have a container inside another container. RoleField component can itself be a container passed to another unless I am violating Redux here?
@AftabNaveed you can have as many containers nested as you like. There's no best way to do this and it depends on your specific situation which is uncertain from the information provided. However, keeping it simple saves time and makes it easier to understand your app.
1

@Deividas Karžinauskas answer makes perfect sense when you have a couple of nested components and I think that is the reasonable way to go in such scenario, but since I wanted my component to be totally independent off any other component, yet testable and not containing any data logic I just came up with slightly different approach. Of course Deividas Karžinauskas answer helped.

To make my component totally independent I just made it yet another container, all the side effects are handled by action/action creators and it still is testable while data is calculated by it's own independent reducer. I then used redux to pass the state as props.

Code Sample

import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';

class RolesField extends React.Component {

    componentWillMount() {
        //if not fetched then fetch
        this.props.fetchRoles();
    }

    roles() {
        let {roles} = this.props;
        if (roles.length > 0) {
            const {input, label, placeholder, type, meta: {touched, error, warning}} = this.props;
            let rolesUi = roles.map((role, index) => {
                return (
                    <div className="checkbox" key={index}>
                        <label htmlFor={input.name}>
                            <input {...input} placeholder={placeholder} type={type}/>
                            &nbsp;{role.name} <a href="#">( Show Permissions )</a>
                        </label>
                    </div>
                );
            });

            return rolesUi;
        }
    }


    render() {

        const {input, label, placeholder, type, meta: {touched, error, warning}} = this.props;

        return (
            <div className="form-group">
                <label className="col-sm-2 control-label" htmlFor={input.name}>
                    Administrative Roles
                </label>
                <div className="col-sm-10">
                    {/*This section should be loaded from server,
                     the component should dispatch the event and role action should load the roles.*/}
                    {this.roles()}
                </div>
            </div>
        );
    }

}

//map state to props.
function mapStateToProps(state) {
    return {
        roles: state.roles.roles
    }
}

function mapDispatchToProps(dispatch) {
    let actionCreators = {
        fetchRoles
    };

    return bindActionCreators(actionCreators, dispatch);

}

export default connect(mapStateToProps, mapDispatchToProps)(RolesField);

/*Action Types*/
const ROLE_FETCH_START = 'ROLE_FETCH_START';
const ROLE_FETCH_COMPLETE = 'ROLE_FETCH_COMPLETE';
/*End Action Types*/

/**
 * Action Creator
 * @returns {{type: string, payload: [*,*,*,*]}}
 */
function fetchRoles() {
    const roles = [
        {'id': 1, 'name': 'Super Admin'},
        {'id': 2, 'name': 'Admin'},
        {'id': 3, 'name': 'Manager'},
        {'id': 4, 'name': 'Technical Lead'}
    ];
    /*Action*/
    return {
        type: ROLE_FETCH_COMPLETE,
        payload: roles
    };
}
/**
 * Roles Reducer
 * @param state
 * @param action
 * @returns {*}
 */
export function rolesReducer(state = {roles: [], fetched: false}, action) {
    switch (action.type) {
        case ROLE_FETCH_COMPLETE :
            return {
                ...state,
                fetched: true,
                roles: action.payload
            };
            break;
    }

    return state;
}

And to re-use the component in redux-form I did

import React from 'react';
import {Field, reduxForm} from 'redux-form';

import {renderInputField, renderDropdownList} from '../../page/components/field.jsx';
import RolesField from '../../page/containers/fields/roles.jsx';

import Box from '../../page/components/box.jsx';
import ActionButtons from '../../page/components/action-buttons';

class UserForm extends React.Component {

    actionButtons() {
        return [
            {
                'type' : 'submit',
                'label' : 'Save',
                'className' : 'btn-primary'
            },
            {
                'type' : 'button',
                'label' : 'Cancel',
                'className' : 'btn-success',
                'onClick' : this.props.reset
            },
            {
                'type' : 'button',
                'label' : 'Delete',
                'className' : 'btn-danger'
            }
        ]
    }

    render() {
        const { handleSubmit } = this.props;
        return (
            <form className="form-horizontal" role="form" onSubmit={handleSubmit}>
                <Box title="Add New User">
                    <ActionButtons buttons = {this.actionButtons()} />
                    <Field
                        name="roles[]"
                        component={RolesField}
                        label="Technical Lead"
                        type="checkbox"
                        className="form-control" />

                </Box>

            </form>
        );
    }

}

UserForm = reduxForm({
    form: 'userForm',
    validate
})(UserForm);

export default UserForm;

Here is the revised diagram. I am not sure if this is a best practice or there are any drawbacks associated with it, but it just worked in my use case.

State chart:

component state chart

Apologies for the diagram not being so clear.

Reusable Redux React Component

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.