reactjsreduxredux-reducers

React-Redux: how to set the state?


I am trying to understand someone else their code but have difficulty understand the interaction between Redux and React.

On a React page, I invoke a Redux action called getSubscriptionPlan. Inside that Redux action, I see it is able to load the correct data (point 1 below). This uses a reducer, in which I can again confirm the correct data is there (point 2 below).

Then the logic returns to the React page (point 3 below). I now would expect to be able to find somewhere in the Redux store the previously mentioned data. However, I can't find that data listed anywhere... not in this.state (where I would expect it), nor in this.props. Did the reducer perhaps not update the store state...?

What am I doing wrong and how can I get the data to point 3 below?

React page:

import { connect } from "react-redux";
import { getSubscriptionPlan } from "../../../appRedux/actions/planAction";

async componentDidMount() {
    let { planId } = this.state;
    await this.props.getSubscriptionPlan(planId);

    // 3. I can't find the data anywhere here: not inside this.state and not inside this.props.

    this.setState({plan: this.state.plan});
}

componentDidUpdate(prevProps, prevState) {
    if (prevProps.payment.paymentData !== this.props.payment.paymentData) {
        this.setState({
            checkout: this.props.payment.paymentData,
            plan: this.props.payment.paymentData.plan,
        });
    }
}

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

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

export default withRouter(
    connect(mapStateToProps, mapDispatchToProps)(Checkout)
);

Redux action:

export const getSubscriptionPlan = (id) => {
    let token = getAuthToken();
    return (dispatch) => {
        axios
            .get(`${url}/getSubscriptionPlan/${id}`, {
                headers: { Authorization: `${token}` },
            })
            .then((res) => {
                if (res.status === 200) {

                    // 1. From console.log(res.data) I know res.data correctly now contains the data

                    return dispatch({
                        type: GET_PLAN_SUCCESS,
                        payload: res.data,
                    });
            })
    };
};

Reducer:

export default function planReducer(state = initial_state, action) {
    switch (action.type) {
        case GET_PLAN_SUCCESS:

            // 2. I know action.payload, at this point contains the correct data.

            return { ...state, plan: action.payload };
        default:
            return state;
    }
}

Solution

  • You are getting tripped up on how Redux works.

    Redux does not use react component state. It manages state separately, and passes that state to components as props. When you call getSubscriptionPlan, you asynchronously dispatch an event to Redux, which handles the event and updates store state in the reducer. This state is the passed to the connected components mapStateToProps function, mapped to props, and then passed as props to your component. Passing new props triggers a componentDidUpdate and a rerender of the component.

    A few key things here.

    1. Redux does not interact with component state unless you explicitly set state with props passed from Redux.
    2. Redux is asynchronous. That means that when you make a change to state via dispatch, the change is not immediately available in the component, but only available when new props are passed. It's event driven, not data binding. As a result, in your code you woun't see the plan prop in componentDidMount because at the time componentDidMount the call to getSubscriptionPlan hasn't happened.

    You should see the prop populated in this.props in componentDidUpdate and in render before the didUpdate.

    When working with react, it's best to think of components as basically functions of props with some extra lifecycle methods attached.