How to use react-redux to store a token and use it on multiple axios request?
Asked Answered
M

2

9

I am building an app using react native that requires me to do multiple get request on same API with token.

Let say the URL is like this:

Token URL = https://test.co/v1/tokens, API URL 1 = https://test.co/v1/students and API URL 2 = https://test.co/v1/cars

First of all, to fetch the data from either API URLs, I wrote it like this

Example for students_actions.js

import axios from 'axios';
import { FETCH_STUDENT } from './types';

const TOKEN_URL = '...'
const STUDENT_URL = '...'

export const fetchStudent = (callback) => async (dispatch) => {
    axios.post(TOKEN_URL, {
        email: 'email',
        password: 'password',
        role: 'user'
    })
    .then((response) => {
        const accessToken = response.data.token;
        //console.log(accessToken);
        axios.get(STUDENT_URL, {
            headers: { 'Authorization': 'Bearer '.concat(accessToken) }
        })
        .then((studentResponse) => {
            dispatch({ type: FETCH_STUDENT, payload: studentResponse.data });
            callback();
        })
        .catch((e) => {
            console.log(e);
        });
    })
    .catch((error) => {
        console.log(error);
    });
};

Example for students_reducers.js

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

const INITIAL_STATE = {
    data: []
};

export default function (state = INITIAL_STATE, action) {
    switch (action.type) {
        case FETCH_STUDENT:
            return action.payload;
        default:
            return state;
    }
}

and call it inside render function like this

//some code
import { connect } from 'react-redux';

import * as actions from '../actions';

onButtonPressProfile = () => {
    this.props.fetchStudent(() => {
        this.props.navigation.navigate('Profile');
    });
}
class StudentProfile extends Component {
    render() {
        return(
            <View><Text>{this.props.students.name}</Text></View>
        );
    }
}

function mapStateToProps({ students }) {
    return { students: students.data };
}

export default connect(mapStateToProps, actions)(StudentProfile);

While this is all running without any problem I feel like students_actions.js can be further simplify by writing the code for retrieving the token in other file and call the value back inside students_actions.js for GET request.

The reason is so I do not have to request token everytime I want to access either students or cars. Lets say, I did request one time and I can use the same token for like 24 hours to access the API. Once it expired, then I have to do another request for token to access the API again.

I already wrote the code for token_actions.js together with token_reducer.js. Below are the two codes.

token_actions.js

//import library
// this code works
const TOKEN_URL = apiConfig.url + 'tokens';
const auth = {
    email: 'email',
    password: 'password',
    role: 'user'
};

export const fetchToken = () => async (dispatch, getState) => {
        axios.post(TOKEN_URL, auth)
        .then((response) => {
            
            dispatch({ type: FETCH_TOKEN, payload: response.data.token });
        })
        .catch((error) => {
            console.log(error);
        });
};

token_reducer.js

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

const INITIAL_STATE = {
    data: []
};

export default function (state = INITIAL_STATE, action) {
    switch (action.type) {
        case FETCH_TOKEN:
            return action.payload;
        default:
            return state;
}

}

students_actions.js

axios.get(STUDENT_URL, { headers: {
                           'Authorization': 'Bearer '.concat(here is the value from token_actions)}})

And now I am stuck at how should I call/import the payload from token_actions.js into students_actions.js? Should I use mapStateToProps or is there any other way to do this?

Right now, this app does not have any authentication function yet. It is basically an app that shows the data fetched from API.

I wrote this app mainly based on examples I found online and for this case I found this example but seems not really what I want to achieve.

I do not really quite understand JavaScript so I will be really glad if anyone could pointed out any link related to this case or maybe same questions here on Stack Overflow and also maybe some suggestions.

Magnesite answered 15/11, 2017 at 8:34 Comment(0)
P
10

I the logical thing to do would be to create something like an AuthReducer where you store your token and your refresh token. This is an example of my basic AuthReducer:

export const INITIAL_STATE = {
  oAuthToken: '',
  refreshToken: '',
};

export default AuthReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case REFRESH_OAUTH_DATA:
      const { oAuthToken, refreshToken } = action.payload;
      return { ...state, oAuthToken, refreshToken };

    case LOGOUT:
      return INITIAL_STATE;

    case LOGIN_FETCH_SUCCESS:
      const { oAuthToken, refreshToken } = action.payload;
      return { ...state, oAuthToken, refreshToken };

    default:
      return state;
  }
};

Now you can get your token in your actions using the getState method doing something like:

export const fetchStudent = (callback) => async (dispatch, getState) => {
    const token = getState().AuthReducer.oAuthToken;
    ....
};

Remember that if you are using ES6 you might also want to use await:

export const fetchStudent = (callback) => async (dispatch, getState) => {
    try {
        const accessToken = getState().AuthReducer.oAuthToken;

        let response = await axios.get(STUDENT_URL, {
            headers: { 'Authorization': 'Bearer '.concat(accessToken) }
         })

         dispatch({ type: FETCH_STUDENT, payload: response.data });
         callback();
    } catch(e) {
        console.log(e);
    }
};

This way your code is way easier to read and maintain.

Procto answered 15/11, 2017 at 8:41 Comment(8)
How should I import the AuthReducer? Is it like import { AuthReducer } from './AuthReducer'; on top of the students_actions.js?Magnesite
No, the getState method is a method from your redux store which is made available in your action thanks to redux-thunk. You don't need to import the reducer in your action to access it.Procto
I usually like to define an API folder in my top level and each API feature has its own folder and inside the required actions to do the fetch and a reducer to hold the state of the request and the result.Procto
hi, I tried with your basic AuthReducer setup, eslint gave me error AuthReducer is not defined. Where should I defined it?Magnesite
how did you import? or was it when defining it?Procto
for the AuthReducer.js, I import the actions like this import { FETCH_TOKEN } from '../actions/types';. It is basically the same structure as example for token_reducer.js that I posted. Only the difference is I change export default function to export default AuthReducerMagnesite
never mind, I solved it by changing the line to export default function AuthReducer and inside reducers/index.js I wrote import { default as AuthReducer } from './AuthReducer'. Also I did not use the arrow function. Thank you for your suggestion. It gave me some idea.Magnesite
glad to being able toç helpProcto
C
1

There is a better (= simpler) approach without redux-thunk at all, and without react-redux additional hooks.

Just create and export your store, and import it everywhere as usual, without using useStore and useDispatch.

Here you can just import store into your api layer and call something like store.getState().auth.token before each request.

Some may say that this way you won't be able to test the app, but they would be wrong, you can mock this import easily, but even better you can just reset your store before each test without mocking and import it as usual.

You can do the same thing with dispatch and remove redux-thunk from the project. The code will be smaller, cleaner, faster.

Comeon answered 26/10, 2023 at 16:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.