Logout React Reducer w/ History, State & Action Bug - javascript

Hey I've looked at a bunch of help files now and cant seem to get the issue solved. Most suggestions are using a different setup than I have. The main issue that the others dont have is the LOGOUT feature. Can you suggest another way to handle the LOGOUT?
Here is my index.js for "combine reducers":
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import { LOGOUT_USER } from '../constants/index';
/* App Reducer Files */
import app from './app/reducer';
import accountData from './account/reducer';
import employeeData from './employee/reducer';
import locationData from './location/reducer';
import googleData from './google/reducer';
import requestData from './request/reducer';
import menuItemData from './menuItem/reducer';
import orderData from './order/reducer';
/* Public Reducer Files */
import valorData from './valor/reducer';
const appReducer = history => combineReducers({
router: connectRouter(history),
app,
accountData,
employeeData,
locationData,
googleData,
requestData,
menuItemData,
orderData,
// Public
valorData,
});
const rootReducer = history => (state, action) => {
if (action.type === LOGOUT_USER) {
state = undefined;
}
return appReducer(history, state, action);
};
export default rootReducer;
And my store.js:
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { routerMiddleware } from 'connected-react-router';
import rootReducer from '../services';
import history from '../history';
const debugware = [];
if (process.env.NODE_ENV !== 'production') {
debugware.push(createLogger({
collapsed: true,
}));
}
export default function configureStore(initialState) {
const store = createStore(
rootReducer(history),
initialState,
compose(
applyMiddleware(
routerMiddleware(history),
thunkMiddleware,
...debugware,
),
),
);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../services', () => {
const nextRootReducer = require('../services/index').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
}
This was working before I upgraded to a new router version. Again the main issue is the LOGOUT. If I just export appReducer it works just fine but doesnt logout.

WOW. The moment I post it I try one more thing...
const rootReducer = history => (state, action) => {
if (action.type === LOGOUT_USER) {
state = undefined;
}
return appReducer(history)(state, action);
};

Why are you returning an undefined state? Instead, return
if (action.type === LOGOUT_USER) {
return{ ...state, user: [] }
}
It basically returns and empty array as user.

Related

CreateAsyncThunk Error: Actions must be plain objects. Use custom middleware for async actions

I am currently setting up my RTK (Redux Toolkit) and did some minor testings. Here's my code:
store/index.js
import { configureStore } from '#reduxjs/toolkit'
import { loginSliceReducer } from './views/page/login/loginSlice'
export default configureStore({
reducer: {
login: loginSliceReducer
}
})
loginSlice.js
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit'
import ApiService from '../../services/ApiService'
export const authorize = createAsyncThunk(
'api/authorize',
async (email, password) => {
const response = await ApiService.post(email, password)
return response.data
}
)
export const loginSlice = createSlice({
name: 'login',
initialState: {
loading: true,
token: null,
data: []
},
reducers: {
updateState: (state, action) => {
const { payload } = action
switch (payload.type) {
case AUTH_SUCCESS:
state.loading = false
state.token = payload.token
state.data = payload.data
break
default:
}
}
},
extraReducers: {
[authorize.fulfilled]: (state, action) => {
// ... do state update here
}
}
})
export default loginSlice.reducer
login.js
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { authorize } from './loginSlice'
const Login = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(authorize('testuser#example.com', 'test123'))
}, [])
return <div>Auth Test</div>
}
The code above doesn't work. I keep getting this error:
Error: Actions must be plain objects. Use custom middleware for async actions.
On this line:
> 25 | dispatch(authorize('testuser#example.com', 'test123'))
Please don't mind me triggering the authorize on useEffect, as this is only a test to check if the endpoint is being called and to check if the state will update once the request is successful. :-D
I had this same issue and it was caused by the fact that I was adding additional middleware.
#reduxjs/toolkit's configureStore has some middleware that is included by default, but if you add anything to the middleware property, it will overwrite these defaults.
The solution is to include the default middleware along with the middleware you define:
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit';
import { loginSliceReducer } from './views/page/login/loginSlice';
import { otherMiddleware } from './middlewares/some-other-module';
export default configureStore({
reducer: {
login: loginSliceReducer
},
middleware: [ // Because we define the middleware property here, we need to explictly add the defaults back in.
...getDefaultMiddleware(),
otherMiddleware
]
})
Note, there is no need to explicitly include redux-thunk when using #reduxjs/toolkit because it is already part of the default middlewares from getDefaultMiddleware()
Looks like the problem is that you didn't add to your store a middleware capable of handling async actions
In your store/index.js try smth like:
import { applyMiddleware } from 'redux';
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import { loginSliceReducer } from './views/page/login/loginSlice'
import thunk from 'redux-thunk';
export default configureStore({
reducer: {
login: loginSliceReducer
},
middleware: [applyMiddleware(thunk), getDefaultMiddleware()]
})
If there are more than one argument to be passed to an action creator, you must pass them inside an object.
For example, if I have to send email and password as payload, I will have to send them in an object like below:
dispatch(authorize({email, password}))
one way you can fix this is to use your store's dispatch method.
This way, since thunk is included in Redux Toolkit (RTK) by default, you'll have access to that - which should fix the error you're getting.
Try This:
store/index.js
import { configureStore } from '#reduxjs/toolkit'
import { loginSliceReducer } from './views/page/login/loginSlice'
export default const store = configureStore({
reducer: {
login: loginSliceReducer
}
})
const useAppDispatch = () => store.dispatch
export { useAppDispatch }
login.js
import React, { useEffect } from 'react'
import { useSelector} from 'react-redux'
import { authorize } from './loginSlice'
import { useAppDispatch } from './store/index'
const Login = () => {
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(authorize('testuser#example.com', 'test123'))
}, [])
return <div>Auth Test</div>
}

React Redux Persist is rehydrating, but no stored data is retrieved, instead, default

I've been struggling with this problem for two days, so I need help!
I have a React web in which I've added Redux Persist (also Redux Saga for handling requests) as the documentation said.
I'm testing with a store that doesn't have any saga in the middle, when I trigger an action, the data is updated, I can see it in the Debugger, but when I refresh, despite the Redux Hydrate process runs, that value goes back to the default one.
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga'
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import rootReducer from './reducers';
// import rootSaga from './sagas';
const persistConfig = {
key: 'root',
storage: storage,
// whitelist: ['login', 'account', 'layout'],
stateReconciler: autoMergeLevel2, // see "Merge Process" section for details.
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const sagaMiddleware = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(persistedReducer, composeEnhancers(applyMiddleware(sagaMiddleware)));
const persistor = persistStore(store);
export { store, persistor, sagaMiddleware };
reducers.js
import { combineReducers } from 'redux';
// Front
import layout from './layout/reducer';
// Authentication Module
import account from './auth/register/reducer';
import login from './auth/login/reducer';
import forget from './auth/forgetpwd/reducer';
const rootReducer = combineReducers({
layout,
account,
login,
forget
});
export default rootReducer;
reducer.js (the one I'm testing, Layout)
import { ACTIVATE_AUTH_LAYOUT, ACTIVATE_NON_AUTH_LAYOUT, TOGGLE, TOGGLE_LD } from './actionTypes';
const initialState={
topbar:true,
sidebar:true,
footer:true,
is_toggle : true,
is_light : true
}
const layout = (state=initialState,action) => {
switch(action.type){
case ACTIVATE_AUTH_LAYOUT:
state = {
...state,
...action.payload
}
break;
case ACTIVATE_NON_AUTH_LAYOUT:
state = {
...state,
...action.payload
}
break;
case TOGGLE:
state = {
...state,
is_toggle : action.payload
}
break;
case TOGGLE_LD:
state = {
...state,
is_light : action.payload
}
break;
default:
// state = state;
break;
}
return state;
}
export default layout;
actions.js
import { ACTIVATE_AUTH_LAYOUT, ACTIVATE_NON_AUTH_LAYOUT, TOGGLE, TOGGLE_LD } from './actionTypes';
export const activateAuthLayout = () => {
return {
type: ACTIVATE_AUTH_LAYOUT,
payload: {
topbar: true,
sidebar: true,
footer: true,
rodri: 'butta',
layoutType: 'Auth'
}
}
}
export const activateNonAuthLayout = () => {
return {
type: ACTIVATE_NON_AUTH_LAYOUT,
payload: {
topbar: false,
sidebar: false,
footer: false,
layoutType: 'NonAuth'
}
}
}
export const toggleSidebar = (is_toggle) => {
return {
type: TOGGLE,
payload: is_toggle
}
}
export const toggleLightDark = (is_light) => {
return {
type: TOGGLE_LD,
payload: is_light
}
}
index.js (APP)
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/lib/integration/react';
import rootSaga from './store/sagas';
import {persistor, store, sagaMiddleware} from './store';
sagaMiddleware.run(rootSaga);
const app = (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter>
<App />
</BrowserRouter>
</PersistGate>
</Provider>
);
ReactDOM.render(app, document.getElementById('root'));
serviceWorker.unregister();
And fragments of the class
...
this.props.toggleSidebar(!this.props.is_toggle);
...
const mapStatetoProps = state => {
const { is_toggle,is_light } = state.layout;
return { is_toggle,is_light };
}
export default withRouter(connect(mapStatetoProps, { toggleSidebar })(Topbar));
The debugger
First time run, when I toggle the menu bar so the action gets triggered)
After refreshing the browser, the rehydrate should bring the layout -> is_toggle to false.. but it remains true (as default)
Fragments in color:
I think your issue is how you are setting up your store:
const store = createStore(persistedReducer, composeEnhancers(applyMiddleware(sagaMiddleware)));
The structure of this function is createStore(reducer, [preloadedState], [enhancer])
So you are trying to pass your enhancers as preloadedState.
Try changing it to this:
const store = createStore(persistedReducer, {}, composeEnhancers(applyMiddleware(sagaMiddleware)));
Also, when you setup your persistConfig I see that you have the whitelist commented out. Any reducer state you want to keep needs to be in this list so it should not be commented out:
// whitelist: ['login', 'account', 'layout'], // uncomment this
And finally as a side note, you don't need break in all of your switch cases
I faced the same bug, the thing worked for me was in the comments of the Author's question saying:
And this solution didn't worked for me :( https://github.com/rt2zz/redux-persist/issues/1114#issuecomment-549107922
So if anyone stuck on this issue then could try this solution out at: https://github.com/rt2zz/redux-persist/issues/1114#issuecomment-549107922
Issue: My Persist was being called first before rehydrate, making my states to the default state again. Hence everytime i refresh my states were gone from the local storage.

Redux-Saga somehow reducer updates without going through the saga

I am new to the saga world. Although I have worked with thunk on react-native territory, I am very confused at the moment. I am trying to get the skeleton of my project going which I expect to get very large soon. With that in mind, I am trying to separate the logic into multiple files.
I have gotten the reducer to fire except it is not the way I want. I am not sure how it is even happening. My saga does not fire but my state updates. I see the console log from my reducer but nothing from the saga watcher function. What should I change?
Index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { Provider } from 'react-redux'
import reducer from './reducers'
import rootSaga from './sagas'
import App from './App'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("etlRootDiv"),
);
App.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import FileExtensionSelector from './components/FileExtensionSelector'
import { setFileExtension } from './actions'
class App extends Component {
constructor(props) {
super(props)
}
handleTypeSelect() {
console.log('handle more click');
this.props.setFileExtension('zip');
console.log(this.props);
}
componentWillReceiveProps(nextProps){
console.log(nextProps);
}
render() {
return (
<div>
<FileExtensionSelector onFileTypeSelect={this.handleTypeSelect.bind(this)} />
<div>{this.props.fileType} ...asdasd</div>
</div>
)
}
}
const mapStateToProps = ({ metaState }) => {
const { fileType } = metaState;
return { fileType };
};
const mapDispatchToProps = (dispatch) => ({
setFileExtension(ext) {
dispatch(setFileExtension(ext))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(App)
reducers/index.js
import { combineReducers } from 'redux';
import metaState from './MetaStateReducer';
const rootReducer = combineReducers({
metaState,
})
export default rootReducer
reducers/metastatereducer.js
const INITIAL_STATE = {
fileType: null,
hasHeader: false,
};
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case 'SET_FILE_EXTENSION':
console.log('/// in set file reducer ///');
console.log(action);
// console.log({ ...state, ...INITIAL_STATE, fileType: action.payload });
return { ...state,...INITIAL_STATE, fileType: action.payload };
default:
return state;
}
}
actions/metaStateActions.js
function action(type, payload = {}) {
return { type, ...payload }
}
export const SET_FILE_EXTENSION = "SET_FILE_EXTENSION";
export const setFileExtension = (extension) => action( SET_FILE_EXTENSION, { payload: extension });
actions/index.js
export { setFileExtension, SET_FILE_EXTENSION } from './metaDataActions';
sagas/metastatesagas.js
import { take, put } from 'redux-saga/effects'
import { SET_FILE_EXTENSION } from '../actions';
function* watchFileExtension(ext) {
console.log(' --- in watch file ext ---');
const { extension } = yield take(SET_FILE_EXTENSION)
console.log(`set extension is ${extension}`);
// yield put({ type: 'SET_FILE_EXTENSION', payload: ext });
}
export const metaStateSagas = [
take("SET_FILE_EXTENSION", watchFileExtension),
]
sagas/index
import { all } from 'redux-saga/effects'
import { metaStateSagas } from './MetaStateSagas';
export default function* rootSaga() {
yield all([
...metaStateSagas,
])
}
redux-saga always passes an action along to the store before attempting to process itself. So, the reducers will always run before any saga behavior executes.
I think the error is that your metaStateSagas array needs to use takeEvery, not take, but I'm not entirely sure. Try that and see if it fixes things.

In Redux, reducer doesn't recognise the action object

I'm using Redux for a React project. For some reason, my reducer doesn't recognise the action type sent to it or even the action itself. I get this error TypeError: Cannot read property 'type' of undefined. And I know I'm using dispatch.
The api from the server works fine, I've tested through Postman.
But I don't understand what's happening with redux.
Please, can someone advise me?
Before stamping down my question, I've read many SO posts that looks similar but none has answered my question, hence why I'm asking it here.
Thanks.
Action:
import axios from 'axios';
export const GET_ALL_USERS = 'GET_ALL_USERS';
export const showAllUsers = () => dispatch => {
console.log('USERS ACTION');
return axios
.get('/api/users/all')
.then(res => {
console.log('GETTING ALL USERS ACTION', res);
return dispatch({
type: GET_ALL_USERS,
payload: res.data
});
})
.catch(err => console.log('Oops! Cannot get any users.'));
};
Reducer:
import { GET_ALL_USERS } from '../actions/usersActions';
const InitialState = {
users: null,
loading: false
};
export default function(state = InitialState, action) {
console.log('USERS REDUCER', action);
switch (action.type) {
case GET_ALL_USERS:
return {
...state,
users: action.payload
};
default:
return state;
}
}
React:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import './index.css';
import App from './containers/App';
import registerServiceWorker from './registerServiceWorker';
import rootReducer from './reducers';
let middleware = [thunk];
const store = createStore(
rootReducer,
{},
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
React component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { showAllUsers } from '../../actions/usersActions';
export class Admin extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
this.props.showAllUsers();
}
render() {
const { users } = this.props;
console.log(`All users in admin`, this.props.users);
return (
<div className="admin">
<h1 className="admin__title">Admin Board</h1>
{this.props.users.map(user => {
return (
<article>
<img src={user.avatar} alt={user.username} />
<p>{user.firstName}</p>
<p>{user.lastName}</p>
<p>{user.username}</p>
<p>{user.email}</p>
</article>
);
})}
</div>
);
}
}
const mapStateToProps = state => ({
users: state.users
});
export default connect(mapStateToProps, { showAllUsers })(Admin);
showAllUsers is async action. You need some middleware to dispatch async action.
Use redux-thunk or redux-saga and integrate it with store.
It will help to perform asynchronous dispatch
import thunkMiddleware from 'redux-thunk';
let middleWares = [thunkMiddleware];
const store = createStore(
rootReducer, applyMiddleware(...middleWares)
)
If you dispatch async action in synchronous way, the error TypeError: Cannot read property type of undefined will be thrown.

redux-persist: How to save state on browser's local storage?

I am learning redux and wanted to know how can I save the state locally so when I refresh after making some changes to state it remains. I came to know about redux-persist and saw the github doc describing how to use it but its not very clear to me.
Here is the index.js code of my app -
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import App from './components/app';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware()(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<App />
</Provider>
, document.querySelector('#container'));
How can I achieve it ?
//In your createStore have this code
import { applyMiddleware, compose, createStore } from 'redux'
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import rootReducer from '../reducers';
import { persistReducer } from 'redux-persist'
import localForage from 'localforage';
const loggerMiddleware = createLogger();
export default (initialState = {}) => {
// ======================================================
// Middleware Configuration
// ======================================================
const middleware = [thunkMiddleware, loggerMiddleware]
// ======================================================
// Store Enhancers
// ======================================================
const enhancers = []
const __DEV__ = process.env.NODE_ENV !== 'production';
if (__DEV__) {
const devToolsExtension = window.devToolsExtension
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}
// ======================================================
// Store Instantiation and HMR Setup
// ======================================================
let config = {
key: 'root',
storage: localForage,
whitelist: ['user'],
debug: __DEV__
}
let configureReducer = persistReducer(config, rootReducer)
const store = createStore(
configureReducer,
initialState,
compose(
applyMiddleware(...middleware),
...enhancers
),
)
return store
}
// In your App.js or root app, do this in your componentDidMount
persistStore(
store,
undefined,
() => {
console.log('callback::')
}
)
Here is what you should do for creating your store, using redux-persist in store.js:
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import { persistStore, autoRehydrate } from 'redux-persist';
import localForage from 'localforage';
import { routerReducer } from 'react-router-redux';
import reducers from './container/reducers';
import middlewares from './middlewares';
const reducer = combineReducers({
...reducers,
routing: routerReducer,
});
export const initStore = (state) => {
const store = createStore(
reducer,
{},
compose(
applyMiddleware(...middlewares),
autoRehydrate(),
),
);
persistStore(store, {
storage: localForage,
whitelist: ['login'],
});
return store;
};
In your app.js, create your store this way:
import React from 'react';
import { Provider } from 'react-redux';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import createRoutes from './routes'; // Contains the routes
import { initStore, persistReduxStore } from './store';
import { appExample } from './container/reducers';
const store = initStore(appExample);
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { rehydrated: false };
}
componentWillMount() {
persistReduxStore(store)(() => this.setState({ rehydrated: true }));
}
render() {
const history = syncHistoryWithStore(browserHistory, store);
return (
<Provider store={store}>
{createRoutes(history)}
</Provider>
);
}
}

Categories