Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling an action in another action in Redux React

Tags:

reactjs

redux

I am learning Redux in React. I am using redux-thunk. I have an action like below

export const login = (value, history) => dispatch => {
    Axios.post('/api/users/login', value)
        .then(response => {
            dispatch(getAddress()); // I am calling like this
        })
        .catch(error => {});
}

My address action is like below

export const getAddress = () => dispatch => {
    Axios.get('/api/address')
        .then(response => {
            dispatch({
                type: 'getAddresses',
                payload: response.data
            });
        })
        .catch(function (error) {});
}

I am trying to display value in my component like below

    render() {
        return (
           <div>Hello {console.log(this.props)}</div> // I am getting empty object
        );
    }


const mapStateToProps = state => ({
  addresses: state.address
});

export default connect(mapStateToProps)(Dashboard);

But I am getting empty Object.

I would like to redirect user to dashboard after login where the addresses will display. How can I do that ?

like image 264
abu abu Avatar asked Nov 21 '25 11:11

abu abu


2 Answers

Redux thunk middleware allows to chain via dispatch, dispatch(...) returns dispatched action, i.e. a promise if promise chains weren't broken:

export const login = (value, history) => dispatch => {
    return Axios.post('/api/users/login', value) // return!
    .then(response => {
        return dispatch(getAddress()); // return!
    })
    .catch(error => {});
}

export const getAddress = () => dispatch => {
    return Axios.get('/api/address') // return!
    .then(response => {
        return dispatch({ // return!
            type: 'getAddresses',
            payload: response.data
        });
    })
    .catch(function (error) {});
}

Initial action should be dispatched somewhere. In vanilla Redux setup, it could be dispatched after store definition:

store.dispatch(login());

In React Redux setup, components are integral parts of Redux data flow. Asynchronous thunk is a side effect and it should be dispatched once in componentDidMount. In case of initial action, it should be dispatched in parent component:

@connect()
class App extends Component {
  componentDidMount() {
    return this.props.dispatch(login());
  }

  render() {
    return !this.props.addresses ? 'Loading' :  <Dashboard />
  }
}

Here is a demo.

like image 65
Estus Flask Avatar answered Nov 23 '25 03:11

Estus Flask


So this needs a very basic introduction to the flow of Redux with 'side effects'.

The Redux Flow

  1. [View]Your view reacts in various ways.
  2. [Actions]We create an abstraction over these reactions called 'actions'.
  3. [Dispatcher] This actions are dispatched to the store.
  4. [Reducer] In the store we have logic written in 'reducers' that look at the actions and do manipulations to the store to update the store state.
  5. [State]This state is loaded on the react state tree.

Definition alert! In Redux stuff like calls to APIs are called side effects. This is because you are doing something outside the typical unidirectional data flow.

enter image description here

Hope you have understood this flow. I'm specifically concerned about a specific part that you seem to be confused about. So in your code you have tried to do this.

 then(response => {
     return dispatch(getAddress()); 
 })

What your code is trying to do here is that, it's trying to 'dispatch' a side effect. This is wrong!

Dispatch is almost always a plain old JS object which is serializable. Typically you will have stereotypical structure like this. Which you seem to have in your other get address call.

{ 
    type: 'GET_ADDRESS_REQUEST_SUCCESS', 
    payload: {} 
}

So the proper way to approach your task is like this. Think of dispatching actions like emitting signals when various things happen.

export const login = (value, history) => dispatch => {
    Axios.post('/api/users/login', value)
        .then(response => {
            //Send out a success signal
            dispatch({
                type: 'loginSuccess',
                payload: response.data
            });

            //Now that I have logged in successfully I can call for my address
            //This is simply how you call an action within an action!
            getAddress();
        }).catch(error => {
            //login failed signal
            dispatch({
                type: 'loginFailed',
                payload: response
            }); // I am calling like this
        });
}

export const getAddress = () => dispatch => {
    Axios.get('/api/address')
        .then(response => {
            //Get address success signal
            dispatch({
                type: 'getAddressSuccess',
                payload: response.data
            });
        }).catch(function (error) {
            //Get addresses failed signal
            dispatch({
                type: 'getAddressFailed',
                payload: error
            }); 
        });
}

The next important part is the reducer, which you have not included here! If actions emits signals think of the reducers as a receiver which receives and processes this signal. A reducer will update the existing state based on the action deployed.

export default function appReducer(state, action) {
    switch (action.type) {
        case 'getAddressSuccess': //Waiting on the signal!
            return Object.assign({},state,{address : action.payload} 
            //Set the proper address based on your payload from the api call

        default:
            return state

    }
}

You also should have setup your redux thunk configuration properly for this to fully work. Cheers!

============EDIT=============

So upon checking the codebase, you have used the combine reducer to meld multiple reducers together.

import { combineReducers } from 'redux';
import authReducer from './authReducer';
import addressReducer from './addressReducer';

const rootReducer = combineReducers({
  authReducer,
  addressReducer
});

export default rootReducer;

So in mapsStateToProps your store state will look like this

{
  authReducer: {
    // state managed by the authReducer 
  },
  addressReducer: {
    address: 'Test Streat, London'
  }
}

So change your mapStateToProps to look like this

const mapStateToProps = state => ({
  addresses: state.addressReducer.address
});

export default connect(mapStateToProps)(Dashboard);
like image 22
Dehan Avatar answered Nov 23 '25 01:11

Dehan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!