22

I am new to React JS and Redux and it has been too overwhelming to get going. I am trying to make a POST request using Axios, but I am unable to make it. May be I am missing something in the container file. Below is the code. Check plnkr

Update: I am getting @@redux-form/SET_SUBMIT_SUCCEEDED message after submitting. But when I am checking in the network tab, I don't see the call to API. And also when I am consoling the submitted values, I see only name and fullname values. It doesn't consist of logo and details. What am I missing?

Component file

   import React, { PureComponent } from 'react'
   import PropTypes from 'prop-types'
   import { Field,reduxForm } from 'redux-form'
   import {   Columns,Column, TextArea, Label,Button  } from 'bloomer'
   import FormField from 'FormField'

   const validate = (values) => {
     const errors = {}
    const requiredFields = 
      ['organizationName','organizationFullName','organizationDetails']

    requiredFields.forEach((field) => {
     if (!values[field]) {
     errors[field] = 'This field can\'t be empty!'
    }
  })
     return errors
}

  const formConfig = {
   validate,
   form: 'createOrganization',
   enableReinitialize: true
   }

  export class CreateOrganization extends PureComponent {
   static propTypes = {
     isLoading:PropTypes.bool.isRequired,
     handleSubmit: PropTypes.func.isRequired, // from react-redux     
     submitting: PropTypes.bool.isRequired // from react-redux
    }
   onSubmit = data => {
     console.log(data)
   }
  render () {
     const { handleSubmit,submitting,isLoading } = this.props
      return (
        <Columns isCentered>
        <form onSubmit={handleSubmit(this.onSubmit.bind(this))} > 

          <Column isSize='3/6' >        
            <Label>Organization Name</Label>             
            <Field 
              name="organizationName"
              component={FormField}
              type="text"
              placeholder="Organization Name"
            />   
          </Column>       


          <Column isSize='3/6'>
            <Label>Organization Full Name</Label>              
            <Field
              name="organizationFullName"
              component={FormField}
              type="text"
              placeholder="Organization Full Name"
            />  
          </Column> 


           <Column isSize='3/6'>            
            <Label>Organization Logo</Label>              
            <Input                  
              name="organizationLogo"                  
              type="file"
              placeholder="Logo"
            /> 
          </Column>

          <Column isSize='3/6'>
            <Label>Organization Details</Label>         
                <TextArea placeholder={'Enter Details'} />               
          </Column>          


          <Column >
            <span className="create-button">
              <Button type="submit" isLoading={submitting || isLoading} isColor='primary'>
                Submit
              </Button>  
            </span> 
              <Button type="button" isColor='danger'>
                Cancel
              </Button>                
          </Column>  

        </form>
      </Columns>
    )    
  }
}

  export default reduxForm(formConfig)(CreateOrganization)

Container File

   import React, { PureComponent } from 'react'
   import PropTypes from 'prop-types'
   import { connect } from 'react-redux'
   import Loader from 'Loader'
   import organization from 'state/organization'
   import CreateOrganization from '../components/createOrganization'

   export class Create extends PureComponent {
   static propTypes = {    
     error: PropTypes.object,
     isLoaded: PropTypes.bool.isRequired,  
     create: PropTypes.func.isRequired,   
    }
    onSubmit = data => {
      this.props.create(data)
    }

    render () {
      const { isLoaded, error } = this.props
    return (      
       <CreateOrganization onSubmitForm={this.onSubmit} isLoading=
         {isLoading} />    
     )
   }
 }

   const mapStateToProps = state => ({
     error: organization.selectors.getError(state),
     isLoading: organization.selectors.isLoading(state)
   })

    const mapDispatchToProps = {
      create: organization.actions.create
    }


  export default connect(mapStateToProps, mapDispatchToProps)(Create)
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
jackD
  • 801
  • 2
  • 8
  • 26
  • 2
    First things first, people will begin down voting this question purely because the formatting is awful. Making it hard to read. You can post a codepen example if you're unable to get the formatting right on here. Second, you should be more descriptive in what you need. What's not working? Are you able to get into the callAPI function? Is the response not getting console logged? – Joshua Underwood Nov 28 '17 at 21:37
  • p.s. `` `this.props` is an object **not** a function – Joshua Underwood Nov 28 '17 at 21:38
  • I am not able to get into the call API function. Thank you for your suggestion.Will add changes – jackD Nov 28 '17 at 21:46
  • onSubmit should point to a function on the props object. – Joshua Underwood Nov 28 '17 at 22:01
  • And that function will be declared in action? – jackD Nov 28 '17 at 22:11
  • Can you link me to a place where this code is posted? I'm losing my mind trying to read it as is :) – Joshua Underwood Nov 28 '17 at 22:12
  • Hi. Here is the link https://plnkr.co/edit/w8H3YvFCunqC3621lmmd?p=preview But I have just posted code here for reference, since there are lot of dependencies created using webpack. – jackD Nov 28 '17 at 22:28
  • 1. containers/createOrganization.js: onSubmit={create} // Where is create coming from? 2. components/createOrganization.js: this.props.onSubmitForm //where is onSubmitForm coming from? You pass onSubmit as a prop in the container but not onSubmitForm – Joshua Underwood Nov 28 '17 at 22:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/160034/discussion-between-heblev-and-joshua-underwood). – jackD Nov 28 '17 at 22:40
  • seems like theres plenty of answers, just pointing out the `mapDispatchToProps` is malformed here, should be a function mapping dispatch to, well, props – swyx Dec 08 '17 at 04:02

4 Answers4

21

Your redux action creators must be plain, object and should dispatch and action with a mandatory key type. However using custom middlewares like redux-thunk you could call axios request within your action creators as without custom middlewares your action creators need to return plain object

Your action creator will look like

export function create (values) {

  return (dispatch) => {
     dispatch({type: CREATE_ORGANIZATION});
     axios.post('/url', values)   
        .then((res) =>{
            dispatch({type: CREATE_ORGANIZATION_SUCCESS, payload: res});
        })
        .catch((error)=> {
            dispatch({type: CREATE_ORGANIZATION_FAILURE, payload: error});
        })
  }

}

and your reducer will look like

export default (state = initialState, action) => {
  const payload = action.payload

   switch (action.type) {    
    case CREATE:    

      return {
        ...state,
        loading: true,
        loaded: false
      }

    case CREATE_SUCCESS:
      return {
        ...state,
        data: state.data.concat(payload.data),
        loading: false,
        loaded: true,
        error: null
      }   

      }

    case CREATE_FAILURE:

      return {
        ...state,
        loading: false,
        loaded: true,
        error: payload
      }
    default:
      return state
  }
}

now while creating the store you can do it like

import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
const store = createStore(
  reducer,
  applyMiddleware(thunk)
);

Apart from this you also need to setUp the redux form

you need to use combineReducers and Provider to pass on the store

import reducer from './reducer';
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form'

export const rootReducer = combineReducers({
   reducer,
   form: formReducer
})

CodeSandbox

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • check this answer and let me know if you face any difficulties – Shubham Khatri Dec 01 '17 at 07:08
  • I am still getting '@@redux-form/SET_SUBMIT_FAILED' in console – jackD Dec 01 '17 at 07:24
  • I added a codeSandbox with all the error rectification, you needed to use combineReducers and set up redux-form in it. Apart from it there were other mistakes like not using Uppercase characters for the Component. Let me know if you still face troubles – Shubham Khatri Dec 01 '17 at 07:39
  • Will have a look. Thanks – jackD Dec 01 '17 at 07:40
  • Difficult to produce a similar thing as it has lot dependencies. For ex: callAPI component written in another package and uses redux thunk for async calls. Check my updated plnkr – jackD Dec 01 '17 at 07:55
  • I see the callApi function, that need not be there, create essentially replaces callApi – Shubham Khatri Dec 01 '17 at 07:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/160259/discussion-between-heblev-and-shubham-khatri). – jackD Dec 01 '17 at 07:59
  • 1
    it seems your configuration doesn't allow redux-thunk to submit the action that triggers your api request. check your `applyMiddleware( your_middleware?, thunk, logger)(createStore)` Then try to implement the logic without the use of `redux-form` – Evhz Dec 01 '17 at 10:14
  • @Karlos, what do you mean – Shubham Khatri Dec 01 '17 at 10:15
  • redux-thunk is very sensible, review your configuration having a closer look at https://www.npmjs.com/package/redux-thunk, then try to free yourself from the use of `redux-form` – Evhz Dec 01 '17 at 10:17
  • Hi, @ShubhamKhatri this method is not working on the post request. Can you please help ?? `export function loginDetails(input) { let url = "admins/api_login"; debugger; return (dispatch) => { dispatch({type: "LOGIN_FETCH"}); ApiCall.postApiCall(url, input) .then((response) => { dispatch({type: "LOGIN_SUCCESS", payload: response}); }) .catch((error) => { dispatch({type: "LOGIN_FAIL", payload: error}); }) } } ` – Riya Kapuria Jan 11 '18 at 05:44
  • `const mapStateToProps = (state) => { return { sessionReducer: state.sessionReducer }; }; const mapDispatchToProps = (dispatch) => { return { loginDetails: (login_details, loggedIn, error, payload) => { dispatch(loginDetails(login_details, loggedIn, error, payload)); } }; }; export default connect(mapStateToProps, mapDispatchToProps)(Signin);` – Riya Kapuria Jan 11 '18 at 05:44
  • `const sessionReducer = (state = {INITIAL_STATE},action) => { switch(action.type){ case "LOGIN_FETCH": state={ ...state, login_details: action.payload, loggedIn: false }; break; case "LOGIN_SUCCESS": state={ ...state, loggedIn: true, error: null }; break; case "LOGIN_FAIL": state={ ...state, loggedIn: false, error: action.payload }; break; default: break; } return state; }; export default sessionReducer;` – Riya Kapuria Jan 11 '18 at 05:44
  • @RiyaKapuria, Can you point me to the question you had asked. Also what error or problem you are facing – Shubham Khatri Jan 11 '18 at 05:47
  • https://stackoverflow.com/questions/48162063/how-to-get-normal-json-object-from-promise-object-in-react-redux?noredirect=1#comment83303803_48162063 this is my qus – Riya Kapuria Jan 11 '18 at 05:48
2

You can do that easily with the help of redux-saga.

About redux-saga:

redux-saga is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, simple to test, and better at handling failures.

Installation:

$ npm install --save redux-saga 

or

$ yarn add redux-saga

Please refer to the link : https://github.com/redux-saga/redux-saga

Community
  • 1
  • 1
Vikas Yadav
  • 3,094
  • 2
  • 20
  • 21
1

Redux action creators apparently don't support asynchronous actions which is what you're trying to do with the post request. Redux Thunk should help with this.

You'll want a store.js file that looks like this:

//npm install --save redux-thunk

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducer.js';

// Note: this API requires redux@>=3.1.0
const store = createStore(
  rootReducer,
  applyMiddleware(thunk) //needed so you can do async
);

Here is what your actions file would look like. Create becomes an action creator that returns a function that then performs the post request and allows you to do the dispatch there allowing you to update your store/state. :

import axios from 'axios'
import { CREATE_ORGANIZATION, CREATE_ORGANIZATION_SUCCESS, CREATE_ORGANIZATION_FAILURE,

       } from './constants'
import * as selectors from './selectors'

/*
  CREATE ORGANIZATION
*/
//uses redux-thunk to make the post call happen
export function create (values) {
  return function(dispatch) {
    return axios.post('/url', values).then((response) => {
      dispatch({ type: 'Insert-constant-here'})
      console.log(response);
    })
    }
  }

Also, you'll want to pass in the onSubmit method you created into onSubmitForm like this. I'm not sure where isLoading is coming from because I don't see it imported in that container component so you may want to look at that too.:

  <createOrganization onSubmitForm={this.onSubmit.bind(this)} isLoading={isLoading} />
Dream_Cap
  • 2,292
  • 2
  • 18
  • 30
1

I would suggest using redux-promise-middleware. This library requires that the action have a property named payload that is a promise and this is easy with axios. It then integrates with Redux to suffix the root action type (e.g. GET_CUSTOMERS) with PENDING, FULFILLED, and REJECTED and fires those actions.

Firing the action is the same as any other action.

Store

import {applyMiddleware, compose, createStore} from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import reducer from './reducers';

let middleware = applyMiddleware(promiseMiddleware());
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const enhancer = composeEnhancers(middleware);

export default createStore(reducer, enhancer);

Action

export function getCustomers() {
  return {
    type: 'GET_CUSTOMERS',
    payload: axios.get('url/to/api')
      .then(res => {
        if (!res.ok) throw new Error('An error occurred.');
        return res;
      })
      .then(res => res.json())
      .catch(err => console.warn(err));
  };
}

Reducer

export default function(state = initialState, action) => {
  switch (action.type) {
    case 'GET_CUSTOMERS_PENDING':
      // this means the call is pending in the browser and has not
      // yet returned a response
      ...
    case 'GET_CUSTOMERS_FULFILLED':
      // this means the call is successful and the response has been set
      // to action.payload
      ...
    case 'GET_CUSTOMERS_REJECTED':
      // this means the response was unsuccessful so you can handle that
      // error here
      ...
    default:
      return state;
  }
}
Mike Perrenoud
  • 66,820
  • 29
  • 157
  • 232