tomaytotomato
tomaytotomato

Reputation: 4048

Error: Actions must be plain objects. Use custom middleware for async actions, in a delete button?

I am trying to get a react action to fetch a list of files after the user deletes a file from the list.

In App.js I pass a handleClick function to the nested component.

App.js

    class App extends Component {
    static propTypes = {
        files: PropTypes.array.isRequired,
        isFetching: PropTypes.bool.isRequired,
        dispatch: PropTypes.func.isRequired,
        handleClick : PropTypes.func
    };

    componentDidMount() {
        const {dispatch} = this.props;
        dispatch(fetchFiles);
    }

    handleClick = fileId => {
        const {dispatch} = this.props;
        deleteFileById(dispatch,fileId);
    };

    render() {
        const {files, isFetching, dispatch} = this.props;
        const isEmpty = files.length === 0;
        return (
            <div>
                <h1>Uploadr</h1>
                {isEmpty
                    ? (isFetching ? <h2>Loading...</h2> : <h2>No files.</h2>)
                    : <div style={{opacity: isFetching ? 0.5 : 1}}>
                        <Files files={files} handleClick={this.handleClick}/>
                    </div>
                }
            </div>
        )
    }
}

const mapStateToProps = state => {
    const {isFetching, items: files} = state.files;

    return {
        files,
        isFetching,
    }
};


export default connect(mapStateToProps)(App)

Files.js

import React from 'react'
import PropTypes from 'prop-types'

const Files = ({files, handleClick }) => (
    <ul>
        {files.map((file, i) =>
            <li key={i}>{file.name}
                <button onClick={() => (handleClick(file.id))}>Delete</button>
            </li>
        )}
    </ul>
);

Files.propTypes = {
    files: PropTypes.array.isRequired,
    handleClick: PropTypes.func.isRequired
};

export default Files

actions.js

I am wanting to trigger a request to get a new list of files from the API after the delete action is done.

export const deleteFileById = (dispatch, fileId) => {
    dispatch(deleteFile);
    return fetch(`/api/files/${fileId}`, {method : 'delete'})
        .then(dispatch(fetchFiles(dispatch)))
};

export const fetchFiles = (dispatch) => {
    dispatch(requestFiles);
    return fetch('/api/files')
        .then(response => response.json())
        .then(json => dispatch(receiveFiles(json)))
};

However I am getting the following error

Error: Actions must be plain objects. Use custom middleware for async actions.

What is the best way to implement this

Upvotes: 0

Views: 92

Answers (1)

Hemadri Dasari
Hemadri Dasari

Reputation: 34014

An action will dispatch another action but not event handler function.

You no need to dispatch deleteFileById from component because this is a function exported in actions which will dispatch an action.

Please remove dispatch in handleClick to work.

Wrong one:

handleClick = fileId => {
    this.props.deleteFileById(dispatch(this.props.dispatch,fileId));
};

Correct one:

handleClick = fileId => {
    this.props.deleteFileById(this.props.dispatch,fileId);
};

Regarding this.props.deleteFileById is not a function.

There are many ways to access actions in your component. Below are few ways

You need to install prop-types

npm install -s prop-types

If your component is Test then set prop types as like below

import PropTypes from 'prop-types';
import React, {Component} from 'react';

class Test extends Component{
    render(){
      return(
        <div</div>
      )
    }
}

Test.propTypes = {
  deleteFileById: PropTypes.func
}

If you are using redux connect then

Without prop-types

import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';

class Test extends Component{
        render(){
          return(
            <div</div>
          )
        }
    }

export default connect(null, {...actions})(Test);

OR

With inbuilt react proptypes you no need to install prop-types separately

import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
import {push} from 'react-router-redux';

class Test extends Component{
static get propTypes() {
    return { 
      sendContactForm: React.PropTypes.func
    }
  }
        render(){
          return(
            <div</div>
          )
        }
    }

const actionsToProps = {
      deleteFileById: actions.deleteFileById,
      push
    }

export default connect(null, actionsToProps)(Test);

Your code App.jsx should be something like below

class App extends Component {
    static propTypes = {
        files: PropTypes.array.isRequired,
        isFetching: PropTypes.bool.isRequired,
        deleteFileById : PropTypes.func,
        fetchFiles: PropTypes.func
    };

    componentDidMount() {
       this.props.fetchFiles();
    }

    handleClick = fileId => {
        this.props.deleteFileById(fileId);
    };

    render() {
        const {files, isFetching} = this.props;
        const isEmpty = files.length === 0;
        return (
            <div>
                <h1>Uploadr</h1>
                {isEmpty
                    ? (isFetching ? <h2>Loading...</h2> : <h2>No files.</h2>)
                    : <div style={{opacity: isFetching ? 0.5 : 1}}>
                        <Files files={files} handleClick={this.handleClick}/>
                    </div>
                }
            </div>
        )
    }
}

const mapStateToProps = state => {
    const {isFetching, items: files} = state.files;

    return {
        files,
        isFetching,
    }
};


export default connect(mapStateToProps)(App)

dispatch should be returned in actions but not from component to actions or vice versa

Below is sample action file for your ref.

import ajax from '../ajax';
import {Map, fromJS} from 'immutable';
import config from '../config';
import {push} from 'react-router-redux'

export const URL_PREFIX = 'http://localhost:3000/api';

export const SEND_CONTACT_FORM_REQUEST = 'SEND_CONTACT_FORM_REQUEST';
export const SEND_CONTACT_FORM_SUCCESS = 'SEND_CONTACT_FORM_SUCCESS';
export const SEND_CONTACT_FORM_ERROR = 'SEND_CONTACT_FORM_ERROR';


export function sendContactFormRequest(){
  return {
    type: SEND_CONTACT_FORM_REQUEST,
    loading: true
  }
}

export function sendContactFormSuccess(data){
  return {
    type: SEND_CONTACT_FORM_SUCCESS,
    loading: false,
    data: data
  }
}

export function sendContactFormError(errors){
  return {
    type: SEND_CONTACT_FORM_ERROR,
    loading: false,
    errors: errors
  }
}



export function sendContactForm(firstName, lastName, email, subject, message) {
  return dispatch => {
    dispatch(sendContactFormRequest());
    return ajax.post(URL_PREFIX + '/communication/contact', { firstName, lastName, email, subject, message })
      .then(res => {
        dispatch(sendContactFormSuccess(res.data))


      })
      .catch(errors => {
        dispatch(sendContactFormError(errors))
      })
  }
}

Upvotes: 1

Related Questions