Why am i getting "Error: Actions must be plain objects. Use custom middleware for async actions." error? - redux

I am continuously getting " Actions must be plain objects. Use custom middleware for async actions." error and I am totally stuck here. What am I doing wrong here please help me figure out and help me get out of this error.
This is my index.js file where I have integrated redux store to the app.
import "babel-polyfill";
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux'
import { composeWithDevTools } from 'redux-devtools-extension';
import { createStore, applyMiddleware, combineReducers, compose} from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootSaga from './sagas'
import { postsReducer } from './reducers/posts'
import Routes from './routes';
import './styles/style.css'
const rootReducer = combineReducers({ postsReducer })
const sagaMiddleware = createSagaMiddleware()
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))
sagaMiddleware.run(rootSaga)
ReactDOM.render((
<Provider store={store}>
<Router><Routes /></Router>
</Provider>
), document.getElementById('root'))
this is my saga.js
import { take, put, call, fork, select, takeEvery, all, takeLatest } from 'redux-saga/effects'
import PostApi from './api/postApi';
import { gotPosts } from './actions/celebrity';
import { POSTS } from '../types'
export function* getAllPosts () {
const posts = yield call(PostApi.getPosts, {})
console.log('postssss', posts)
yield put(gotPosts(posts.data))
}
export function* watchGetPosts () {
yield takeLatest(POSTS, getAllPosts)
}
export default function* root() {
yield all([ fork(watchGetPosts) ])
}
this is my action.js
import { POSTS } from '../../types';
export const gotPosts = (data) => {
return {
type: POSTS,
data,
}
}
export const getPosts = () => dispatch => {
dispatch(gotPosts);
}
this is component page where i dispatched action.
import React, { Component } from 'react';
import { Card, Row, Col } from 'antd';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux'
import { getPosts } from '../actions/celebrity';
const { Meta } = Card;
class MainPage extends Component {
componentDidMount () {
console.log(this.props)
this.props.getPosts();
}
render() {
return <Row type="flex" className="main" justify="center" align="between">
......
</Row>
}
}
const mapStateToProps = state => {
return {
posts: state.postsReducer
}
}
const mapDispatchToProps = dispatch => ({
getPosts: () => {
dispatch(getPosts());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(MainPage);
postsReducer
export const postsReducer = (state = [], action) => {
console.log(action)
switch(action.type){
case POSTS:
return action.data;
default:
return state;
}
}

You can't dispatch function w/o middleware support.
Problem originates from mapDispatchToProps:
{
getPosts: () => { dispatch(getPosts()); }
}
tracing down to your actions.js, getPosts() returns dispatch => dispatch(gotPosts), which is actually a function not an action(plan javascript object), redux dispatch by default doesn't recognize functions, unless you use middleware to enhance it, redux thunk for example.
Since you already have redux saga for async flow, simply dispatch an action from mapDispatchToProps should be fine, also consider create separate actions to differentiate POSTS_REQUEST, POSTS_RECEIVE, POSTS_FAILURE if possible.
import {POST} from '....../actionTypes'
...
{
getPosts: () => { dispatch({ type: POST }); }
}

Related

getServerSideProps not initialising client side state (next-redux-wrapper, redux/toolkit, redux-saga) Next.JS

I am doing a NextJS project using next-redux-wrapper, redux/toolkit and redux-saga.
This is my setup:
store/enquiry/slice.ts:
import { PayloadAction, createSlice } from '#reduxjs/toolkit'
import { HYDRATE } from 'next-redux-wrapper';
export enum LoadingTracker {
NotStarted,
Loading,
Success,
Failed
}
type EnquiryState = {
enquiries: Enquiry[],
loadingTracker: LoadingTracker
}
const initialState: EnquiryState = {
enquiries: [],
loadingTracker: LoadingTracker.NotStarted
}
export const enquirySlice = createSlice({
name: 'enquiries',
initialState,
reducers: {
//#ts-ignore
loadEnquiries: (state, action: PayloadAction<Enquiry[]>) => {
state.enquiries = action.payload;
state.loadingTracker = LoadingTracker.Success
},
failedLoadEnquiries: (state) => {
state.loadingTracker = LoadingTracker.Failed;
},
startedLoadingEnquiries: (state) => {
state.loadingTracker = LoadingTracker.Loading;
}
},
extraReducers:{
[HYDRATE]:(state, action) => {
return{
...state,
...action.payload.enquiry
};
}
},
});
export const { loadEnquiries, failedLoadEnquiries, startedLoadingEnquiries } =
enquirySlice.actions;
export default enquirySlice.reducer;
This is my enquiry saga setup: sagas/enquiry/saga.ts
import { call, put, takeLatest } from "redux-saga/effects";
import { loadEnquiries, startedLoadingEnquiries, failedLoadEnquiries } from
"../../store/enquiry/slice";
export function* loadEnquiriesSaga() {
try {
//#ts-ignore
let response = yield call(() =>
fetch("https://xxxxx686888wwg326et2.mockapi.io/enqueries")
);
if (response.ok) {
//#ts-ignore
let result = yield call(() => response.json());
yield put(loadEnquiries(result));
} else {
yield put(failedLoadEnquiries());
}
} catch (e) {
yield put(failedLoadEnquiries());
}
}
export function* loadEnquiriesWatcher() {
const { type } = startedLoadingEnquiries();
yield takeLatest(type, loadEnquiriesSaga);
}
This is my root saga setup: sagas/index.js
import { all } from 'redux-saga/effects';
import { loadEnquiriesWatcher } from './enquiry/saga';
export default function* rootSaga() {
yield all([
loadEnquiriesWatcher()
])
}
this is my store setup: store/index.js
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import createSagaMiddleware from "redux-saga";
import enquiryReducer from '../store/enquiry/slice';
import { createWrapper } from 'next-redux-wrapper';
import rootSaga from '../sagas/index';
const makeStore = () => {
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: {
enquiry: enquiryReducer,
},
middleware: [...getDefaultMiddleware({ thunk: false }), sagaMiddleware],
devTools: true
})
//#ts-ignore
store.sagaTask = sagaMiddleware.run(rootSaga);
return store;
}
const store = makeStore();
export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const useAppDispatch: () => AppDispatch = useDispatch
This is _app.ts file
import React, { useEffect } from "react";
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import { Provider } from 'react-redux'
import { wrapper } from '../store/index';
const App = ({ Component, ...rest }: AppProps) => {
const [queryClient] = React.useState(() => new QueryClient());
const { store, props } = wrapper.useWrappedStore(rest);
const { pageProps } = props;
return (
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</Provider>
);
}
export default App;
I am running a getServersideProps function in pages/index.js file.
import Head from 'next/head'
import { Inter } from '#next/font/google'
import utilStyles from '../styles/Utils.module.css';
import Layout from '../components/layout';
import { wrapper } from '../store';
import { startUserLogin } from '../store/user/slice';
import { END } from 'redux-saga';
import { GetServerSideProps } from 'next';
import { startedLoadingEnquiries } from '../store/enquiry/slice';
const inter = Inter({ subsets: ['latin'] })
export const getServerSideProps: GetServerSideProps = wrapper.getServerSideProps(
//#ts-ignore
(store) => async ({ req }) => {
await store.dispatch(startedLoadingEnquiries());
store.dispatch(END);
await (store as any).sagaTask.toPromise();
return {
props:{
status:1
}
}
})
export default function Home(props:any) {
return (
<Layout home>
<Head>
<title>Holiday Experts</title>
</Head>
<section className={utilStyles.headingMd}>
<p>Introduction to Holiday Exers blah blah blah............. status:
{props.status}</p>
</section>
<section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
<h2 className={utilStyles.headingLg}>To Do: List of Top Destinatons as
cards</h2>
</section>
</Layout>
)
}
When I got to the home page localhost:3000/, I see all the state values are populated. I go to localhost:3000/enquiries and i see a list of enquiries as well, but when I refresh, the list is empty because, client side state is not set. If I open Redux dev tools, I see enquiry state to be empty all the time.
I think there is something missing during the Hydration of the state. Not sure what it is.Can somebody help me with it?
Here are my references that I followed:
https://romandatsiuk.com/en/blog/post-next-redux-saga/
https://github.com/kirill-konshin/next-redux-wrapper

Error: Cannot read properties of undefined (reading 'getState')

I am getting the error "Error: Cannot read properties of undefined (reading 'getState')"
Not sure what is the issue but I have followed all instructions. I do not resolve it at all after searching everything. My app is in next.js and I want to add redux into it. Here is my code
/* pages/_app.js */
import App from "next/app"
import { Provider } from "react-redux"
import React from "react"
import { createWrapper } from "next-redux-wrapper"
import store from "../store/store"
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx)
: {}
//Anything returned here can be accessed by the client
return { pageProps: pageProps }
}
render() {
//pageProps that were returned from 'getInitialProps' are stored in the props i.e. pageprops
const { Component, pageProps, store } = this.props
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
}
//makeStore function that returns a new store for every request
const makeStore = () => store
const wrapper = createWrapper(makeStore)
export default wrapper.withRedux(MyApp)
//store/store.js
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"
import rootReducer from "../reducers/rootReducer"
import { composeWithDevTools } from "redux-devtools-extension"
const initialState = { counter: 0 }
const middleware = [thunk]
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(
applyMiddleware(...middleware)
// other store enhancers if any
)
)
export default store
///reducers/rootReducer.js
import counterReducer from "./counterReducer"
import { combineReducers } from "redux"
const rootReducer = combineReducers({
counter: counterReducer,
})
export default rootReducer
//counterReducer.js //
const counterReducer = (state = { value: 0 }, action) => {
return { ...state }
}
export default counterReducer

I have successfully implemented the redux-persist with next-redux-wrapper in next js

Im getting data from the external api and storing it in the reducer.And im using redux-persist to persist the state while navigating from one page to another.But i have made left the whiteList as an empty array but all the state are being persisted?Need help
import "../assets/css/style.scss";
import "owl.carousel/dist/assets/owl.carousel.css";
import "owl.carousel/dist/assets/owl.theme.default.css";
import Layout from "../component/Layout/Layout";
import { wrapper } from "../redux/store";
import { useEffect } from "react";
import { useStore } from "react-redux";
function MyApp({ Component, pageProps }) {
const store = useStore((store) => store);
useEffect(() => {
{
typeof document !== undefined
? require("bootstrap/dist/js/bootstrap.bundle")
: null;
}
}, []);
return (
<Layout>
<Component {...pageProps} />;
</Layout>
);
}
export default wrapper.withRedux(MyApp);
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import rootReducer from "./index";
import { createWrapper, HYDRATE } from "next-redux-wrapper";
const middleware = [thunk];
let initialState={}
// BINDING MIDDLEWARE
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== "production") {
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const makeStore = ({ isServer }) => {
if (isServer) {
//If it's on server side, create a store
return createStore(rootReducer,initialState, bindMiddleware(middleware));
} else {
//If it's on client side, create a store which will persis
const persistConfig = {
key: "root",
storage: storage,
whiteList: [],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer,initialState, bindMiddleware(middleware));
store.__persisitor = persistStore(store); // This creates a persistor object & push that
persisted object to .__persistor, so that we can avail the persistability feature
return store;
}
};
// export an assembled wrapper
export const wrapper = createWrapper(makeStore);
If you keep the whitelist an empty array then nothing will be persisted.
You have to put inside that string array the redux reducers values you want to be persisted.
Example:
const persistConfig = {
key: 'root',
storage: storage,
whiteList: ['cart', 'form', 'user'],
};

Why this error is coming TypeError: Cannot read property 'type' of undefined

How to solve this error TypeError: Cannot read property 'type' of undefined.
I am very new to react-redux and redux, simply I am trying to do state management through redux.
For this I installed react-redux and redux npm packages. And I created a store, In store a have reducer.js file. Even I imported Provider and store in index.js. Help to resolve this issue.
In store folder I have reducer.js file
This is reducer.js file code
const initialState = {
age: 21
}
const reducer = (state = initialState, action) => {
const newState = {state};
if(action.type === 'AGE_UP') {
newState.age++
}
if(action.type === 'AGE_DOWN') {
newState.age--
}
return newState;
};
export default reducer
This is App.js
import React, { Component } from 'react';
import './App.css'
import { connect } from 'react-redux'
class App extends Component {
render() {
return (
<div className='App'>
<div>Age: <span>{this.props.age}</span></div>
<button onClick={this.props.onAgeUp}>Age UP</button>
<button onClick={this.props.onAgeDown}>Age Down</button>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
age:state.age
}
}
const mapDispatchToProps = (dispatch) => {
return {
onAgeUp: () => dispatch({type: 'AGE_UP'}),
onAgeDown: () => dispatch({type: 'AGE_DOWN'})
}
}
// export default connect()(App)
export default connect(mapStateToProps,mapDispatchToProps) (App)
This is index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reducer from './store/reducer';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import * as serviceWorker from './serviceWorker';
const store = createStore({}, reducer);
ReactDOM.render(<Provider projectStore={store}><App></App></Provider>, document.getElementById('root'));
serviceWorker.unregister();
I can see multiple issues in your code:
createStore takes reducer as the first argument, look at API documentation, try this:
const store = createStore(reducer);
Provider has prop store and not projectStore, look at API documentation, try this:
ReactDOM.render(<Provider store={store}><App></App></Provider>, document.getElementById('root'));
You probably mutate your state in a reducer, try this:
const reducer = (state = initialState, action) => {
// it is better to use switch instead of if in reducer, for sake of readability
switch (action.type) {
case 'AGE_UP':
// it is better to have return statement here, it is more robust to developer errors
// it is better to use object spread and return result here than creating `newState` variable, you will do less errors
return {...state, age: state.age + 1}
case 'AGE_DOWN':
return {...state, age: state.age - 1}
}
return state;
};

Action.type undefined redux

No matter what I do, I can't get rid of the mistake. I have often rewritten the actions but the error remains. I also wrote thunk at the top of the createstore. It would be great if you could support me a little bit.
My action, nothing special here only a fetch call to get my players
import fetch from "cross-fetch"
export const SET_PLAYERS = "setplayers"
export const setPlayers = players => {
return{
type: "setplayers",
players
}
}
export const fetchPlayers = () => (dispatch, getState) => {
return fetch("http://localhost:4444/api/players")
.then(response => response.json())
.then(players => {
dispatch(setPlayers(players))
}).catch(err => {
console.log("Could not fetch assortments" , err)
})
}
Component, at this point in time only a dummy to invoke the action:
import React from "react"
import PropTypes from "prop-types"
import { fetchPlayers } from "./action"
import { connect } from "react-redux"
import EnhancedTable from "../components/list/List"
import getPlayers from "./reducer"
class PlayerTable extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
componentDidMount(){
this.props.fetchPlayers()
}
render() {
console.log("#######", this.props.players)
return (
<EnhancedTable />
)
}
}
PlayerTable.propTypes = {
classes: PropTypes.object.isRequired,
}
const mapStateToProps = state => ({
players: getPlayers(state)
})
export default connect(mapStateToProps, { fetchPlayers })(PlayerTable)
Reducer
import { SET_PLAYERS } from "./action"
const setPlayers = (state={}, action) => {
console.log("ACTION", action)
switch (action.type) {
case SET_PLAYERS:
return {...state, players: action.players}
default:
return state
}
}
export default setPlayers
export const getPlayers = state => ([])
CombinedReducers
import { combineReducers } from "redux"
import { reducer as formReducer } from "redux-form"
import showProgressbar from "../components/progressbar/reducer"
import showSnackBar from "../components/snackbar/reducer"
import setPlayers from "../player/reducer"
export default combineReducers({
form: formReducer,
showProgressbar,
showSnackBar,
setPlayers
})
CreateStore
import App from "./App"
import React from "react"
import rootReducer from "./reducers"
import thunk from "redux-thunk"
import { render } from "react-dom"
import { createStore, applyMiddleware, compose } from "redux"
import { Provider } from "react-redux"
import { createLogger } from "redux-logger"
const store = createStore(
rootReducer,
compose(applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
))
render(
<Provider store={store}>
<App />
</Provider>,
/* eslint-disable*/
document.getElementById("root")
/* eslint-enable */
)
You've defined mapStateToProps properly, but don't you need to do the same with mapDispatchToProps for the second argument to connect()?
const mapStateToProps = state => ({
players: getPlayers(state)
})
const mapDispatchToProps = dispatch => ({
fetchPlayers() {
dispatch(fetchPlayers())
}
})
export default connect(mapStateToProps, mapDispatchToProps)(PlayerTable)

Resources