user12590864
user12590864

Reputation:

React-Redux: Action Dispatch on button click not working

On click of Button my Action is not getting dispatched. Below are all the files viz - action, reducer, root reducer, configSTore, Index and Component.

Please help me why my action is not getting dispatched on button click

Actions.js

import axios from 'axios';

const requestURL='JSONUrl';
let responseData = '';

export function fetchRequests() {
    axios.get(requestURL)
    .then((res) => {
        responseData = res;
    });
    return responseData;
}

export const fetchDataAction = () => {
    return {
        type: 'FETCH_DATA',
        data: fetchRequests()
    };
}

export function fetchDataSuccessAction(err) {
    return {
        type: 'FETCH_DATA_SUCCESS',
        err
    };
}

export function fetchDataErrorAction(err) {
    return {
        type: 'FETCH_DATA_ERROR',
        err
    };
}

Reducer.js


export function fetchDataReducer(state = [], action) {
        switch(action.type) {
            case 'FETCH_DATA':
                return action.data;
            default: return state;
        }
}

export function fetchDataSuccessReducer(state = [], action) {
    switch(action.type) {
        case 'FETCH_DATA_SUCCESS':
            return action.err;
        default: return state;
    }
}

export function fetchDataErrorReducer(state = [], action) {
    switch(action.type) {
        case 'FETCH_DATA_ERROR':
            return action.err;
        default: return state;
    }
}

RootReducer

import {combineReducers} from 'redux';
import { fetchDataAction, fetchDataSuccessAction, fetchDataErrorAction}
from '../actions/fetchData';

export default combineReducers({
    fetchDataAction,
    fetchDataSuccessAction,
    fetchDataErrorAction
});

configStore.js

import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers/rootReducer";
import thunk from 'redux-thunk';

export default function configureStore() {
    const enhance = applyMiddleware(thunk);
 
    return createStore(
        rootReducer,        
        enhance
    );
}

INdex.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

import reportWebVitals from './reportWebVitals';
import Header from './App';
import configureStore from './store/configureSTore';
import {CounterApp} from './counter';
import { Provider } from 'react-redux';

ReactDOM.render(
    <Provider store={configureStore()}>
      <Header favcol="yellow"/>   
      <CounterApp /> 
    </Provider>,
  document.getElementById('root')
);

reportWebVitals();

My Component File

import React, { useState } from 'react';
import { fetchDataAction, fetchRequests } from './actions/fetchData';
import { connect } from 'react-redux';

 export class CounterApp extends React.Component {
    constructor(props) {
        super(props);
    }
    btnClick = () => {
        return this.props.fetchDataAction;        
    }
    render() {   
          
    return (        
        <div className="App">
            <h1>Hello</h1>                                       
            <div>
                <h1>Fetch Data click below</h1>
                <button onClick={() => this.props.fetchDataAction}>
                Fetch Data
                </button>
                {this.props.datafetchedFromApi}            
            </div>
        </div>
        )
    }
    
}
const mapStateToProps = state => ({    
    datafetchedFromApi: state.data
    
});

const mapDispatchToProps = (dispatch) => ({
    fetchDataAction: () => dispatch(fetchDataAction())  
});


export default connect(mapStateToProps, mapDispatchToProps)(CounterApp);

On click of Button my Action is not getting dispatched. Below are all the files viz - action, reducer, root reducer, configSTore, Index and Component.

Please help me why my action is not getting dispatched on button click

Upvotes: 1

Views: 4694

Answers (4)

Nike Lepz
Nike Lepz

Reputation: 543

The answer got too long, so first part is if you wanna fully understand why things went south. If you just want your problem solved so you can finally go pee, the second part is for you :).

First Part (Basically what asynchronous programming in JavaScript is, so any questions to what are asynchronous tasks in JS can be referred to this answer.)

Okay a couple of problems detected here. As others have pointed out make sure all the paths for imports are correct. Now assuming they are all correct, here's what you need to do to solve your problem.

First let's take a look at the top of your component file:

import React, { useState } from 'react';
import { fetchDataAction, fetchRequests } from './actions/fetchData';
import { connect } from 'react-redux';
...

And then the block where you have called fetchDataAction:

...
<button onClick={() => this.props.fetchDataAction}>
   Fetch Data
</button>
...

Here what you have done is this.props.fetchDataAction, I don't see you passing fetchDataAction as a prop to this component, so it's most probably undefined that's why you get an error TypeError: this.props.fetchDataAction is not a function because of course undefined is not a function. This was the first mistake I noticed.

Now, moving on to the second one. I'm gonna start with this

Dispatching actions in redux is synchronous. So you cannot do something like the following:

export default function SomeComponent(props) {
  const fetchAction = () => {
     let payload;
     //wait for 5 seconds for the async task(the setTimeout below is an async task) to populate the payload with data.
     setTimeout(() => payload = "This is my data", 5000); 
     //then return the action object
     return {
       type: 'Data',
       payload,
     };
   }
   const handleClick = () => {
       dispatch(fetchAction());
   }
   return (<>
    <Button onClick={handleClick} />
  </>);
}

The above will not throw any errors, but will certainly not do what I want it to do. The above will dispatch the following action object:

{
  type: 'Data',
  payload: undefined
}

which ofcourse is not what I want the payload to be.

And that's exactly what you're doing. Take a look at your fetchDataAction and fetchRequests functions:

...
let responseData = '';

export function fetchRequests() {
    axios.get(requestURL)
    .then((res) => {
        responseData = res;
    });
    return responseData;
}

export const fetchDataAction = () => {
    return {
        type: 'FETCH_DATA',
        data: fetchRequests()
    };
}
...

Now I'll compare with the example I've given above:

  • Here your responseData is analogous to my payload
  • Your fetchRequests function is analogous to my setTimeout

Looks familiar? I'm sure by now it does. Plain simple answer as to why it doesn't work is that you're performing an async task, in your case you're making a network request with axios.get(requestUrl)...

Network requests are async(now if you don't know what async things are, check out https://javascript.info/callbacks which gives you idea about what those are. Also check out a video on it by TheNetNinja https://www.youtube.com/watch?v=ZcQyJ-gxke0 ), in simple words, \network requests take some time to get finished(just like setTimeout).

So the axios.get request takes some time to get the response back from the server. Now the other tasks(below it) won't wait for that request to get completed, instead js will execute those tasks immediately without waiting for the response.

I know this answer is getting too long. But I want you to understand, because trust me I have made the same mistakes before :).

So in your fetchRequests function:

export function fetchRequests() {
    axios.get(requestURL) --- (1)
    .then((res) => {
        responseData = res; --- (2)
    });
    return responseData; --- (3)
}

In line 1, you start an async task. Remember the function inside then block will execute only after sometime. So the responseData is still undefined. Instead of line (2), line (3) will execute first, cause as I told you earlier, js won't wait for the response from the server(the technical wording is 'the thread doesn't get blocked by network request'.) So basically you're returning undefined from this function.

Also see this video by JavaBrains. He uses an excellent analogy to understand async tasks in js, and you might also learn about what the event loop is and about single threaded-ness of javascript. https://www.youtube.com/watch?v=EI7sN1dDwcY

Now the Second part (I really wanna go pee): Replace(I've pointed out where I have made changes)

Your component file with this.

import React, { useState } from 'react';
import { fetchDataAction, fetchRequests } from './actions/fetchData';
import { connect } from 'react-redux';

export class CounterApp extends React.Component {
  constructor(props) {
      super(props);
  }
  btnClick = () => {
     return this.props.fetchDataAction;        
  }
    render() {       
        return (        
            <div className="App">
                <h1>Hello</h1>                                       
                <div>
                    <h1>Fetch Data click below</h1>
                    <button onClick={() => fetchDataAction()}> //this is the only change here
                        Fetch Data
                    </button>
                    {this.props.datafetchedFromApi}            
                </div>
            </div>
        )
    } 
}
const mapStateToProps = state => ({    
    datafetchedFromApi: state.data  
});

const mapDispatchToProps = (dispatch) => ({
    fetchDataAction: () => dispatch(fetchDataAction())  
});

export default connect(mapStateToProps, mapDispatchToProps)(CounterApp);

And then in your action.js file:

import axios from 'axios';

const requestURL='JSONUrl';
let responseData = '';

export function fetchRequests() { 
    return axios.get(requestURL) //change is here
    //removed return responseData and the 'then' block
}

export const fetchDataAction = () => { 
    return dispatch => {                        //almost whole of this function is changed and that's it
    fetchRequests().then((res) => {
       responseData = res;
       dispatch({
            type: 'FETCH_DATA',
            data: responseData
       })
    });
  };
}

export function fetchDataSuccessAction(err) {
    return {
        type: 'FETCH_DATA_SUCCESS',
        err
    };
}

export function fetchDataErrorAction(err) {
    return {
        type: 'FETCH_DATA_ERROR',
        err
    };
}

Now it should work. Tell me more if it doesn't. Remember this answer is assuming that you have all your functions imported properly into your files. I'll be making edits if this doesn't answer your question.

So the answer was to use 'thunk' - an official "async function middleware" for redux.

Also see this to learn more about handling 'async actions' and also using redux thunk: https://redux.js.org/tutorials/fundamentals/part-6-async-logic https://www.npmjs.com/package/redux-thunk

Upvotes: 2

Ruchi Tiwari
Ruchi Tiwari

Reputation: 261

For the other issue you have mentioned TypeError: this.props.fetchDataAction is not a function is because of the curly brackets used while importing fetchRequests

Remove the curly brackets

import fetchRequests from "./actions/fetchData";

This should resolve your issue

Upvotes: 0

karthik rao
karthik rao

Reputation: 121

<button onClick={() => this.props.fetchDataAction()}> 

<button onClick={() => this.props.fetchDataAction}> Here you are trying to return the function definition alone ,not call the fetchDataAction onclick use onClick={() => this.props.fetchDataAction()} or pass a separate handler for the onclick as good practice.

Upvotes: 0

Ruchi Tiwari
Ruchi Tiwari

Reputation: 261

Try this

<button onClick={() => this.props.fetchDataAction()}>

Also if you need to dispatch the url from your components you can do this way

    <button onClick={() => this.props.fetchDataAction(url)}>


    const mapDispatchToProps = (dispatch) => {
    return {
    fetchDataAction: (url) => dispatch(fetchRequests(url))
     };
     };

And in Action.js

   export function fetchRequests(url) {
    axios.get(url)
    .then((res) => {
    responseData = res;
    });
   return responseData;
   }

Upvotes: 0

Related Questions