Evolutio
Evolutio

Reputation: 974

right way to POST data to a server and handle response with redux

I'm very new to react and redux.

Now I want to rewrite my post request with a redux process.

my current request looks like this:

  _handleSubmit(event) {
    axios
      .post('/createUrl', {
        url: this.state.url
      })
      .then((response) => {
        this.setState({
          shortenInfos: response.data
        })
      })
      .catch((error) => {
        console.log(error);
      });

    event.preventDefault()
  }

now I created a store:

export default function url(state = 0, action) {
  switch (action.type) {
    case 'CREATE_URL':
      // maybe axios request?!
      return `${action.url}/test`
    case 'CREATED_URL':
      return `${action.url}/created`
    default:
      return state
  }
}

so where I must use my store.dispatch()? Should I make my _handleSubmit something like this?

_handleSubmit(event) {
  axios
    .post('/createUrl', {
      url: this.state.url
    })
    .then((response) => {
      store.dispatch({
        type: 'CREATED_URL',
        url: response.data
      })
    })
    .catch((error) => {
      console.log(error);
    });

  event.preventDefault()
}

I think this is wrong? And where I must use mapStateToProps method? Or should I do the axios-request in my CREATE_URL in my reducer?

Upvotes: 0

Views: 5664

Answers (1)

totymedli
totymedli

Reputation: 31048

Introduction

Using React with Redux gives you high freedom on how you can do things. The downside of this is that it can be hard to find out how things should be done properly, mainly because there is no standard or comprehensive guide to the use of the many dependency you need for a properly implemented project. This answer will guide you through the basics with links to references that will help you to find out wheres next and how to deeper your knowledge.

Reducer

Reducers should be pure, meaning that they have no side effects (like making axios requests) and they should always return a new object/array/value instead of changing the previous state. It is also a good practice to use action types as constants. You can place action types wherever you want, but for simplicity I will put them into the reducer's file, but there are better ways to organize them like using ducks.

export const CREATED_URL = 'CREATE_URL';

export default const url = (state = '', action) => {
  switch (action.type) {
    case CREATED_URL:
      return action.url;
    default:
      return state;
  }
};

Asynchronous actions

Everything that causes a side effect should be an action, so XHR should happen there. Because XHR should be asynchronous it is recommended to use a middleware: redux-thunk and redux-saga are two popular solutions. I will go with thunk so install it first.

First (because const has temporal dead zone) you need an action that will "load" the result of the XHR to the store:

import { CREATED_URL } from './reducer';

const createdUrl = url => ({
  type: CREATED_URL,
  url, // ES6 trailing comma for prettier git diffs
});

Then you can create the action that will fire the XHR, wait for the response then load it to the store using the action created previously. We need to return a function that will receive dispatch as the parameter. This technique is used in functional programming and is called currying.

export const createUrl = url => dispatch => { // with only 1 parameter the parentheses can be omited
  axios
    .post('/createUrl', { url }) // ES6 Shorthand property name in { url }
    .then(response => {
      dispatch(createdUrl({
        url: response.data,
      })
    })
    .catch(error => {
      // @TODO dispatch an action that will show a message
      // notifying the user that the request failed
      console.log(error);
    }); 
}

Usage in the React component.

Preparation

For ease of use, you need to connect your React component with Redux. react-redux comes to the rescue. Read the API documentation and add the <Provider> component to the root of your React component tree.

Now, in the top of your React component's file, import all the necessary stuff:

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { createUrl } from './reducer';

mapStateToProps and mapDispatchToProps

Then create the two helper functions for connect:

const mapStateToProps = store => ({ url: store.url })

const mapDispatchToProps = dispatch => bindActionCreators({ createUrl }, dispatch)

With the help of mapStateToProps you can subscribe to store updates and inject the important parts of the Redux store to your components props. mapStateToProps should return an object that will be merged to the component's props. Usually we just do something like store => store.url but because our example is so simple that the reducer returns a plain string instead of something more complex in an object, we need to wrap that string into an object over here.

mapDispatchToProps with the help of bindActionCreators will inject the passed actions to the component's props so we can call and pass them down to subcomponents with ease: this.props.createUrl().

The component itself

Now we can create the component itself. I will use an ES6 class to show an example with componentDidMount, but if you don't need that and you have a stateless component, you can go with a function too.

class Example extends React.Component {

  componentDidMount() {
    // If you need to load data from a remote endpoint place the action call here, like so:
    // this.props.createUrl('your-url');
  }

  render() {
    return (
      <div>
        <div>URL injected from the store, automatically refreshed on change: {this.props.url}</div>
        <div onClick={event => {this.props.createUrl('your-url');}}>Click me to fetch URL</div> 
      </div>
    )
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Example)

Upvotes: 9

Related Questions