anonym
anonym

Reputation: 4850

React-Redux: How do I set the initial state from the response of an asynchronous AJAX call?

How to setState() the response received from an AJAX Request so that I can display them in the page?

 constructor(props)
 {
    super(props);
    this.state = {
        email: '',
        first_name: '',
        middle_name: '',
        country: '',
        country_code: '',
        mobile_number: '',
        gender: ''
    }
 }

componentDidMount()
{
    store.dispatch(getUserProfile())
    .then(() => {
        const user = this.props.userProfile.userProfile && this.props.userProfile.userProfile.data.data;
        this.setState({
          email: user.email,
          first_name: user.first_name
        });

    })
}

render()
{
    return (
       <div className="form-group col-sm-12">
            <label htmlFor="email">Email*</label>
            <input type="email" name="email" value={this.state.email || ''}/>
       </div>

        <div className="form-group col-sm-12">
            <label htmlFor="email">First Name*</label>
            <input type="email" name="email" value={this.state.first_name || ''}/>
       </div>
    )
}

Apparently, I can't use .then() with store.dispatch method.

Uncaught TypeError: _store2.default.dispatch(...).then is not a function

getUserProfile() action function

import axios from 'axios';

export function getUserProfile()
{
    return function(dispatch)
    {

        dispatch(userProfileSuccess(false));
        dispatch(userProfileError(null));

        const request = axios
        ({
            url: "http://testapi/auth/v1/user/details",
            method: "get",
            headers: {
                'Content-Type': 'application/json',
                'Authorization' : 'Bearer ' + localStorage.getItem('access_token')
            }
        })

        .then(function(response) { dispatch(userProfileSuccess(response)); })
        .catch(function(error) {
            console.log(error)
        });

        return {
            type: 'USER_PROFILE_SUCCESS',
            payload: request
        }

    };

}

function userProfileSuccess(userProfile)
{
    return {
        type: 'USER_PROFILE_SUCCESS',
        userProfile: userProfile
    };
}
function userProfileError(userProfileError)
{
    return {
        type: 'USER_PROFILE_ERROR',
        userProfileError: userProfileError
    };
}

export default getUserProfile;

In the AJAX call, I tried:

.then(function(response) {
        return new Promise((resolve) => {
            dispatch(userProfileSuccess(response));
            resolve();
        });

    })

but the console reports the same error.

Is there a callback that I can pass to store.dispatch? What is the correct approach to this?

Upvotes: 2

Views: 2726

Answers (3)

Prakash Sharma
Prakash Sharma

Reputation: 16472

As you are using redux then your redux store should keep track about when the api call is in progress or has completed or caught some error. So instead of passing any callback or promise, you should dispatch an action for each event like processing, success, error etc (which you are already doing in getprofile function). Though i would say you nicely distinguish between process, success, error. For example you getprofile method should roughly look like this

export function getUserProfile() {
    return function (dispatch) {
        dispatch(userProfileProcessing())

        const request = axios({
            url: "http://testapi/auth/v1/user/details",
            method: "get",
            headers: {
                'Content-Type': 'application/json',
                'Authorization' : 'Bearer ' + localStorage.getItem('access_token'),
            },
        })

        .then(function (response) {
            dispatch(userProfileSuccess(response))
        })
        .catch(function (error) {
            dispatch(userProfileError(response))
            console.log(error)
        });
    };
}

It is just what i prefer. If you want it your way, that is also fine.

Now everytime you dispatch any action, redux will update the reducer state. So thats the place where you can set/reset some flag to make the component aware of what is going on with api call. So your reducer might look like this:

// getUserProfileReducer.js

userProfileReducer = (state = {}, action) => {
    switch (action.type) {
        case 'USER_PROFILE_PROCESSING':
            return {
                ...state,
                processing: true,
                success: false,
                fail: false,
                userProfile: null,
            }
        case 'USER_PROFILE_SUCCESS':
            return {
                ...state,
                processing: false,
                success: true,
                fail: false,
                userProfile: action.userProfile,
            }
        case 'USER_PROFILE_Error':
            return {
                ...state,
                processing: false,
                success: false,
                fail: true,
                userProfile: null,
            }
    }
}

Now all you need to do is to access this state from you component so that you can take necessary action according to that. For that you can user mapStateToProps function which convert the redux state to prop of the component.

constructor(props) {
    super(props)
    this.state = {
        email: '',
        first_name: '',
        middle_name: '',
        country: '',
        country_code: '',
        mobile_number: '',
        gender: '',
    }
}

componentWillReceiveProps(newProps) {
    if (newProps.userProfileStatus.success) {
        // The success flag is true so set the state
        const user = newProps.userProfileStatus
        this.setState({
            email: user.email,
            first_name: user.first_name,
        })
    }
    else if (newProps.userProfileStatus.processing) {
        // Api call is in progress so do action according to that like show loader etc.
    }
}
componentDidMount() {
    store.dispatch(getUserProfile())
}

render() {
    return (
        ...
    )
}

const mapStateToProps = (state) => {
    return {
        userProfileStatus: state.userProfileReducer,
    }
}

Upvotes: 1

Arjita Mitra
Arjita Mitra

Reputation: 962

You can add a callback in componentDidMount()

componentDidMount()
{
    store.dispatch(getUserProfile(), () => {
        const user = this.props.userProfile.userProfile && this.props.userProfile.userProfile.data.data;
        this.setState({
          email: user.email,
          first_name: user.first_name
        });

    })
}

This may not run exactly same, I just want to give you an idea how to add callback using arrow function so that you don't need to use then.

Upvotes: 2

tony
tony

Reputation: 1326

Redux stores the state in the Redux store, separately from the React component state (think setState). You are almost there. What you need to do is guide the result data from the async dispatch to the redux store and then to your local component state. Steps 3 and 4 below.

  1. Dispatch an async action to fetch the data.
  2. Dispatch an action from within the promise to populate the redux state.
  3. Write a reducer that intercepts the action and populates the redux state.
  4. Connect your local component state with the redux state by using the connect function.

Upvotes: 1

Related Questions