How to properly make REST calls from ReactJS + Redux application?

后端 未结 3 1256
南笙
南笙 2020-12-23 14:27

I\'m using ReactJS + Redux, along with Express and Webpack. There is an API built, and I want to be able to make REST calls -- GET, POST, PUT, DELETE -- from the client-side

3条回答
  •  执笔经年
    2020-12-23 15:04

    The simpliest way, is to do it using redux-thunk package. This package is an redux middleware, so first of all, you should connect it to redux:

    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers/index';
    
    const store = createStore(
      rootReducer,
      applyMiddleware(thunk)
    );
    

    This allows you to dispatch async actions along with regular sync actions. Let's create one of them:

    // actions.js
    
    export function fetchTodos() {
      // Instead of plain objects, we are returning function.
      return function(dispatch) {
        // Dispatching REQUEST action, which tells our app, that we are started requesting todos.
        dispatch({
          type: 'FETCH_TODOS_REQUEST'
        });
        return fetch('/api/todos')
          // Here, we are getting json body(in our case it will contain `todos` or `error` prop, depending on request was failed or not) from server response
          // And providing `response` and `body` variables to the next chain.
          .then(response => response.json().then(body => ({ response, body })))
          .then(({ response, body }) => {
            if (!response.ok) {
              // If request was failed, dispatching FAILURE action.
              dispatch({
                type: 'FETCH_TODOS_FAILURE',
                error: body.error
              });
            } else {
              // When everything is ok, dispatching SUCCESS action.
              dispatch({
                type: 'FETCH_TODOS_SUCCESS',
                todos: body.todos
              });
            }
          });
      }
    }
    

    I prefer to separate react components on presentational and container components. This approach was perfectly described in this article.

    Next, we should create TodosContainer component, which would provide data to presentational Todos component. Here, we are using react-redux library:

    // TodosContainer.js
    
    import React, { Component } from 'react';
    import { connect } from 'react-redux';
    import { fetchTodos } from '../actions';
    
    class TodosContainer extends Component {
      componentDidMount() {
        // When container was mounted, we need to start fetching todos.
        this.props.fetchTodos();
      }
    
      render() {
        // In some simple cases, it is not necessary to create separate `Todos` component. You can put todos markup directly here.
        return 
      }
    }
    
    // This function is used to convert redux global state to desired props.
    function mapStateToProps(state) {
      // `state` variable contains whole redux state.
      return {
        // I assume, you have `todos` state variable.
        // Todos will be available in container component as `this.props.todos`
        todos: state.todos
      };
    }
    
    // This function is used to provide callbacks to container component.
    function mapDispatchToProps(dispatch) {
      return {
        // This function will be available in component as `this.props.fetchTodos`
        fetchTodos: function() {
          dispatch(fetchTodos());
        }
      };
    }
    
    // We are using `connect` function to wrap our component with special component, which will provide to container all needed data.
    export default connect(mapStateToProps, mapDispatchToProps)(TodosContainer);
    

    Also, you should create todosReducer, which will handle FETCH_TODOS_SUCCESS action, and other 2 actions if you want display loader / error message.

    // reducers.js
    
    import { combineReducers } from 'redux';
    
    const INITIAL_STATE = {
      items: [],
      isFetching: false,
      error: undefined
    };
    
    function todosReducer(state = INITIAL_STATE, action) {
      switch (action.type) {
        case 'FETCH_TODOS_REQUEST':
          // This time, you may want to display loader in the UI.
          return Object.assign({}, state, {
            isFetching: true
          });
        case 'FETCH_TODOS_SUCCESS':
          // Adding derived todos to state
          return Object.assign({}, state, {
            isFetching: false,
            todos: action.todos
          });
        case 'FETCH_TODOS_FAILURE':
          // Providing error message to state, to be able display it in UI.
          return Object.assign({}, state, {
            isFetching: false,
            error: action.error
          });
        default:
          return state;
      }
    }
    
    export default combineReducers({
      todos: todosReducer
    });
    

    For other operations like CREATE, UPDATE, DELETE there is nothing special, they are implementing the same way.

提交回复
热议问题