React-Redux, why is action null in a reducer - reactjs

Given:
//Reducers/ScriptReaderReducers.js
let initialState = {};
const sceneReaderReducers = (state = initialState, action => { //eslint-disable-line
switch (action.type) {
case ScriptActions.MOVE_NEXT: {
return {
...state, //eslint-disable-line
currentIndex: action.currentIndex
};
}
I've mapped this into an app that appears to be otherwise working:
import { createHashHistory } from 'history';
import { applyMiddleware, combineReducers, createStore} from 'redux';
import { connectRouter, routerMiddleware } from 'connected-react-router';
import createSagaMiddleware from 'redux-saga';
import SceneReducer from './Logic/Reducers/ScriptReaderReducers.js';
import ScriptSagas from './Logic/Sagas/ScriptSagas';
const sagaMiddleware = createSagaMiddleware();
// Start history
const history = createHashHistory({});
//Merge Reducers
let rootReducer = combineReducers({
SceneReducer
});
// Merge middlewares
let middlewares = [
routerMiddleware(history),
sagaMiddleware
];
// Development adds logging, must be last
if ( process.env.NODE_ENV !== 'production') {
middlewares.push( require('redux-logger')({
// Change this configuration to your liking
duration: true, collapsed: true
}) );
}
// Generate store
const store = createStore(connectRouter(history)(rootReducer),
{},
applyMiddleware(...middlewares)
);
console.error(store); //eslint-disable-line
sagaMiddleware.run(ScriptSagas);
// Export all the separate modules
export {
history,
store
};
On starting up this app in dev, I receive an error message: ScriptReaderReducers.js:9 Uncaught TypeError: Cannot read property 'type' of undefined. This appears to be the first test of the switch on action.
I've proven to my satisfaction that actions are being dispatched but I can't seem to get a call stack for the exact timing on this error.
Why would action be null within the Reducers?

As jmargolisvt said, you missed () in your code.
Please change line const sceneReaderReducers = (state = initialState, action => { to const sceneReaderReducers = ((state = initialState, action) => { and try again.

Related

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

I am having this error in react-redux. I don't know how to solve it. I wanted to send a param which is taken from an api to another api and fetch results.
This is my store
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import rootReducer from "./Reducers/rootReducer";
const store = createStore(
rootReducer,
{},
compose(applyMiddleware(thunk), composeWithDevTools())
);
export default store;
This is my reducer code:
import * as types from "../Actions/types";
const initialState = {
posts: [],
table: [],
};
const postReducer = (state = initialState, action) => {
switch (action.type) {
case types.FETCH_DATA:
return {
...state,
posts: action.payload,
};
case types.FETCH_TABLE:
return {
...state,
table: action.payload,
};
default:
return {
...state,
};
}
};
export default postReducer;
This is my action code
export const getData = (from_userpart) => async (dispatch) => {
try {
const { data } = await api.getData(from_userpart);
dispatch({
type: types.FETCH_TABLE,
payload: data,
});
} catch (error) {
console.log(error);
}
};
When i use this code on the parent component,it works but i want to use that in my child component but it gives this error as i mentioned on the label. How to solve that?
useEffect(() => {
dispatch(getData(phone));
}, [dispatch]);
Change the setting of your store, your enhancer should come second.
let composeEnhancers = null
if (process.env.NODE_ENV === 'development') {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
} else {
composeEnhancers = compose
}
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(thunk))
);
I've noticed that i had imported my function from api folder not the action one. It works now

Redux does not update state even if reducer is triggered

I am trying to use redux for react.
i have action creator:
export const API_PATH_UPDATED = 'query/apiPathUpdated';
export const apiPathUpdated = (apiPath) => ({
type: API_PATH_UPDATED,
payload: {
apiPath,
},
});
This action creator is called in this function:
const getUpcoming = useCallback(
() => dispatch(apiPathUpdated('/movie/upcoming')),
[dispatch],
);
And should update my state with the following reducer:
const initialState = {
basePath: 'https://api.themoviedb.org/3',
apiPath: '/movie/popular',
lang: 'en-US',
adult: false,
genresPath: '/genre/movie/list',
};
const queryReducer = (state = initialState, action) => {
switch (action.type) {
case API_PATH_UPDATED: {
return {
...state,
apiPath: action.payload.apiPath,
};
}
default:
return state;
}
};
export default queryReducer;
And actually when i click on button that triggers getUpcoming function in Redux devtools i see that action is treggered with right argiments:
enter image description here
But it doesn't update state and i see the old apiPath value:
enter image description here
Here is the code for my store:
import {
createStore, applyMiddleware, compose, combineReducers,
} from 'redux';
import camelMiddleware from 'redux-camel';
import thunk from 'redux-thunk';
import moviesReducer from './movies/index';
import queryReducer from './query/index';
import genresReducer from './genres/index';
const rootReducer = combineReducers({
query: queryReducer,
movies: moviesReducer,
genres: genresReducer,
});
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
trace: true, traceLimit: 25,
}) || compose;
const store = createStore(rootReducer, composeEnhancers(
applyMiddleware(thunk, camelMiddleware()),
));
export default store;
Thanks everyone for help.
I found my issue. I forgot to put curly braces around named import in my queryReducer file.
so the line
import API_PATH_UPDATED from './queryActions';
should be
import { API_PATH_UPDATED } from './queryActions';
That what happens when coding at 5a.m. :/

React: integrating a redux reducer to dandelion-pro project

end developer and recently I started to learn front-end. I have troubles with adding some new data to redux store. I am working with dandelion-pro react template and can't figure out how to add my reducers to their stores, it seems much more complex then redux stores I have build for other projects, also I observed they used redux saga. I am trying to introduce a global state for user data on login.
Here is code for my reducer
import { CallToAction } from '#material-ui/icons';
import { SUCCESSFUL_LOGIN, FETCH_LOGIN, ERROR_LOGIN } from '../../actions/actionConstants';
const initialState = {
auth: false,
isLoading: false,
errMess: null,
isAdmin: false,
token: ''
}
export default function userReducer (state = initialState, action) {
console.log("Action: ")
console.log(action)
switch (action.type) {
case SUCCESSFUL_LOGIN: return {
...state,
auth: true,
isLoading: false,
errMess: null,
isAdmin: action.payload.isAdmin,
token: action.payload.token
}
case FETCH_LOGIN: return {
...state,
auth: false,
isLoading: true,
errMess: null
}
case ERROR_LOGIN: return {
...state,
auth: false,
isLoading: false,
errMess: action.payload
}
default: return state
}
}
Code for fetch user data
import { SUCCESSFUL_LOGIN, FETCH_LOGIN, ERROR_LOGIN } from '../../actions/actionConstants';
import axios from 'axios';
import { server } from '../../config'
export const fetchUser = (username, password) => (dispatch) => {
console.log("a ajuns")
dispatch(loginLoading(true));
axios.post(`${server + "/auth/login"}`, { username, password })
.then(res => {
const user = res.data;
console.log(user);
if (user.status) {
window.location.href = '/app';
return dispatch(loginUser(user));
}
else {
var errmess = new Error("False Status of User");
throw errmess;
}
})
.catch(error => dispatch(loginFailed(error.message)))
}
export const loginLoading = () => ({
type: FETCH_LOGIN
});
export const loginFailed = (errmess) => {
return ({
type: ERROR_LOGIN,
payload: errmess
})
};
export const loginUser = (user) => ({
type: SUCCESSFUL_LOGIN,
payload: user
})
Section that combine reducers
/**
* Combine all reducers in this file and export the combined reducers.
*/
import { reducer as form } from 'redux-form/immutable';
import { combineReducers } from 'redux-immutable';
import { connectRouter } from 'connected-react-router/immutable';
import history from 'utils/history';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
import login from './modules/login';
import uiReducer from './modules/ui';
import initval from './modules/initForm';
import user from '../my_redux/modules/initForm';
/**
* Creates the main reducer with the dynamically injected ones
*/
export default function createReducer(injectedReducers = {}) {
const rootReducer = combineReducers({
user,
form,
login,
ui: uiReducer,
initval,
language: languageProviderReducer,
router: connectRouter(history),
...injectedReducers,
});
// Wrap the root reducer and return a new root reducer with router state
const mergeWithRouterState = connectRouter(history);
return mergeWithRouterState(rootReducer);
}
I try to connect my Login component like this
const mapStateToProps = state => ({
user: state.user
});
const mapDispatchToProps = dispatch => ({
fetchUser: (username, password) => dispatch(fetchUser(username, password))
});
// const mapDispatchToProps = dispatch => ({
// actions: bindActionCreators(userActions, dispatch),
// });
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(Login));
The store is created here
/**
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'connected-react-router';
import { fromJS } from 'immutable';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
export default function configureStore(initialState = {}, history) {
let composeEnhancers = compose;
const reduxSagaMonitorOptions = {};
// If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production' && typeof window === 'object') {
/* eslint-disable no-underscore-dangle */
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
// NOTE: Uncomment the code below to restore support for Redux Saga
// Dev Tools once it supports redux-saga version 1.x.x
// if (window.__SAGA_MONITOR_EXTENSION__)
// reduxSagaMonitorOptions = {
// sagaMonitor: window.__SAGA_MONITOR_EXTENSION__,
// };
/* eslint-enable */
}
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [sagaMiddleware, routerMiddleware(history)];
const enhancers = [applyMiddleware(...middlewares)];
const store = createStore(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers),
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.injectedReducers = {}; // Reducer registry
store.injectedSagas = {}; // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers));
});
}
return store;
}
on login form submit I call this.props.fetchUser("admin", "admin"); but I get the following error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
at dispatch (redux.js:198)
at eval (middleware.js:29)
at eval (redux-saga-core.dev.cjs.js:1412)
at Object.fetchUser (Login.js?f3c5:66)
at Login.submitForm (Login.js?f3c5:30)
at onSubmit (Login.js?f3c5:49)
at executeSubmit (handleSubmit.js?e3b3:39)
at handleSubmit (handleSubmit.js?e3b3:131)
at Form._this.submit (createReduxForm.js?d100:362)
at HTMLUnknownElement.callCallback (react-dom.development.js:149)
I reviewed my answer, and update it according to your question update
The syntax you use for defining async function is called a thunk a fancy name for a function that return a promise (or async function), anyway to use that pattern in code you need a library called redux-thunk
To apply the redux-thunk middle ware for your application,
npm install redux-thunk
then apply the middleware in your app store
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));
example from official repo of redux-thunk
and for your code just add the thunk imported from redux-thunk in middleware array
import thunk from 'redux-thunk';
const middlewares = [sagaMiddleware, routerMiddleware(history), thunk];
Now for Saga
you need to have a root saga that run others sagas, and run the root saga from the created saga middleware
here're the steps:
1- create saga middleware(just like how you did, but we need to run the root saga from there too)
import createSagaMiddleware from 'redux-saga'
const sagaMiddleware = createSagaMiddleware();
// after you've created the store then run the root saga
sagaMiddleware.run(rootSagas);
2- create your rootSaga
export function* rootSagas() {
try {
yield fork(fetchUsersSaga);
} catch (error) {
console.warn(error);
}
}
3- create your fetch user saga
import { take, put, call } from "redux-saga/effects";
export function* fetchUsersSaga() {
while (true) {
const action: FetchUser = yield take(FETCH_USER);
try {
const response = yield call(usersService.fetchUsersOfProject, { ...paramsPassedToFetchUserFunction })
if (response) {
const { data: { response: { user } } } = response;
yield put(setUser({ user }));
}
} catch (error) {
yield put(fetchUser());
}
}
}
now you need to notice the big difference between saga and thunk, for thunk you write an action that is hard coded to do one thing(or multiple but it still for a more specific case) and in saga you listen for what ever action the store has dispatched and react to that action in generator code style

redux-persist got error: Store does not have a valid reducer

I got an error when using redux-persist. I could find few documents about redux-persist v5. And I just follow the official usage example. But I am confused about this. Before I use redux-persist, I can get state from store correctly. But I want to keep login state in local. So I try to use redux-persist. Then I got some problems. Here is my code:
reducer.js
const initialState = {
isLogin: false,
uname: "",
}
const userReducer = (state = initialState, action) => {
switch(action.type) {
case 'DO_LOGIN':
return Object.assign({}, state, {
isLogin: true,
uname: action.payload.username
})
default:
return state
}
}
const reducers = combineReducers({
userInfo: userReducer
})
export default reducers
store.js
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger'
import { createStore, applyMiddleware, compose } from 'redux'
import { persistStore, persistCombineReducers } from 'redux-persist'
import storage from 'redux-persist/es/storage'
import reducers from '../reducers'
const loggerMiddleware = createLogger()
const middleware = [thunk, loggerMiddleware]
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const configureStore = composeEnhancers(
applyMiddleware(...middleware),
)(createStore)
const config = {
key: 'root',
version: 1,
storage,
}
const combinedReducer = persistCombineReducers(config, reducers)
const createAppStore = () => {
let store = configureStore(combinedReducer)
let persistor = persistStore(store)
return { persistor, store }
}
export default createAppStore
App.js
const mapStateToProps = (state) => ({
logged: state.userInfo.isLogin
})
When I run this code I got this error message TypeError: Cannot read property 'isLogin' of undefined
And this error message in console Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
I guess something is not correct when combine reducers. But I have no idea where is wrong?
In the redux-persist docs:
import reducers from './reducers' // where reducers is a object of reducers
the 2nd argument to persistCombineReducers must be an object of reducers.
The export in yout reducer.js should be:
export default {
reducer: reducer
};
Make the changes and let me know if it solved.

Redux action type is PROBE_UNKNOWN_ACTION

I'm calling the action requestLoadOrders to fetch the orders I need. I'm dispatching with type: REQUEST and afterwards with SUCCESS or FAILURE. The fetch succeeded because my orders are in the payload in the redux dev-tools, but the action that I receive in my reducer is ##redux/PROBE_UNKNOWN_ACTION_z.r.p.l.z. I found a thread about this here, however I can't seem to find what I'm doing wrong?
actions.js
import {
LOAD_ORDERS_REQUEST,
LOAD_ORDERS_SUCCESS,
LOAD_ORDERS_FAILURE
} from './constants';
import { fetchOrders } from '../../api';
export const requestLoadOrders = () => {
return (dispatch, getState) => {
dispatch({ type: LOAD_ORDERS_REQUEST });
fetchOrders().then(orders => {
dispatch({ type: LOAD_ORDERS_SUCCESS, payload: orders });
}).catch(error => {
console.error(error);
dispatch({ type: LOAD_ORDERS_FAILURE, payload: error });
});
};
};
reducer.js
import {
LOAD_ORDERS_REQUEST,
LOAD_ORDERS_SUCCESS,
LOAD_ORDERS_FAILURE
} from './constants';
const initialState = {
orders: []
};
const orderReducer = ( state = initialState, { payload, type }) => {
switch (type) {
case LOAD_ORDERS_REQUEST :
return state;
case LOAD_ORDERS_SUCCESS :
return { ...state, orders: payload};
case LOAD_ORDERS_FAILURE :
return { ...state, error: payload.error};
default :
return state;
}
};
export default orderReducer;
My actions get dispatched correctly, but I suppose there's a problem with the reducer receiving its data. Therefor I also added my store and combined reducers files.
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
import thunk from 'redux-thunk';
import makeRootReducer from './reducers';
export const history = createHistory();
const initialState = {}
const enhancers = [];
const middleware = [ routerMiddleware(history), thunk ];
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.devToolsExtension;
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension());
}
}
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
);
const store = createStore(
makeRootReducer,
initialState,
composedEnhancers
);
export default store;
reducers.js
import { combineReducers } from 'redux';
import orderReducer from '../modules/Order/reducer';
export const makeRootReducer = asyncReducers => {
return combineReducers({
order: orderReducer,
...asyncReducers
});
}
export default makeRootReducer;
I found my mistake. I should execute the makeRootReducer function by adding the brackets after the word in createStore().
Updated the createStore() part of store.js to:
const store = createStore(
makeRootReducer(),
initialState,
composedEnhancers
);
was the fix.

Resources