Search code examples
reactjsreduxcode-reuse

Redux: using different reducers for a component


I've got a component UsersList which I'd like to reuse with two different reducers - one for listing regular users (state.users.x) and one for listing administrators (state.adminusers.x). The display is the same in both cases, but the state is in different places and different api actions apply (different endpoints with different business rules).

How can I write my component so it can use either reducer?


Solution

    1. Write the UsersList component as normal, but do not connect it to redux.

    For example:

    import React, { Component } from 'react';
    import { Table } from 'react-bootstrap';
    import UserInviteRow from 'jsx/components/Lib/Users/UserInviteRow';
    
    export class UsersList extends Component {
    
        render() {
            const { inviteUserToOrg } = this.props;
    
            return (
                <Table bordered hover>
                    <thead>
                        <tr>
                            <th className="width-200">First Name</th>
                            <th className="width-250">Last Name</th>
                            <th>Email</th>
                            <th className="width-150">Last Login</th>
                            <th className="width-100">&nbsp;</th>
                        </tr>
                    </thead>
                    <tbody>
                        <UserInviteRow invitefxn={ inviteUserToOrg }/>
                        { this.renderRows() }
                    </tbody>
                </Table>
            );
        }
    
        renderRows() {
            const { usersList } = this.props;
            if( ! usersList.length ) {
                return (
                    <tr>
                        <td colSpan="5">
                            <em>No users exist for this non-profit</em>
                        </td>
                    </tr>
                );
            }
    
            return usersList.map( (user) => {
                return (
                    <tr key={user.key}>
                        <td>{user.firstName}</td>
                        <td>{user.lastName}</td>
                        <td>{user.correspondenceEmailAddress}</td>
                        <td>{ (user.lastSeen) ? formatTime(user.lastSeen) : '' }</td>
                        <td className="text-center">
                            { this.renderRemoveButton( user ) }
                        </td>
                    </tr>
                );
            });
        }
    
        renderRemoveButton(user) {
            const { currentUser } = this.props;
            if( currentUser.key === user.key ) {
                // users cannot remove themselves
                return null;
            }
    
            return (
                <a className="text-danger" onClick={ () => { this.removeUser(user) } }>
                    <em className="fa fa-times" />
                </a>
            );
        }
    
        removeUser( user ) {
            this.props.removeUserFromOrg(user.key);
        }
    }
    
    export default UsersList;
    
    1. Make sure both your reducers implement the action functions you use, in this case inviteUserToOrg and removeUserFromOrg.

    2. Create new container components connected to each reducer

    For example:

    import { connect } from 'react-redux';
    import { 
        inviteUserToOrg, 
        removeUserFromOrg 
    } as actions from 'jsx/redux/modules/nonadminUsers'; 
    import UsersList from 'jsx/components/Lib/Users/UsersList';
    
    var NonadminUserList = connect(
      state => {
        return {
          usersList: state.users.usersList,
        };
      },
      actions
    )(UsersList);
    
    export default NonadminUserList;
    

    and

    import { connect } from 'react-redux';
    import { 
        inviteUserToOrg, 
        removeUserFromOrg 
    } as actions from 'jsx/redux/modules/adminUsers';
    import UsersList from 'jsx/components/Lib/Users/UsersList';
    
    var AdminUserList = connect(
        state => {
            return {
                usersList: state.adminusers.usersList,
            };
        },
        actions
    )(UsersList);
    
    export default AdminUserList;
    

    Now changes to your presentation component, UsersList, will affect both container components and each container component can reference it's own reducer state and actions.