React and Redux dispatch function problems - reactjs

I am trying to understand how React, Redux and Axios work together but I just hit a wall and I need some help ...
My problem is that inside the action there is a dispatch but after i return the dispatch it does not continue further.
It's most likely that I do not understand how this works so please try to explain in as much as possible details. Thanks in advance.
my combineReducer
import {combineReducers} from "redux";
import getAvailableDatesReducer from "./getAvailableDatesReducer";
export default combineReducers({
availableDates: getAvailableDatesReducer
});
my reducer
import {FETCH_AVAILABLE_DATES} from "../actions/types";
const initialState = {
availableDates: null
};
const getAvailableDatesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_AVAILABLE_DATES:
return {...state, availableDates: action.availableDates};
default:
console.log('just default..');
return state;
}
}
export default getAvailableDatesReducer;
my action
export const fetchAvailableDates = (appointmentKey) => {
//return (dispatch) => {
axios.post('/app_dev.php/termin/getavailability/new', {
appointmentKey: appointmentKey
}).then((response) => {
console.log('response received...');
return (dispatch) => {
console.log('not hitting this...');
dispatch({type: FETCH_AVAILABLE_DATES, availableDates: response.data.availability});
};
}).catch(err => {
console.log(err);
});
//}
}
my component
import {fetchAvailableDates} from "../actions";
const Calendar = (props, appointmentKey) => {
useEffect(() => {
fetchAvailableDates(appointmentKey);
}, []);
const mapStateToProps = (state) => {
return {
availableDates: state.availableDates,
}
}
export default connect(mapStateToProps, {fetchAvailableDates})(Calendar);
my index.js file
import {Provider} from 'react-redux';
import thunk from 'redux-thunk';
import {applyMiddleware, compose, createStore} from "redux";
import reducers from './reducers';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);

First, create an action in your action file:
const fetchDatesAction = (response) => ({
type: FETCH_AVAILABLE_DATES,
availableDates: response.data.availability,
});
Then, update connect
export default connect(mapStateToProps, {fetchDatesAction})(Calendar);
Finally, Call api in useEffect, like this:
useEffect(() => {
axios
.post("/app_dev.php/termin/getavailability/new", {
appointmentKey: appointmentKey,
})
.then((response) => {
console.log("response received...");
return (dispatch) => {
console.log("not hitting this...");
props.fetchDatesAction();
};
})
.catch((err) => {
console.log(err);
});
}, []);

Thunk is a library which is responsible for handling side-effects in state management for redux.
Redux is a simple pure function which accepts state and action as an input and based on these two, it returns a new state. So its pretty simple and straight forward.
Now in certain scenarios like the one you have mentioned in your example, we need to perform some asynchronous actions which may not provide immediate result but a promise. In that case, we need to use a third party tool which is also called as enhancer.
That's the reason why you have added
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
Now when asynchronous action is triggered, it goes to thunk. Thunk processes the request and then triggers one more action which again goes to reducer.
Now reducer being a pure function, does not distinguish between these sources of event and simply update the state based on action and its payload.
Hope this diagram helps you understand the concept.
https://miro.medium.com/max/1400/1*QERgzuzphdQz4e0fNs1CFQ.gif

Related

Try to connect Action with Reducer but get Error: Uncaught (in promise) TypeError: dispatch is not a function

Friends, I have a problem with the connection between Action and Reducer when trying to send data that come back from the API I get dispatch, not a function and when trying to console.log(action.type) I get ##redux/INITw.y.u.w.s.a are any help, and thanks very much
Store
import { createStore, applyMiddleware , compose } from 'redux'
import reduxThunk from 'redux-thunk';
import combineReducers from './Reducers/index';
const enhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
let store = createStore(combineReducers,enhancer(applyMiddleware(reduxThunk)))
export default store;
Action
import { READ_USER_DATA } from './../Types';
export const fetchUser = () => {
return (dispatch) => {
fetch(`https://jsonplaceholder.typicode.com/users`).then(response => response.json()).then(data => {
dispatch({
type: READ_USER_DATA,
data: data
})
})
}
}
Reducer
import { READ_USER_DATA } from './../Types';
const userReducer = (state = {}, action) => {
switch (action.type) {
case READ_USER_DATA:
return { ...state,userInfo: action.data }
default:
return state;
}
}
export default userReducer;
combine Reducers
import { combineReducers } from "redux";
import userReducer from './UserReducer';
export default combineReducers({
user: userReducer,
})
connect - Code in component-
export default connect((state) => {
console.log(state)
return {
userInfo: state.user.userInfo
}
},{ fetchUser })(Home)
I am using the useEffect useEffect(() => ( fetchUser() ), [])
For one, you have to use {}, not () in your useEffect.
Also, you need to call props.fetchUser, not fetchUser directly when you are using the legacy connect function. You should generally not use connect in function components, it is a legacy api to support legacy class components.
So it would look like
useEffect(() => { props.fetchUser() }, [])
But it should even be
const userInfo = useSelector(state => state.user.userInfo)
const dispatch = useDispatch()
useEffect(() => { dispatch(fetchUser()) }, [])
without using connect at all.

Trouble using Redux-Sauce in App? What am I doing wrong?

I am supposed to fetch data from an endpoint and display the results using Redux-Sauce.
All is fine except I can't seem to update the state after I fetch data. Read the docs so this is what I could come up with. Please tell me what I am doing wrong!?
How do I update the state calling the action creators inside HomeContainer.js?
Link to codeSandbox
https://codesandbox.io/s/fragrant-sky-56yhi?file=/src/index.js
HomeContainer.js
import React, { useEffect, useState } from "react";
import axios from "axios";
import { connect } from "react-redux";
import Creators from "../redux/Reducers/reducers";
const HomeContainer = ({ iTunesData, actions }) => {
const { loading, data, error } = iTunesData;
const [searchTerm, setSearchTerm] = useState("");
const submitHandler = (e) => {
e.preventDefault();
const getData = async () => {
actions.fetchDataRequest();
try {
const { data } = await axios.get(
`https://itunes.apple.com/search?term=${searchTerm}`
);
// console.log(data);
actions.fetchDataSuccess(data);
} catch (error) {
actions.fetchDataFail(error);
}
};
getData();
// console.log("On submit handler clicked!");
};
// console.log(iTunesData, actions);
// console.log(searchTerm);
// console.log(iTunesData);
console.log(loading, data, error);
return (
<form onSubmit={submitHandler}>
<h1> Home Container</h1>
<input
placeholder="Search..."
type="text"
onChange={(e) => setSearchTerm(e.target.value)}
/>
<button>Go</button>
</form>
);
};
const mapStateToProps = (state, ownProps) => {
return {
iTunesData: state
};
};
const mapDispatchToProps = (state, ownProps) => {
return {
actions: Creators
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeContainer);
reducer.js
import { createReducer, createActions } from "reduxsauce";
const { Types, Creators } = createActions({
fetchDataRequest: null,
fetchDataSuccess: ["payload"],
fetchDataFail: ["error"]
});
export default Creators;
const initialState = {
loading: false,
data: [],
error: false
};
export const fetchDataRequest = (state = initialState, action) => {
return { ...state, loading: true, data: [], error: false };
};
export const fetchDataSuccess = (state = initialState, action) => {
return { ...state, data: action.payload, error: false };
};
export const fetchDataFail = (state = initialState, action) => {
return { ...state, data: null, error: action.error };
};
// map our action types to our reducer functions
export const HANDLERS = {
[Types.FETCH_DATA_REQUEST]: fetchDataRequest,
[Types.FETCH_DATA_SUCCESS]: fetchDataSuccess,
[Types.FETCH_DATA_FAIL]: fetchDataFail
};
export const reducer = createReducer(initialState, HANDLERS);
store.js
import { applyMiddleware, combineReducers, compose, createStore } from "redux";
import { reducer } from "./Reducers/reducers";
import thunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./redux/store";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
rootElement
);
Your mapDispatchToProps is wrong. Written like you want to use it, it would need to acutally bind dispatch to the actions, which you don't.
If you want to use that nested, you will have to call bindActionCreators manually.
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(Creators, dispatch)
};
};
Otherwise you could also use the "object notation"
const mapDispatchToProps = Creators
in which case the bound action creators will be available as props.fetchDataSuccess, not props.actions.fetchDataSuccess.
Generally, it is also recommended to not use connect at all with function components, but the React-Redux hooks useSelector and useDispatch.
See https://react-redux.js.org/api/hooks
Also, as for your internship, please forward the official Redux Style Guide to your team, with best regards from a Redux Maintainer ;)
https://redux.js.org/style-guide/style-guide/
We really want them to use the official Redux Toolkit, as it will simplify their code a lot more than Redux-Sauce already does - including allowing for immutable logic in reducers thanks to immer integration and containing a full blown api cache abstraction.
Maybe trying that out and prototyping using it might make for a nice internship project for you in the end ;)

react cannot put parameter in function (redux)

rootReducer
import { combineReducers } from "redux";
import mods from "./mods.js";
export default combineReducers({
mods
})
reducers/mods.js
import { GET_MODS, GET_SPECIFC_MOD } from "../actions/types"
const initialState = {
mods: [],
currMod: []
}
export default function(state = initialState, action) {
switch(action.type) {
case GET_MODS:
return {
...state,
mods: action.payload
}
case GET_SPECIFC_MOD:
return {
...state,
currMod: action.payload
}
default:
return state
}
}
actions/mods.js
import axios from 'axios'
import { GET_MODS, GET_SPECIFC_MOD } from './types'
// get the mods
export const getMods = () => dispatch => {
axios.get('http://localhost:8000/api/mods')
.then(res => {
dispatch({
type: GET_MODS,
payload: res.data
})
}).catch(err => console.log(err))
}
// get single mod
export const getSpecificMod = (title) => dispatch => {
axios.get(`http://localhost:8000/api/mods/${title}`)
.then(res => {
dispatch({
type: GET_SPECIFC_MOD,
payload: res.data
})
}).catch(err => console.log(err))
}
components/download.js
import React from 'react'
import { useState, useEffect } from 'react'
import { connect } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = () => {
useEffect(() => {
const title = window.location.pathname.split('/')[3]
getSpecificMod(title)
})
return (
<></>
)
}
const mapStateToProp = state => ({
currMod: state.mods.currMod
})
export default connect(mapStateToProp, getSpecificMod)(Download)
Response from backend
GET http://localhost:8000/api/mods/function(){return!window.__REDUX_DEVTOOLS_EXTENSION_LOCKED__&&a.dispatch.apply(a,arguments)}
Basically the user clicks on a mod and gets sent to the download section that is handled by 'download.js' the component ('download.js') renders it and reads the window.location to retrieve the title, with redux I want to get the mod so i made a function that takes the title and sends the request 'getMod(title)' but for some reason it is throwing horrible errors that I dont understand, any help is appreciated!
You are not dispatching the action properly in your component. Right now you are actually just calling the getSpecificMod action creator function from your imports. Your Download component doesn't read anything from props so it is ignoring everything that gets created by the connect HOC.
If you want to keep using connect, you can fix it like this:
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = ({currMod, getSpecificMod}) => {
const title = window.location.pathname.split('/')[3]
useEffect(() => {
getSpecificMod(title)
}, [title])
return (
<></>
)
}
const mapStateToProps = state => ({
currMod: state.mods.currMod
})
export default connect(mapStateToProps, {getSpecificMod})(Download)
We are now accessing the bound action creator as a prop of the component. mapDispatchToProps is an object which maps the property key to the action.
But it's better to use the useDispatch hook:
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getSpecificMod } from '../actions/mods'
const Download = () => {
const currentMod = useSelector(state => state.mods.currMod);
const dispatch = useDispatch();
const title = window.location.pathname.split('/')[3]
useEffect(() => {
dispatch(getSpecificMod(title));
}, [title, dispatch]);
return (
<></>
)
}
export default Download;
There might be some confusion on terminology here. Your getSpecificMod function is a function which takes dispatch as an argument but it is not a mapDispatchToProps. It is a thunk action creator.
Make sure that you have redux-thunk middleware installed in order to handle this type of action. Or better yet, use redux-toolkit.
Your useEffect hook needs some sort of dependency so that it knows when to run. If you only want it to run once you can use an empty array [] as your dependencies. If you don't specify the dependencies at all then it will re-run on every render.
Does the pathname change? If so, how do you know when? You might want to add an event listener on the window object. Or consider using something like react-router. But that is a separate question.

Redux-Thunk Action setting action as Promise

So I'm using TS React, Redux, and Thunk middleware to handle redux actions that communicate with my api but I cant seem to get the initial configuration for my action function.
My action function is as follows:
export const startSession = ((accessCode: string) => {
return async (dispatch: Dispatch): Promise<Action> => {
try {
const response = await apiCall(accessCode);
return dispatch({ type: SessionActions.START_SESSION, payload: response });
} catch (e) {
console.log('error', e)
}
};
});
I have also tried this:
export const startSession = ((accessCode: string) => {
return async (dispatch: Dispatch) => {
try {
await apiCall(accessCode)
.then(response => dispatch({ type: SessionActions.START_SESSION, payload: response }))
} catch (e) {
console.log('error', e)
}
};
})
but neither seems to work. I thought waiting for the api response would force redux to wait, but it seems to be returning the promise into the state - shown in my redux-logger:
action undefined # 19:10:17.807
redux-logger.js?d665:1 prev state: {some state}
redux-logger.js?d665:1 action: PromiseĀ {<pending>}
And I get the error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
I noticed that this dispatched type is undefined, so there must me a dispatch call being made initially before the data is returned from the api. If anyone could explain to me why it does this, and the standard format for writing actions that use thunk that would be super helpful.
Also please let me know if there is information that I'm missing.
Someone below asked to see how I the initialized store with thunk:
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { createBrowserHistory } from 'history';
import rootReducer from '../_reducers/index'
const loggerMiddleware = createLogger();
export const history = createBrowserHistory();
export const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
);

How to fix 'Actions must be plain objects. Use custom middleware for async actions.'

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">
<img src="/images/love.svg" alt="post like" width="50px" height="50px"/>
<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.
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
)
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 });
};
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 it's boilerplate, like you don't really need getState in there, yes it's part of middleware, but if you are not going to use something, no need to define it. 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.
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);
}

Resources