Search code examples
javascriptreactjsreduxstatestore

Set `store` as reducer's `state` parameter


I'm new to Redux (and React as well) so this is probably a very elementar question, but I was unable to find a straight answer for it over the Internet.

There is a sign up screen on my app that I call SignUp. It is pretty straightforward and have just three inputs (email, password and passwordCheck) and one button (signup). Thing is, I want to make my user life as simple as possible, so as passwordCheck is changed, I check it against password. If them match, signup is setted enabled and if them don't, signup goes disabled and a message is show to the user. When I was using only React things were pretty simple - I simply made a this.state:

this.state: {
    // ... stuff
    password: "",
    passwordIsValid: false, //test password against a predicate function
    passwordMessage: "", //if the password is not valid there is a message
    passwordStyle: "", //if password is wrong i put some classes here
    passwordCheck: "",
    passwordCheckMessage: "", //the 'passwords do not match' message goes here
    passwordCheckStyle: "",
    passwordsMatch: false
}

And then I handled the app's state inside SignUp. Now I'm using Redux as well, so I killed this.state, moved everything to store and started using reducers instead of handlers. Everything works for password, but passwordCheck is a different story. Since I need to know Store's password to check it against passwordCheck I have been unable (so far?) to do it on passwordCheckReducer. Instead, I removed passwordCheckMessage, passwordCheckStyle and passwordsMatch from passwordCheckReducer, calculating this values on SignUp. It seems to me as a very wrong and ugly way to settle the whole thing down though.

So, instead, I would like a more elegant and Redux-like solution.

If I could get store to be on passwordCheckReducer's state I would be able to set passwordsMatch and the others in the reducer while keeping it pure. Unfortunately, I have been unable to do it (so far?). So, I would like to know if what I want to do is possible or if there's others, more desirables, ways to do it.

OBS1: Wherever I look on the Internet, I found the Redux official documentation on the subject of initializing state1. I do not think preloadedState is the way to resolve my problem, though, since it is used to iniatilize store, not to make it avaiable on my reducers. Instead, I simply need to have store - even when empty - visible to passwordCheckReducer. OBS2: I know I could pass store as a key of action, but since the reducer pattern includes a state argument it seems redundant to me to define pass such in action.

Here is my Store:

// store.js
import { applyMiddleware, createStore } from 'redux';
import { createLogger } from 'redux-logger';
import thunkMiddleare from 'redux-thunk';
import { Reducers } from '../reducers/reducers';

const logger = createLogger();
const middleware = applyMiddleware(thunkMiddleare, logger);

export const Store = createStore(Reducers, middleware);

I'm using combineReducers:

// reducers.js
import { combineReducers } from 'redux';
import { emailReducer } from './emailReducer';
import { passwordReducer } from './passwordReducer';
import { passwordCheckReducer } from './passwordCheckReducer';

export const Reducers = combineReducers({
    emailChange: emailReducer,
    passwordChange: passwordReducer,
    passwordCheckChange: passwordCheckReducer
}); 

And here is passwordCheckReducer:

// passwordCheckReducer.js
import { CHANGE_PASSWORDCHECK } from '../actions/actionTypes';

const initialState = {
    passwordCheck: ""
};

export const passwordCheckReducer = (state=initialState, action) => {
    const { payload, type } = action;

    switch(type) {
        case CHANGE_PASSWORDCHECK:
            const passwordCheck = payload;
            return {
                ...state,
                passwordCheck
            };
        default: 
            return state;
    }
};

Last, this is the implementation of mapDispatchToProps:

// SignUp.js
const mapDispatchToProps = dispatch => ({
    handleEmailChange: e => dispatch(updateEmail(e.target.value)),
    handlePasswordChange: e => dispatch(updatePassword(e.target.value)),
    handlePasswordCheckChange: e => dispatch(updatePasswordCheck(e.target.value))
});

Solution

  • If I remember correctly, when you pass your reducer to combineReducers, that reducer will receive the redux module state (so, what the initialState was, not the entire app's root state object). You don't have access to state from other reducers.

    You have a few options.

    I think having separate reducers for password and passwordCheck is somewhat overkill. I would go with one reducer for the entire form:

    const initialState = {
        password: '',
        passwordCheck: '',
        // ....
    }
    
    export const signupReducer = (state=initialState, action) => {
        const { payload, type } = action;
    
        switch(type) {
            case CHANGE_PASSWORD:
                return { ...state, password: action.password}
            case CHANGE_PASSWORDCHECK:
                const passwordCheck = payload;
                if (state.password != state.passwordCheck) 
                    return { ...state, passwordCheck, error: "..."}; // or something like this
    
                return {
                    ...state,
                    passwordCheck
                };
            default: 
                return state;
        }
    };
    
    

    If you want to split things up, you can always define reducers, and call them from a parent reducer, passing the whole parent-reducer state. Something like:

    import passwordReducer from './passwordReducer'
    import passwordCheckReducer form './passwordCheckReducer'
    
    export const signupReducer(state = initialState, action) => {
        state = passwordReducer(state, action)
        state = passwordCheckReducer(state, action)
        return state
    }