Reputation:
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
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:
responseData
is analogous to my payload
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
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
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
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