0
votes

I am making a React-redux component to embed in Laravel blade file. Where in the react side,

I am using redux with the thunk, When I try to get data without thunk from the Laravel route, it getting properly.

But When I use an axios request in the action creator to get data asynchronously. It gives the:

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

This is the entry component of the react side.

Entry.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

import reducer from '../store/reducers/reducer';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';

import thunk from "redux-thunk";


const store = createStore(reducer, applyMiddleware(thunk)
+window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
// console.log(getState());

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>
    , document.getElementById('like_post'));

This is the App.js main component

import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from 'prop-types';
import axios from 'axios';
import {getCurrentPostLikes} from '../store/actions/actions';

class App extends Component {
    constructor(props) {
        super(props)

        var domain = window.location.hostname;
        var url = window.location.pathname;
        var urlsplit = url.split("/");
        var post_slug = urlsplit[urlsplit.length - 1];

        this.state={
            post_slug: post_slug,
            post_likes:''
        }
    }

    kFormatter(num) {
        return num > 999 ? (num / 1000).toFixed(1) + 'k' : num
    }

    componentDidMount() {
        this.props.getCurrentPostLikes();
        // axios.get(`/api/get_post_likes/${this.state.post_slug}`)
        // .then(response => {
        //     console.log(response.data.likes);
        //     this.setState({
        //         post_likes: response.data.likes
        //     })
        // })
    }

  render() {
    return (
      <div className="App">
        <a href="javascript:"><img src="/images/love.svg" alt="post like" width="50px" height="50px"/></a>
        <p>{this.kFormatter(this.state.post_likes)}</p>
        <p><span>{this.props.likes}</span></p>
      </div>
    );
  }
}


export default connect(null, {getCurrentPostLikes})(App);
// export default connect( mapStateToProps, mapDispachToProps )(App);

This is the actions.js file /store/actions/actions.js

// types.js is also defined properly as
// export const GET_POST_LIKES = 'GET_POST_LIKES';

import axios from 'axios';
import {GET_POST_LIKES} from './types';

// Get current post likes
export const getCurrentPostLikes = () => dispatch => {
  return dispatch => {
    setTimeout(() => {
      axios.get(`/api/get_post_likes/2`)
          .then(res => {
              // console.log(response.data.likes);
              // console.log(getState());
            setTimeout(() => {
                dispatch({
                    type: GET_POST_LIKES,
                    payload: res.data.likes
                })
            }, 4000);
          })
          .catch(err => {
              dispatch({
                  type: GET_POST_LIKES,
                  payload: {}
              })
          })
    }, 3000);
  }
}

Tried this action creator also, but still same error

export const getCurrentPostLikes = () => {
  return dispatch => {
      axios.get(`/api/get_post_likes/2`)
          .then(res => {
                dispatch({
                    type: GET_POST_LIKES,
                    payload: res.data.likes
                })
          })
          .catch(err => {
              dispatch({
                  type: GET_POST_LIKES,
                  payload: {}
              })
          })
  }
}

This is the reducers.js file under /store/reducers/reducer.js

import { GET_POST_LIKES } from '../actions/types';

const initialState = {
    likes: null
};

const reducer = (state=initialState, action) => {
    const newState = {...state};

    switch(action.type){
        case 'GET_POST_LIKES':
            return {
                ...state,
                post: action.payload
            }

        case 'LIKE_UP':
            newState.likes += action.value
            break;
    }
    return newState;
};

export default reducer;

Now, This should return a field value of posts table, with post id = 2.

4
The main question is why the setTimeouts in the action creator?? You're not returning a plain object you're returning a function... - SakoBu
Also tried without setTimeout, please check the updated action creator in the code, but still same error - Ashwani Garg
@SakoBu, I was about to ask the same thing. - Daniel

4 Answers

5
votes

Your problem is at Entry.js, this line:

const store = createStore(reducer, applyMiddleware(thunk) +window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

You are setting the thunk middleware as the second parameter. The second parameter is for the initial state. The thunk middleware should be part of the composed enhancers in the third parameter of the createStore function.

The parameters should be applied as such:

createStore(
  connectRouter(history)(rootReducer),
  initialState,
  composedEnhancers
)

Full example:

import { createStore, applyMiddleware, compose } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import thunk from 'redux-thunk'
import createHistory from 'history/createHashHistory'
import rootReducer from './core/reducers'

export const history = createHistory()

const initialState = {}
const enhancers = []
const middleware = [thunk, routerMiddleware(history)]

if (process.env.NODE_ENV === 'development') {
  const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__

  if (typeof devToolsExtension === 'function') {
    enhancers.push(devToolsExtension())
  }
}

const composedEnhancers = compose(
  applyMiddleware(...middleware),
  ...enhancers
)


export default createStore(
  connectRouter(history)(rootReducer),
  initialState,
  composedEnhancers
)
3
votes

This is the 'signature' of an action creator curried function using redux-thunk:

export const getCurrentPostLikes = () => async dispatch => {
  const response = await axios.get(`/api/get_post_likes/2`);
  dispatch({ type: GET_POST_LIKES, payload: response.data.likes });
};
1
votes

This is how you want your action creator to look:

export const getCurrentPosts = () => {
  return async function(dispatch, getState) {
    const response = await axios.get("/api/get_post_likes");
    dispatch({ type: "GET_POST_LIKES", payload: response });
  };
};

Leave the Promises out of it and go with ES7 async/await syntax and also leave out the setTimeout, unless you can provide a really good justification for wanting to time out an asynchronous request that is already taking a non-zero amount of time to complete, maybe I don't understand, but you do want that data don't you? It's necessary for your reducers to do their jobs and eventually update state in your application.

What I have above is not perfect in that its boilerplate, like you don't really need getState in there, not really, yes its part of middleware, but if you are not going to use something, no need to define it, but anyway I am trying to get you to a point where your action creator works again and you are sparing your eyes and your mind.

0
votes

It seems your action has an extra dispatch =>

export const getCurrentPostLikes = () => dispatch => {

    setTimeout(() => {
      axios.get(`/api/get_post_likes/2`)
          .then(res => {
              // console.log(response.data.likes);
              // console.log(getState());
            setTimeout(() => {
                dispatch({
                    type: GET_POST_LIKES,
                    payload: res.data.likes
                })
            }, 4000);
          })
          .catch(err => {
              dispatch({
                  type: GET_POST_LIKES,
                  payload: {}
              })
          })
    }, 3000);

}