How to apply async react redux middleware - reactjs

I'm a beginner in react.
I'd like to use the react redux to request api.
Error: Actions must be plain objects. Use custom middleware for async actions. An error has occurred.
Please help me with any problems.
I'd like to ask you how redux middleware should be applied.
action/index.js
export const fetchActionMovies = async () => {
const request = await axios.get(`${BASE_URL}/discover/movie?api_key=${API_KEY}&with_genres=28`)
return {
type: FETCH_ACTION_MOVIES,
payload: request
}
}
reducers/reducerActionMovies.js
import { FETCH_ACTION_MOVIES } from '../actions/index';
export default function (state = {}, action) {
switch (action.type) {
case FETCH_ACTION_MOVIES:
const data = action.payload.data.results;
return { ...state, data }
default:
return state;
}
}
container/ActionMovie.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchActionMovies } from '../store/actions/index';
const ActionMovies = () => {
const dispatch = useDispatch();
const fetch = dispatch(fetchActionMovies());
console.log(fetch);
return (
<div>
<h1>Action Movies</h1>
</div>
)
}
export default ActionMovies;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import ReduxThunk from 'redux-thunk';
import rootReducer from './store/reducers';
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(ReduxThunk))
);
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
Error: Actions must be plain objects. Use custom middleware for async actions.

First, even if you're going to use redux-thunk, you need to break up your action into three parts to track the asynchronous state of the request.
const FETCH_ACTION_MOVIES_REQUEST = "FETCH_ACTION_MOVIES_REQUEST";
const FETCH_ACTION_MOVIES_SUCCESS = "FETCH_ACTION_MOVIES_SUCCESS";
const FETCH_ACTION_MOVIES_FAILURE = "FETCH_ACTION_MOVIES_FAILURE";
You should create three actions that use these types that your reducer will track. Now, if you're not going to use redux-thunk... you will need to perform this fetch in your component. However, if you are using redux-thunk you can create an action like this:
export const fetchActionMovies = () => dispatch => {
dispatch(fetchActionMoviesRequest());
return axios.get(`${BASE_URL}/discover/movie?api_key=${API_KEY}&with_genres=28`).then(({
data
}) => {
dispatch(fetchActionMoviesSuccess(data));
}).catch(error => {
dispatch(fetchActionMoviesFailure(error));
})
}

Another option to consider is redux-saga.

Related

Redux saga called multiple times (2 or 3 times) Nextjs

I am using Redux and Redux saga with Nextjs, i wrapped the store on _app.js file and when i call the api with post or get requests Redux-Saga is getting called at least two times, specially for post requests for example if i want to register a user using the api it is registering the user two times on the database
PS: I am using rootSaga and i am not calling a saga twice there
This is my store file:
import { createStore, applyMiddleware, compose } from "redux";
import createSagaMiddleware from "redux-saga";
import reducers from "./reducers";
import sagas from "./sagas";
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];
export function configureStore(initialState) {
const store = createStore(
reducers,
initialState,
compose(applyMiddleware(...middlewares))
);
sagaMiddleware.run(sagas);
if (module.hot) {
module.hot.accept("./reducers", () => {
const nextRootReducer = require("./reducers");
store.replaceReducer(nextRootReducer);
});
}
return store;
}
export const wrapper = createWrapper(configureStore, { debug: true });
And this is my _app.js file
import "../styles/styles.global.scss";
import "../styles/Home.module.scss";
import React from "react";
import App, { Container } from "next/app";
import { Provider, connect } from "react-redux";
import withRedux from "next-redux-wrapper";
import { configureStore, wrapper } from "../libs/store";
const context = React.createContext(undefined);
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { ...pageProps };
}
render() {
const { Component, pageProps, router } = this.props;
return (
<Provider store={configureStore()} context={context}>
<Component {...pageProps} key={router.asPath} />
</Provider>
);
}
}
export default wrapper.withRedux(MyApp);
Thank you.
I Fixed it by removing the provider from _app.js and deleting _document.js
PS: This solutions is for Nextjs >= 10.0.0

Redux Invalid hook call

I am trying to exchange some data between components in my React App and trying to use Redux for the purpose.
I am really looking for simple functionality (storing accesstoken, retrieving accesstoken).
I have one file in folder src/reducers/currenttokenreducer.js:
const currentTokenReducer = (state, action) => {
switch(action.type){
case 'SETTOKEN':
return action.payload
case 'GETTOKEN':
return state
default: return null
}
}
export default currentTokenReducer;
then I have an index.js in src/reducers/:
import currentUserReducer from './currentuser.js'
import currentTokenReducer from'./currenttoken.js'
import {combineReducers} from 'redux'
const allReducers = combineReducers({
currentUserReducer, currentTokenReducer
})
export default allReducers
finally in index.js I have:
import React from 'react';
import b2cauth from 'react-azure-adb2c';
import ReactDOM from 'react-dom';
import jwt from 'jwt-decode'
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {createStore} from 'redux';
import allReducers from './reducers';
import {Provider} from 'react-redux'
const store = createStore(allReducers);
and I guess proper encapsulation of App/ with Provider:
b2cauth.run(() => {
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
serviceWorker.unregister();
In App.js I fire a gettoken function and want to store it both in local state and redux store:
componentDidMount (){
this.gettoken();
}
async gettoken(){
const dispatch = useDispatch();
let apiurl = 'https://get.....azurewebsites.net……'
var token = await Axios.get(apiurl,{headers: { 'Content-Type': 'application/x-www-form-urlencoded' }});
this.setState({accesstoken: token.data});
dispatch (settoken(token.data));
}
settoken is defined in src/actions/:
export const settoken = (token) => {
return {
type: 'SETTOKEN',
payload: token
};
};
When I deploy it I get:
Unhandled Rejection (Error): Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See ... for tips about how to debug and fix this problem.
And it points to this line in App.js:
const dispatch = useDispatch();
What am I doing wrong?
You can't use a hook inside a class component
Please read the Rules of Hooks documentation
Call Hooks from React function components.
BTW, you don't need to have a GETTOKEN action in your reducer because the token is already stored into the store.
const defaultState = {value: null};
const currentTokenReducer = (state = defaultState , action) => {
switch(action.type){
case 'SETTOKEN':
return {...state, value: action.payload};
default:
return state;
}
}
export default currentTokenReducer;
Then you don't need to create a internal state inside your component because you will retrieve the token from the store
import React, {useEffect} from 'react';
import { useSelector, useDispatch } from 'react-redux';
import axios from 'axios';
function AppComponent() {
const dispatch = useDispatch();
const token = useSelector(state => state.token.value);
useEffect(async () => {
const apiurl = 'https://get.....azurewebsites.net……';
const response = await axios.get(apiurl, ...);
dispatch({type: 'SETTOKEN', payload: response.data});
}, []);
return <div>{token}</div>;
}
In this example I used the hooks useDispatch and useSelector, you can find more information on theses hooks on the react-redux documentation
Would it be possible that you're trying to use a hook (useDispatch()) inside a Class? Because hooks don't work inside classes (see: https://reactjs.org/docs/hooks-overview.html#but-what-is-a-hook).
You can still get dispatch from your props with a good old connect(mapStateToProps)(App). (see: https://react-redux.js.org/using-react-redux/connect-mapdispatch#default-dispatch-as-a-prop)

How to fix Uncaught TypeError: Cannot read property 'getState' of undefined?

I am trying to use React with Redux for the frontend part with django rest framework in the backend. Got the issue getState in Provider tag in App component because of issue in store. And when i try to use the map function in the Words.js, I get error of undefined use of map. And I believe this is because of value of the array is null. Hence to fixed this error of getState.
Got this error even on including the store in Provider of App component when a reducers was not defined.
When I load a static array it does get rendered properly in the specific component.
This is Redux Store in the filename:store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers'
const initialState = {};
const middleware = [thunk];
const enhancer = composeWithDevTools(applyMiddleware(...middleware));
const store = createStore(
rootReducer,
initialState,
enhancer
);
export default store;
The index.js file is below
import App from './components/App'
import ReactDOM from 'react-dom';
import React from 'react';
ReactDOM.render(<App />, document.getElementById("app"));
They action types file types.js using django rest_framework to create the data.
export const GET_WORDS = "GET_WORDS";
The action file words.js
import { GET_WORDS } from "./types";
import axios from 'axios';
export const getWords = () => dispatch => {
axios.get('/api/words/')
.then(res => {
dispatch({
type: GET_WORDS,
payload: res.data
});
}).catch(err => console.log(err));
}
combined reducer file
import { combineReducers } from "redux";
import words from './words';
export default combineReducers({
words
});
The reducer file word.js
import { GET_WORDS } from '../actions/types';[enter image description here][1]
const initialState = {
words: []
}
export default function (state = initialState, action) {
switch (action.type) {
case GET_WORDS:
return {
...state,
words: action.payload
}
default:
return state;
}
}
The Component in which the words list will be called: Words.js
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getWords } from "../../../actions/words";
export class Words extends Component {
static propTypes = {
words: PropTypes.array.isRequired,
getWords: PropTypes.func.isRequired
};
componentDidMount() {
this.props.getWords();
}
render() {
return (
<Fragment>
Hi
</Fragment>
)
}
}
const mapStateToProps = state => ({
words: state.words.words
});
export default connect(mapStateToProps, { getWords })(Words);
And finally the App component
import React, { Component, Fragment } from 'react';
import Footer from './Layout/Footer/Footer';
import Header from './Layout/Header/Header';
import WordsDashboard from './Content/Words/WordsDashboard';
import { store } from '../store';
import { Provider } from "react-redux";
import { Words } from './Content/Words/Words';
export class App extends Component {
render() {
return (
<Provider store={store}>
<Fragment>
<Header />
React Buddy
<Words />
<Footer />
</Fragment>
</Provider>
)
}
}
export default App;
Your initialState has only words prop, so when mapping it to props you have one extra words level. Try changing it to:
const mapStateToProps = state => ({
words: state.words
});
Also you need to use mapDispatchToProps for getWords, since in your current code you're missing dispatch wrapper:
const mapDispatchToProps = dispatch => ({
getWords: () => dispatch(getWords())
})
export default connect(mapStateToProps, mapDispatchToProps)(Words);

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

I am using redux-thunk/ReactJs for async actions and i am getting error like this Error: Actions must be plain objects. Use custom middleware for async actions.
But i have installed redux-thunk and have configured it in index.js file
Here is my index.js file :
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import myReducer from './reducers/index';
const store = createStore(
myReducer,
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
registerServiceWorker();
and this is my 'action.js' file :
import * as types from './../constants/ActionTypes';
import callApi from '../utils/callApi';
export const actFetchProductsRequest = () => {
return dispatch => {
return callApi('products', 'GET', null).then(res => {
dispatch(actFetchProducts(res.data))
}).catch(err => console.log(err))
}
}
export const actFetchProducts = (products) => {
return {
type: types.LIST_ALL,
products
}
}
What is the problem here , thanks ?
Try this:
export const actFetchProducts = (products) => {
return ({
type: types.LIST_ALL,
products
})
}
You need to put the return object in the parentheses. If you don't, then you'll definitely get this error.
Hopefully, that helps!

How to use .dispatch in react redux?

I have the following
CatsPage.js:
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
//import * as catActions from '../../actions/catActions';
import CatList from './CatList';
import {loadCats} from '../../actions/catActions';
class CatsPage extends React.Component {
componentDidMount () {
this.props.dispatch(loadCats())
}
render() {
return (
<div>
<h1>Cats</h1>
<div>
<CatList cats={this.props.cats} />
</div>
</div>
);
}
}
CatsPage.propTypes = {
cats: PropTypes.array.isRequired
};
function mapStateToProps(state, ownProps) {
return {
cats: state.cats
};
}
export default connect(mapStateToProps)(CatsPage);
catActions.js
import * as types from './actionTypes';
import catApi from '../api/CatsApi';
export function loadCats() {
return function(dispatch) {
return catApi.getAllCats().then(cats => {
dispatch(loadCatsSuccess(cats));
}).catch(error => {
throw(error);
});
};
}
export function loadCatsSuccess(cats) {
return {type: types.LOAD_CATS_SUCCESS, cats};
}
I'm getting the following error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. at dispatch (createStore.js:166)
I'm a newbie try to learn how to use React + Redux. What am I doing wrong that I need to fix to make the dispatch work and loadCats()?
Thank you!
There's a chance you didn't properly install/configure your store. It should look something like this:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);

Resources