red house 87
red house 87

Reputation: 2415

Redux - reducers / action creators not linked properly to component

I'm learning how to use Redux and am having an issue setting up the actions and reducers. Here is my setup:

Actions are here:

export const loginUser = (test) => {
        return {
            type:'loginUser',
            loggedIn:true
        }
}

export const toggleRegLog = (test) => {
        return {
            type:'toggleRegLog',
        }
}

Reducers are here:

let initialState = [];

const userAuthReducer = (state = initialState,action) => {
  switch (action.type) {
    case 'loginUser':  
      let newState = [...state];
            if(action.loggedIn) {
        newState = "logged-in";
            } else {
                newState = "not-logged-in";
            }
      return newState;
            break;
        case:'toggleRegLog':
            let newState = [...state];
            return action.state;
            break;
    default:
      return state;
  } 
}

export default userAuthReducer;

Combination of reducers is here in a file called index:

import {combineReducers} from 'redux';
import userAuthReducer from './reducers/userAuthReducer';

function lastAction(state = null, action) {
  return action;
}

export default combineReducers({
  userAuthReducer
});

Demo component:

import React, {Component} from 'react';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
import * as authActions from './actions/userAuthActions';

class App extends Component {
  constructor(props) {
    super(props);
  }


componentDidMount() {

    console.log(this.props)
}    

  render() {
    return (
      <div>
                <button onClick={this.props.loginUser()}></button>
      </div>
    );
  }

const mapStateToProps = (state) => {
  return {
        userAuthReducer:state.userAuthReducer
  };
};

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators(authActions,dispatch);
};

export default connect(mapStateToProps,mapDispatchToProps)(App);    
}

I had the state working before in a more basic way but after looking at some more tutorials and deciding to introduce separate action creators (ie not just dispatching from my component straight to the reducer) and by introducing multiple cases in my reducer it doesn't seem to work anymore. The console.log(this.props) on my component returns the actions as functions and states as undefined.

Can anyone spot where i've gone wrong?

Upvotes: 4

Views: 441

Answers (2)

Jose Paredes
Jose Paredes

Reputation: 4090

You have so many errors in this project setup.

You should see them if you are running the code you posted


Let's walk through your code

Constants (missed)

Constants are important to add consistency to your actions and reducers.

export const LOGIN_USER = 'loginUser';

export const TOGGLE_REG_LOG = 'toggleRegLog';

Actions

There is nothing wrong with your actions, just a couple of recommendations

  • Follow the Flux standard action
  • Don't declare parameters if you won't use it
  • Actions don't return new states, instead, they describe a behavior to alter the redux store

.

import { LOGIN_USER, TOGGLE_REG_LOG } from '../constants/userAuthConstants';

export const loginUser = (loggedIn = true) => {
  return {
    type: LOGIN_USER,
    payload: {
      loggedIn
    }
  }
}

export const toggleRegLog = () => {
  return {
    type: TOGGLE_REG_LOG,
  }
}

Reducers

You have an error in case: toggleRegLog: declaration. Recommendations:

  • Wrap your switch cases in block (curly braces) if you are declaring variables, so you are not limited to use different names
  • Use Object.assign instead of spread operator
  • Use action's data to alter the newState
  • Use constants
  • Use objects instead of array as the initial state

.

import { LOGIN_USER, TOGGLE_REG_LOG } from '../constants/userAuthConstants';

const initialState ={
  userLoginStatus: 'not-logged-in'
};

const userAuthReducer = (state = initialState, action) => {
  switch (action.type) {
    case LOGIN_USER: {
      const userLoginStatus = action.payload.loggedIn ? 'logged-in' : 'not-logged-in';

      const newState = Object.assign(state, {
        userLoginStatus
      });

      return newState;
    }
    case TOGGLE_REG_LOG: {
      let newState = Object.assign({}, state, {}); // do what you need here

      return newState;
    }
    default:
      return state;
  }
}

export default userAuthReducer;

Component

When you attach the onClick event in the button you are executing the loginUser function when it's expecting a function callback, instead, pass the loginUser without executing it so when user clicks the button the action will be dispatched

Recommendations:

  • Avoid using constructor if you will not do anything within it
  • Select the specific property you want from your redux store

.

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { loginUser } from './actions/userAuthActions';

class App extends Component {
  componentDidMount() {
    console.log(this.props);
  }

  render() {
    const { userLoginStatus, loginUser } = this.props;

    return (
      <div>
        <p> User status: {userLoginStatus}</p>
        <button onClick={loginUser}>click me</button>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    userLoginStatus: state.userAuthReducer.userLoginStatus
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    loginUser: () => dispatch(loginUser())
  };
};

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

Before you start testing any library/framework make sure, you are following the best practices, also that your code does not contain any error otherwise you can start having nightmares with your code.

Upvotes: 4

palsrealm
palsrealm

Reputation: 5243

Actions: Here I have added the newState payload to the action as it is being used in the reducer.

export const loginUser = () => {
    return {
        type:'loginUser',
        loggedIn:true


    }
}
export const toggleRegLog = () => {
        return {
            type:'toggleRegLog',
            newState: xyz, //whatever you want it to be
        }
}

Reducers: Here I have changed your initial state to a json object which has a field for loginStatus. You can use the same object to track other things by adding other fields. I have set the initial state of the field to "not-logged-in". In the reducer, the initail state is spread in a new object using the spread operator and the fields that are to be updated are overwritten.

let initialState = {
loginStatus:"not-logged-in",
 //other stuff
};


const userAuthReducer = (state = initialState,action) => {
  switch (action.type) {
    case 'loginUser':  
      let newState = {};
            if(action.loggedIn) {
        newState = {...state,  loginStatus:"logged-in"};
            } else {
                newState = {...state,  loginStatus:"not-logged-in"};
            }
      return newState;
            break;
        case:'toggleRegLog':

            return {...state,  loginStatus:action.newState};
            break;
    default:
      return state;
  } 
}

export default userAuthReducer;

The App component:

import React, {Component} from 'react';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
import * as authActions from './actions';

class App extends Component {
  constructor(props) {
    super(props);
  }


componentDidMount() {

    console.log(this.props)
}    

  render() {
    return (
      <div>
                <button onClick={this.props.loginUser}></button>
      </div>
    );
  }
}
const mapStateToProps = (state) => {
  return {
        userAuthReducer:state.userAuthReducer
  };
};

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators(authActions,dispatch);
};




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

Upvotes: 0

Related Questions