I am trying to route the user to specific route according to user's role. I am using redux as state management tool. I am trying to access the user object from auth state to route the user.
When I login to the system below are the logical steps that take user to route.
Login to the system
Authenticate and load the user
Redirect user to route according to role
I am trying to access the user.role in DashBoard component which is causing the error.
Please help me understand the bug.
1. Action to login to system
export const login = (email, password) => async dispatch => {
const config ={
headers:{
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({email, password});
try {
const res = await axios.post('/api/v1/midasUsers/login',body,config);
dispatch({
type:LOGIN_SUCCESS,
payload:res.data
});
dispatch(loadUser());
} catch (err) {
console.log(err)
const errors = err.response.data.errors;
if(errors){
errors.forEach(error =>dispatch(setAlert(error.msg,'danger')));
}
dispatch({
type:LOGIN_FAIL
})
}
}
2.loaduser to localstorage to authenticate:
export const loadUser = () => async dispatch => {
console.log("I am inside loaduser");
if(localStorage.token){
setAuthToken(localStorage.token)
}
try {
const res = await axios.get('/api/v1/midasUsers/auth');
dispatch({
type: USER_LOADED,
payload:res.data
})
} catch (err) {
dispatch({
type: AUTH_ERROR
})
}
}
3. DashBord.js - Component to route the user
import React,{useEffect} from 'react';
import {Redirect} from 'react-router-dom';
import { connect} from 'react-redux';
import PropTypes from 'prop-types';
//import store from '../../store';
//import {loadUser} from '../../action/auth';
const Dashboard = ({auth:{user,loading,isAuthenticated}}) => {
if(user.role === 'admin'){
return <Redirect to='/adminLandingPage'/>
}
}
Dashboard.propTypes = {
auth:PropTypes.object.isRequired
}
const mapStateToProps = state => ({
auth : state.auth
})
export default connect(mapStateToProps,{})(Dashboard);
I am calling loadUser everytime App.js is mounted the same function after LOGIN_SUCCESS action calls this route
GET /%3Canonymous%3E
Please help me understand the issue
auth.reducer
import {REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT} from '../action/types';
const initialState = {
token : localStorage.getItem('token'),
isAuthenticated : null,
loading: true,
user:null
}
export default function(state= initialState, action){
const {type, payload} = action;
switch (type) {
case USER_LOADED:
return{
...state,
isAuthenticated:true,
loading: false,
user:payload
}
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
localStorage.setItem('token', payload.token);
return{
...state,
...payload,
isAuthenticated:true,
loading:false,
}
case REGISTER_FAIL:
case AUTH_ERROR:
case LOGIN_FAIL:
case LOGOUT:
localStorage.removeItem('token');
return{
...state,
token:null,
isAuthenticated:false,
loading:false
}
default:
return state;
}
}
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 store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
index.js in reducers folder
import { combineReducers } from 'redux';
import alert from './alert';
import auth from './auth';
export default combineReducers ({
alert,
auth
});
import React, { Fragment, useState } from 'react';
import { Link, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { login } from '../../action/auth';
const Login = ({ login, isAuthenticated, user }) => {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const { email, password } = formData;
const onChange = e =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = async e => {
e.preventDefault();
login(email, password);
};
//Redirect if logged in
if(isAuthenticated){
//console.log(user.role);
return <Redirect to ="/dashboard"/>
}
return (
<Fragment>
<h1 className='large text-primary'>Sign In</h1>
<p className='lead'>
<i className='fas fa-user' /> Sign Into Your Account
</p>
<form className='form' onSubmit={e => onSubmit(e)}>
<div className='form-group'>
<input
type='email'
placeholder='Email Address'
name='email'
value={email}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group'>
<input
type='password'
placeholder='Password'
name='password'
value={password}
onChange={e => onChange(e)}
minLength='6'
/>
</div>
<input type='submit' className='btn btn-primary' value='Login' />
</form>
<p className='my-1'>
Don't have an account? <Link to='/register'>Sign Up</Link>
</p>
</Fragment>
);
};
Login.propTypes = {
login: PropTypes.func.isRequired,
isAuthenticated:PropTypes.bool
};
const mapStateToProps = state =>({
isAuthenticated : state.auth.isAuthenticated,
user:state.auth.user
})
export default connect(mapStateToProps,
{ login }
)(Login);
I have this login component which redirects user to dashbaord and at dashbaord i am not able to access the user.role from state
Related
Help me to understand. i use useDispatch but i don't change the state. what am I doing wrong. I read a lot of information, but I can not understand what is my mistake.
i tried other hooks but nothing works.
I've marked up the code below to make it clear. Reducer, action, store, component
component:
import React from 'react';
import {useDispatch} from "react-redux";
import {loginAction, passwordAction} from "../action/userAction";
import storeConfiguration from "../store/storeConfiguration";
import {changeLogin, changePassword} from "../utils/Const";
const Login = () => {
const dispatch = useDispatch()
const stateUser = storeConfiguration.getState()
const senData = () => {
localStorage.setItem(stateUser.login, stateUser.password)
console.log(storeConfiguration.getState());
let keys = Object.keys(localStorage);
for(let key of keys) {
console.log(`${key}: ${localStorage.getItem(key)}`);
dispatch(loginAction())
dispatch(passwordAction())
}
}
function clear() {
localStorage.clear();
}
return (
<div>
<p>Please, enter Username</p>
<input placeholder={'your login'}
onChange={e => changeLogin( e.target.value)}/>
<p>Please, enter Password</p>
<input placeholder={'your password'}
onChange={e => changePassword( e.target.value)}/>
<p></p>
<button onClick={()=>senData()}>Enter</button>
<button onClick={()=>clear()}>clear</button>
</div>
);
};
export default Login;
action:
export const LOGIN = 'loginAction'
export const PASSWORD = 'passwordAction'
export const loginAction = login =>(
{
type: LOGIN,
payload: login
})
export const passwordAction = password =>(
{
type: PASSWORD,
payload: password
})
reducer:
import {LOGIN, PASSWORD} from "../action/userAction";
function userReducer (state, action)
{
switch (action.type){
case LOGIN:
return {...state, login: action.payload }
case PASSWORD:
return {...state, password: action.payload }
default:
return state
}
}
export default userReducer
store:
import userReducer from "../reducer/userReducer";
import { legacy_createStore as createStore} from 'redux'
const initialState =
{
login:'',
password: '',
}
const store = createStore(userReducer, initialState)
export default store
const:
export const currentLogin = 'Admin'
export const currentPassword = '12345'
export const changeLogin = (login) => {
return login
}
export const changePassword = (password) => {
return password
}
In this two lines of code
dispatch(loginAction())
dispatch(passwordAction())
You haven't passed any payload, so nothing can be changed actually
When trying to use auth context in the App.js file it shows undefined. Please help me out in finding the mistake i am doing. Thank you for the help.
App.js
import React, { Fragment, useContext, useEffect } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Navbar from "./components/layout/Navbar";
import Home from "./components/pages/Home";
import About from "./components/pages/About";
import Register from "./components/auth/Register";
import Login from "./components/auth/Login";
import Alerts from "./components/layout/Alerts";
import setAuthToken from "../src/utils/setAuthToken";
//PRIVATE ROUTE
import PrivateRoute from "./components/routing/PrivateRoute";
//CONTEXT IMPORTS
import ContactState from "./context/contact/ContactState";
import AuthState from "./context/auth/AuthState";
import AuthContext from "./context/auth/authContext";
import AlertState from "./context/alerts/AlertState";
import "./App.css";
if (localStorage.token) {
setAuthToken(localStorage.token);
}
const App = () => {
const authContext = useContext(AuthContext);
useEffect(() => {
authContext.loadUser();
//eslint-disable-next-line
});
return (
<AuthState>
<ContactState>
<AlertState>
<Router>
<Fragment>
<Navbar />
<div className='container'>
<Alerts />
<Switch>
<PrivateRoute exact path='/' component={Home} />
<Route exact path='/about' component={About} />
<Route exact path='/register' component={Register} />
<Route exact path='/login' component={Login} />
</Switch>
</div>
</Fragment>
</Router>
</AlertState>
</ContactState>
</AuthState>
);
};
export default App;
Also, in Home.js i am using context the same way but there shows no error in this file and the app runs perfectly.
Home.js
import React, { useContext, useEffect } from "react";
import Contacts from "../contact/Contacts";
import ContactForm from "../contact/ContactForm";
import ContactFilter from "../contact/ContactFilter";
import AuthContext from "../../context/auth/authContext";
const Home = () => {
const authContext = useContext(AuthContext);
useEffect(() => {
authContext.loadUser();
//eslint-disable-next-line
});
return (
<div className="grid-2">
<div>
<ContactForm />
</div>
<div>
<ContactFilter />
<Contacts />
</div>
</div>
);
};
export default Home;
authContext.js
import { createContext } from "react";
const authContext = createContext();
export default authContext;
authReducer.js
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
AUTH_ERROR,
USER_LOADED,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
CLEAR_ERRORS,
} from "../types";
const authReducer = (state, action) => {
switch (action.type) {
case USER_LOADED:
return {
...state,
isAuthenticated: true,
loading: false,
user: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
localStorage.setItem("token", action.payload.token);
return {
...state,
...action.payload,
isAuthenticated: true,
loading: false,
};
case REGISTER_FAIL:
case AUTH_ERROR:
case LOGIN_FAIL:
case LOGOUT:
localStorage.removeItem("token");
return {
...state,
token: null,
isAuthenticated: null,
loading: false,
user: null,
error: action.payload,
};
default:
return state;
}
};
export default authReducer;
authState.js
import React, { useReducer } from "react";
import axios from "axios";
import setAuthToken from "../../utils/setAuthToken";
import AuthContext from "./authContext";
import authReducer from "./authReducer";
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
CLEAR_ERRORS,
} from "../types";
//CREATE INITIAL STATE
const AuthState = (props) => {
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: null,
user: null,
loading: true,
error: null,
};
//STATE ALLOWS US TO USE ANYTHING WE PUT IN THE STATE
//DISPATCH ALLOWS US TO DISPATCH OBJECTS,ACTIONS,METHODS OR ANYTHING TO REDUCER
const [state, dispatch] = useReducer(authReducer, initialState);
//ACTIONS
//LOAD USER - WHICH IS GOING TO TAKE CARE OF WHICH USER IS LOGGED AND ITS GOING TO HIT THAT AUTH ENDPOINT AND GET THE USER DATA
const loadUser = async () => {
// LOAD TOKEN INTO GLOBAL HEADERS
setAuthToken(localStorage.token);
try {
const res = await axios.get("/api/auth");
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
}
};
//REGISTER USER - WHICH SIGNS THE USER UP AND GETS A TOKEN BACK
const register = async (formData) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
const res = await axios.post("api/user", formData, config);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data,
});
loadUser();
} catch (err) {
dispatch({
type: REGISTER_FAIL,
payload: err.response.data.msg,
});
}
};
//LOGIN USER - WHICH WILL LOG THE USER IN AND GET THE TOKEN
const login = async (formData) => {
const config = {
headers: {
"Content-Types": "application/json",
},
};
try {
const res = await axios.post("/api/auth", formData, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
loadUser();
} catch (err) {
dispatch({
type: LOGIN_FAIL,
payload: err.response.data.msg,
});
}
};
//LOGOUT - WHICH WILL DESTROY THE TOKEN AND JUST CLEAR EVERYTHIN UP
const logout = () => dispatch({ type: LOGOUT });
//CLEAR_ERRORS - TO CLEAR OUT ANY ERRORS IN THE STATE
const clearErrors = () =>
dispatch({
type: CLEAR_ERRORS,
});
//BY SURROUNDING THE COMPONENT IN THE SPECIFIC PROVIDER TAGS WE GET ACCESS TO THE STATE AND FUCNTIONS OF THE PROVIDER
return (
<AuthContext.Provider
value={{
token: state.token,
isAuthenticated: state.isAuthenticated,
loading: state.loading,
user: state.user,
error: state.error,
register,
loadUser,
login,
logout,
clearErrors,
}}
>
{props.children}
</AuthContext.Provider>
);
};
export default AuthState;
The above image shows the folder structure
You might need to wrap your App.js file in an AuthContext provider, probably in your index.js file.
ReactDOM.render(
<AuthContextProvider>
<App />
</AuthContextProvider>,
document.getElementById('root')
)
Every time I refresh the page, my react app renders the login component meanwhile it fetches the current status of signed in user. What is that is I am doing wrong over here, as redux store is supposed to be hold values even when the page is refreshed. But right now, even if the user is signed in, the app shows login component.
my app.js:
import { doc, getDoc, setDoc } from "#firebase/firestore";
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Route, Switch } from "react-router";
import Login from "./components/Login";
import Mainthread from "./components/Mainthread";
import Notifications from "./screens/notifications";
import Profile from "./screens/profile";
import { auth, db } from "./services/Firebase";
import { login, logout, selectUser } from
"./services/redux/userSlice";
function App() {
const user = useSelector(selectUser);
const dispatch = useDispatch();
async function createDocument(id, data) {
await setDoc(doc(db, "users", id), data);
}
async function checkDocument(id) {
const docRef = doc(db, "users", id);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log("your document exists with us...");
} else {
const data = { email: user?.email };
createDocument(user?.uid, data);
console.log("your document is being created...");
}
}
useEffect(() => {
function unsubscribe() {
auth.onAuthStateChanged(function (authUser) {
if (authUser) {
dispatch(
login({
uid: authUser.uid,
email: authUser.email,
})
);
checkDocument(user?.uid);
} else {
dispatch(logout());
console.log("Logged Out!");
}
});
}
unsubscribe();
}, [dispatch]);
return (
<>
<Switch>
<Route path="/notifications">
<Notifications />
</Route>
<Route path="/profile">
<Profile />
</Route>
<Route path="/">{user ? <Mainthread /> : <Login />}</Route>
</Switch>
</>
);
}
export default App;
and userSlice.js:
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
userDetails: null,
};
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
login: (state, action) => {
state.userDetails = action.payload;
},
logout: (state,action) => {
state.userDetails = null;
},
},
});
export const { login, logout } = userSlice.actions;
export const selectUser = (state) => state.user.userDetails;
export default userSlice.reducer;
I am using Redux without hooks and all seem to be tied together perfectly, but when I look in the browser console Redux window my state doesn't change. So basically I have a store file which looks like this
import {createStore, applyMiddleware} from "redux";
import thunk from 'redux-thunk'
import {composeWithDevTools} from "redux-devtools-extension/developmentOnly";
import rootReducer from './reducers'
const middleware = [thunk]
const initialState = {}
const store = createStore(rootReducer, initialState,composeWithDevTools(applyMiddleware(...middleware)))
export default store
then I have my global reducer file
import {combineReducers} from "redux";
import searchReducer from './searchReducer'
export default combineReducers({
books: searchReducer
})
and the searchReducers file
import {SEARCH_BOOK, SET_INDEX,FETCH_BOOKS} from "../actions/types";
const initialState = {
query: '',
books: [],
loading: false,
book: []
}
export default function (state = initialState, action) {
switch (action.type) {
case 'SEARCH_BOOK':
return {
...state,
query:action.payload,
loading: false
}
case 'SET_INDEX':
return {
...state,
index:action.payload,
loading: false
}
case 'FETCH_BOOKS':
return {
...state,
index:state.index+40,
books:state.books.concat(action.payload),
loading: false
}
default:
return state
}
}
for now I only have the action type you see imported there
here is that action
import {SEARCH_BOOK} from "./types";
export const searchBook = query => dispatch => {
dispatch ({
type:SEARCH_BOOK,
payload:query
})
}
export const fetchBooks = (query,index) => {
console.log(query)
axios
.get(`https://www.googleapis.com/books/v1/volumes?q=${query}&maxResults=40&orderBy=relevance&startIndex=${index}`)
.then(response =>{
return({
type: FETCH_BOOKS,
payload: response.data.items
})}
)
.catch(err => console.log(err));
};
All is tied together in the App where I imported the provider that wraps up everything.
Here comes the problem. I have a search form that should on change update the query value of the global state
import React, { useState, useReducer} from "react";
import {useSelector, useDispatch} from 'react-redux'
import { Button, Container, Row, Col, Form, FormGroup, FormInput } from "shards-react";
import queryBuilder from "../js/helper";
import style from "./SearchForm/body.module.css";
import {searchBook, fetchBooks} from "../actions/SearchActions";
const initialState = {
title:'',
author:'',
publisher:''
}
function reducer(state,{ field, value }){
return {
...state,
[field]: value
}
}
function SearchForm() {
const index = useSelector(state => state.index)
const [state, dispatch] = useReducer(reducer, initialState);
const [query, setQuery] = useState('');
const disp = useDispatch();
const onChange = e => {
dispatch({ field: e.target.name, value: e.target.value })
}
const { title,author,publisher } = state;
const handleSubmit = e => {
e.preventDefault()
setQuery(queryBuilder(state))
disp(fetchBooks(query, 0))
}
return(
<div>
<Container className={style.FormContainer}>
<Form onSubmit={handleSubmit}>
<Row className={'topBar'}>
<Col>
<FormGroup>
<FormInput id={'bookTitle'} name={'title'} placeholder={'title'} value={title} onChange={onChange}/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<FormInput id={'bookAuthor'} name={'author'} value={author} onChange={onChange} placeholder={'author'}/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<FormInput id={'bookPublisher'} name={'publisher'} value={publisher} onChange={onChange}
placeholder={'publisher'}/>
</FormGroup>
</Col>
<Col>
<Button outline theme='primary' type={'submit'}>Submit</Button>
</Col>
</Row>
</Form>
</Container>
</div>
)
}
export default SearchForm
I don't know what is missing.
Edit
As suggested I tried using hooks and now everything is tied together just fine. The problem is now with the fetching of the books. I updated the action file so you can see the action I added. When I dispatch this action I get this error
Actions must be plain objects. Use custom middleware for async actions
Does anybody know how to fix this?
I guess your error can be resolved as
export const fetchBooks =(query,index) => dispatch => {
console.log(query)
axios
.get(`https://www.googleapis.com/books/v1/volumes?q=${query}&maxResults=40&orderBy=relevance&startIndex=${index}`)
.then(response =>{
dispatch({
type: FETCH_BOOKS,
payload: response.data.items
})}
)
.catch(err => console.log(err));
};
It looks like you're missing a return in your fetchBooks function. You're not returning the promise, which means that the thunk middleware isn't receiving the promise result.
export const fetchBooks = (query,index) => {
console.log(query)
return axios
.get(`https://www.googleapis.com/books/v1/volumes?q=${query}&maxResults=40&orderBy=relevance&startIndex=${index}`)
.then(response =>{
return({
type: FETCH_BOOKS,
payload: response.data.items
})}
)
.catch(err => console.log(err));
};
How can I access the field values in a parent component of a redux-form component?
I'm not sure if it's caused by typescript, but before I started using typescript, I was able to access the form values through mapStateToProps just like how I have it currently. I've been trying to figure out what was different to my previous implementation but the only difference would be the versions of the npm dependencies and the addition of typescript.
LoginPage.tsx
import LoginForm from 'components/Forms/LoginForm'
import Layout from 'components/Layout'
import { StatusCodes } from 'lib/enums/statusCodes'
import { storeAuthToken } from 'lib/helpers/auth'
import { NextPage } from 'next'
import Router from 'next/router'
import React from 'react'
import { connect, DispatchProp } from 'react-redux'
import { FormInstance } from 'redux-form'
interface IProps {
login: FormInstance<IFormData, IFormProps>
}
interface IState {
errorMessage?: string,
processing: boolean
}
interface IRootState {
form: IProps
}
export interface IFormData {
username?: string,
password?: string
}
export interface IFormProps {
contactId?: string,
errorMessage?: string,
fieldValues: Partial<IFormData>,
processing: boolean
}
class LoginPage extends React.Component<NextPage & DispatchProp & IProps, IState> {
state = {
errorMessage: undefined,
processing: false
}
setErrorMessage = (message: string) => {
this.setState({
errorMessage: message,
processing: false
})
}
handleSubmit = async (values: IFormData) => {
if (values && values.username && values.password) {
this.setState({
errorMessage: undefined,
processing: true
})
try {
const { dispatch } = this.props
await storeAuthToken(dispatch, values.username, values.password)
Router.push('/')
} catch (error) {
if (error === StatusCodes.BAD_REQUEST) {
this.setErrorMessage("Sorry, you have entered incorrect details. Please try again.")
} else {
this.setErrorMessage("Sorry, there was an issue trying to log you in")
}
}
}
}
render() {
const { login } = this.props
const { processing } = this.state
return (
<Layout title="Login">
<div className="form-wrapper full">
<LoginForm processing={processing} onSubmit={this.handleSubmit} fieldValues={login.values} />
</div>
</Layout>
)
}
}
const mapStateToProps = ({ form: { login } }: IRootState) => ({ login })
export default connect(mapStateToProps)(LoginPage)
LoginForm.tsx
import Link from 'next/link'
import React from 'react'
import { Field, InjectedFormProps, reduxForm } from 'redux-form'
import FormButton from 'components/Forms/FormButton'
import Input from 'components/Fields/Input'
import { validateRequired } from 'lib/helpers/validators'
import { IFormProps, IFormData } from 'pages/login'
class LoginForm extends React.Component<IFormProps & InjectedFormProps<IFormData, IFormProps>> {
render() {
const { contactId, errorMessage, fieldValues, handleSubmit, processing } = this.props
return (
<form id="login" onSubmit={handleSubmit} >
<h1>Sign in</h1>
<fieldset>
<div className="fields">
{
!contactId
? <Field name="username" type="text" component={Input} label="Username" validate={validateRequired} />
: <Field name="username" type="email" component={Input} label="Email" validate={validateRequired} />
}
</div>
<div className="fields">
<Field name="password" type="password" component={Input} label="Password" validate={validateRequired} />
</div>
</fieldset>
{ errorMessage && <p className="error-message">{errorMessage}</p> }
<div className="form-bottom">
<Link href="/"/*{`/forgot-password${fields.email ? `?email=${encodeURIComponent(fields.email)}` : ''}`}*/>
<a className="inline">Forgotten your password?</a>
</Link>
<FormButton loading={processing}>
Login
</FormButton>
</div>
</form>
)
}
}
export default reduxForm<{}, IFormProps>({ form: 'login' })(LoginForm)
Here is my redux store file incase if that is coded incorrectly
import { createWrapper, HYDRATE, MakeStore } from 'next-redux-wrapper'
import { AnyAction, applyMiddleware, combineReducers, createStore, Reducer } from 'redux'
import { reducer as formReducer } from 'redux-form'
import thunkMiddleware, { ThunkMiddleware } from 'redux-thunk'
import authReducer, { AuthState } from './auth/reducer'
import contactReducer, { ContactState } from './contact/reducer'
import initialState from './initialState'
export interface State {
auth: AuthState
contact: ContactState
}
const bindMiddleware = (middleware: [ThunkMiddleware]) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
const combinedReducer = combineReducers({
auth: authReducer,
contact: contactReducer,
form: formReducer
})
const reducer: Reducer = (state: State, action: AnyAction) => {
if (action.type === HYDRATE) {
const nextState: Reducer = {
...state,
...action.payload
}
return nextState
} else {
return combinedReducer
}
}
const makeStore: MakeStore<State> = () => createStore(reducer, initialState, bindMiddleware([thunkMiddleware]))
export const wrapper = createWrapper<State>(makeStore/*, { debug: true }*/)
It seems like I missed out a key in the IApplicationState interface and as mentioned by #cbr, the parameters state and action needed to be passed to combinedReducer even though it doesn't directly take any.
Additionally it didn't like when the nextState constant had the type Reducer, so I have changed that to CombinedState<State> as well
The changed code looks like this
import { createWrapper, HYDRATE, MakeStore } from 'next-redux-wrapper'
import { AnyAction, applyMiddleware, combineReducers, createStore, Reducer } from 'redux'
import { reducer as formReducer } from 'redux-form'
import thunkMiddleware, { ThunkMiddleware } from 'redux-thunk'
import authReducer, { AuthState } from './auth/reducer'
import contactReducer, { ContactState } from './contact/reducer'
import initialState from './initialState'
export interface State {
auth: AuthState
contact: ContactState,
form: FormStateMap
}
const bindMiddleware = (middleware: [ThunkMiddleware]) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
const combinedReducer = combineReducers({
auth: authReducer,
contact: contactReducer,
form: formReducer
})
const reducer: Reducer = (state: State, action: AnyAction) => {
if (action.type === HYDRATE) {
const nextState: CombinedState<State> = {
...state,
...action.payload
}
return nextState
} else {
return combinedReducer(state, action)
}
}
const makeStore: MakeStore<State> = () => createStore(reducer, initialState, bindMiddleware([thunkMiddleware]))
export const wrapper = createWrapper<State>(makeStore)