React Redux: Getting Props And Updating State

2019-05-18 03:50发布

I am trying out React/Redux/JS for the first time. I am a little confused about setting state in component vs letting redux update it.

I want to click a button to set the lightOn to true and have the updated this.props.lightOn value display. I am missing something fundamental but not sure what.

action:

export const setLight = lightStatus => dispatch => {
  const res = lightStatus;
  console.log(res); // => true on click

  dispatch({ type: SET_LIGHT, payload: res });
};

In the component, {this.props.onLight} is undefined so I am not updating state correctly:

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

class Light extends Component {
  render() {

    return (
      <div>
        Light Status: {this.props.lightOn}
        <button
          className="btn"
          onClick={() => this.props.setLight(true)}
        >
          Set Light!
        </button>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    lightOn: state.lightOn
  };
}

function mapDispatchToProps(dispatch) {
  //whenever selectBook is called the result should be passed to all of our reducers
  return bindActionCreators({ setLight: setLight }, dispatch);
}

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

the reducer (updated with spread operator):

import { SET_LIGHT } from '../actions/types';

export default function(state = [], action) {
  switch (action.type) {
    case SET_LIGHT:
     return { ...state, lightOn: action.payload };
    default:
      return state;
  }
}

5条回答
ら.Afraid
2楼-- · 2019-05-18 04:09

the wrong on the reducer, the reducer should return the state object with lightOn key, but you return payload value, and this is the issue. your reducer should return lightOn with payload value like this.

import { SET_LIGHT } from '../actions/types';

export default function(state = {}, action) {
  switch (action.type) {
    case SET_LIGHT:
      return {...state, action.payload}
    default:
      return state;
  }
}
查看更多
Anthone
3楼-- · 2019-05-18 04:11

How about set it as a state instead of pass direct to the function ? First binding it inside Constructor and then on click setState to true ?

constructor(props) {
super(props);
this.state:{lightOn: false};
this.OnClick = this.OnClick.bind(this);
}

onClick(event) {
this.setState({lightOn: true});
}

onClick ={this.onClick()}

As you are using redux , you can do it this way by returning a fresh state instead

   return {...state, action.payload };
查看更多
Root(大扎)
4楼-- · 2019-05-18 04:16

How it works? Through connect you can inject props that are used to control redux state and bind it to the component. For example:

class SomeComponent extends React.Component {
    componentWillMount() {
        this.props.sendSomething('someName');
    }

    render() {
        return (
            <div>{this.props.stateProp}</div>
        )
    }
}

const mapStateToProps = state = ({
    stateProp: state.someReduxState,
});

const mapDispatchToProps = dispatch => ({
    sendSomething: (name) => dispatch(someActionCreator(name)),
});

const ConnectedComponent = connect(
    mapStateToProps,
    mapDispatchToProps,
)(SomeComponent);

In given example you are binding someReduxState part of state to stateProp, which is injected into component. You are injecting method (sendSomething) that will dispatch and action in redux. If component relays on displaying part of redux state, it will be updated each time redux state changes, because props that are injected to component will change. You can find more in official Redux documentation here, or you can watch free tutorial on egghead here.

查看更多
趁早两清
5楼-- · 2019-05-18 04:29

Instead of setting state with array I am now setting with object. I guess I was putting lightOn object in light array which was making it inaccessible.

action:

export const setLight = lightStatus => dispatch => {
  dispatch({ type: SET_LIGHT, payload: lightStatus });
};

reducer:

import { SET_LIGHT } from '../actions/types';

export default function(state = { on: false }, action) {
  console.log('state ', state);
  switch (action.type) {
    case SET_LIGHT:
      return { ...state, on: action.payload };
    default:
      return state;
  }
}

the component:

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

class Light extends Component {
  render() {
    return (
      <div>
        Light On: {this.props.light.on.toString()}
        <button
          className="btn"
          onClick={() => this.props.setLight(!this.props.light.on)}
        >
          Set Light!
        </button>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return { light: state.light };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ setLight: setLight }, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Light);
查看更多
Melony?
6楼-- · 2019-05-18 04:31

You are not returning a new state here

export default function(state = [], action) {
  switch (action.type) {
    case SET_LIGHT:
      return action.payload;
    default:
      return state;
  }
}

You're directly returning action.payload which is simply lightStatus

Which means you can't access state.lightOn in your mapStateToProps

Your should be doing

 case SET_LIGHT:
              return {
                ...state,
                lightOn: action.payload,
              };

Or directly

case SET_LIGHT:
                  return {
                    ...state,
                    action.payload,
                  };

This way the entire state is not lost and its not directly mutating the state. Here i am using the object spread operator instead of Object.assign for the sake of simpicity.

查看更多
登录 后发表回答