Search code examples
javascriptreactjsreduxaxiosredux-thunk

How to perform async axios call to REST api and update the store (redux thunk is confusing)?


So, I have to make an asynchronous request to REST api, I have read about redux thunk, but I am still a bit confused with that so I was trying to make an asynchronous request inside of reducer

Here is a working example, it returns plain json object

 return Object.assign({}, state,
 { texts: state.texts.concat({ data: 'some text'}) })

But when it comes to the network request it doesn't give any error, it just looks like return doesn't work, so that's why my components can't update

axios.get('https://dog.ceo/api/breeds/image/random').then(res => {
    return Object.assign({}, state,
 { texts: state.texts.concat({ data: action.payload }) })
})

Even setTimeout() does not work...

setTimeout(()=> {
    return Object.assign({}, state,
{ texts: state.texts.concat({ data: 'some text' }) })
},100)

Complete code as some people asked... Reducer:

import axios from 'axios'

const LOAD = 'LOAD'

let initialState = {
   texts: [
      { data: 'Orinary text' }
   ]
}

export let textReducer = (state = initialState, action) => {
   switch (action.type) {
      case LOAD:
         axios.get('https://dog.ceo/api/breeds/image/random').then(res => {
            return Object.assign({}, state,
               { texts: state.texts.concat({ data: action.payload }) })
         })
      default:
         return state
   }
}

export let loadActionCreator = () => {
   return { type: LOAD, payload: 'res.data.message ' }
}

Store:

import { createStore, combineReducers } from "redux";
import { textReducer } from './text-reducer'

let reducers = combineReducers({
   textReducer: textReducer
})

export let store = createStore(reducers)

Component:

import { loadActionCreator } from './Redux/text-reducer'
import { connect } from 'react-redux'

function mapStateToProps(state) {
  return ({
      state: state}
  )
}

function mapDispatchToProps(dispatch) {
  return ({
      loadActionCreator: () => dispatch(loadActionCreator())}
  )
}

function App(props) {
  return (
    <>
      {props.state.textReducer.texts.map(el => {
        return (
          <div>{el.data}</div>
        )
      })}
      <button onClick={() => props.loadActionCreator()}>Add data</button>
    </>
  );
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { subscribe } from 'redux'
import { store } from './Redux/store'
import { Provider } from 'react-redux'

let rerender = (store) => {
   ReactDOM.render(
      <Provider store={store}>
         <App />
      </Provider>,
      document.getElementById('root'))
}

rerender(store)

store.subscribe(() => {
   rerender(store)
})

Solution

  • Ok, so lets break this down.

    I have read about redux thunk, but I am still a bit confused with that so I was trying to make an asynchronous request inside of reducer.

    Redux Thunk came up with their library exactly for what you're trying to do. So you're in the right direction. See their their movitation below.

    Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.

    An action creator that returns a function to perform asynchronous dispatch:

    const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
    
    function increment() {
      return {
        type: INCREMENT_COUNTER
      };
    }
    
    function incrementAsync() {
      return dispatch => {
        setTimeout(() => {
          // Yay! Can invoke sync or async actions with `dispatch`
          dispatch(increment());
        }, 1000);
      };
    }
    

    However, you're trying to perform an async call, without any async middleware. This is pretty hard to do (properly) unless you perform the call from the component, in a thunk or using other design patterns like sagas.

    Lets stick with Redux Thunk and see what your implementation would look like. I've created a working example, sticking to the very basics.

    The relevant part is that you only had action creators, but not thunks:

    // Action creators
    export const textLoaded = text => {
      return {
        type: LOADED,
        text
      };
    };
    
    // Thunks
    export function textLoadRequest() {
      return dispatch => {
        /**
         * If you want to use axios, to recieve data; replace the
         * `setTimeout` with `axios.get()` and perform the dispatch
         * in the `then()` callback.
         */
        setTimeout(() => {
          dispatch(textLoaded("text after timeout"));
        }, 1000);
      };
    }
    

    Please don't hesitate to give feedback on the usefulness of this answer.