Redux combineReducer returns default state for reducer not called in action - redux

I'm new to react redux, so I think I'm just missing something basic.
I have three reducers, two to handle orders that update in the store as arrays, and one that shows the status of a web socket connection I'm using to receive orders from the server.
// reducers.js
import { combineReducers } from 'redux'
import { ADD_POS_ORDER, ADD_MOBILE_ORDER, UPDATE_WS_STATUS, wsStatuses } from '../actions/actions'
const { UNINITIALIZED } = wsStatuses
const posOrders = (state = [], action) => {
switch (action.type) {
case ADD_POS_ORDER:
return [
...state,
{
id: action.order.id,
status: action.order.status,
name: action.order.name,
pickupNum: action.order.pickupNum
}
]
default:
return state
}
}
const mobileOrders = (state = [], action) => {
switch (action.type) {
case ADD_MOBILE_ORDER:
return [
...state,
{
id: action.order.id,
status: action.order.status,
name: action.order.name,
pickupNum: action.order.pickupNum
}
]
default:
return state
}
}
const wsStatus = (state = UNINITIALIZED, action) => {
switch (action.type) {
case UPDATE_WS_STATUS:
return action.status
default:
return state
}
}
const displayApp = combineReducers({
posOrders,
mobileOrders,
wsStatus
})
export default displayApp
When I connect to the socket, I dispatch an action to update wsStatus and the action is stored as 'CONNECTED'.
When I follow with an order with the posOrders reducer, the wsStatus is reset to its default, 'UNINITIALIZED'.
What I am struggling to understand is why wsStatus is not using the previous state of 'CONNECTED', but instead returning default.
// actions.js
export const UPDATE_WS_STATUS = 'UPDATE_WS_STATUS'
export const wsStatuses = {
UNINITIALIZED: 'UNINITIALIZED',
CONNECTING: 'CONNECTING',
CONNECTED: 'CONNECTED',
DISCONNECTED: 'DISCONNECTED'
}
export const ADD_POS_ORDER = 'ADD_POS_ORDER'
export const ADD_MOBILE_ORDER = 'ADD_MOBILE_ORDER'
export const UPDATE_POS_ORDER = 'UPDATE_POS_ORDER'
export const setWsStatus = (status) => {
return {
type: 'UPDATE_WS_STATUS',
status: status
}
}
export const updateOrderQueue = (action, order) => {
return {
type: action,
id: order.id,
order: order,
receivedAt: Date.now()
}
}
Here's where I make the calls:
// socketListeners.js
import { setWsStatus } from '../actions/actions'
import SockJS from 'sockjs-client'
export const socket = new SockJS('http://localhost:3000/echo')
export default function (dispatch, setState) {
socket.onopen = function () {
dispatch(setWsStatus('CONNECTED'))
}
socket.onclose = function () {
dispatch(setWsStatus('DISCONNECTED'))
}
}
// orders container
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { socket } from '../helpers/socketListeners'
import { updateOrderQueue, setWsStatus } from '../actions/actions'
import PosOrder from '../components/queue/PosOrder'
class PosOrderList extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
const { dispatch } = this.props
socket.onmessage = function(e) {
// convert order info to object
let parsedOrder = JSON.parse(e.data)
let action = parsedOrder.action
let order = parsedOrder.order
dispatch(updateOrderQueue(action, order))
}
}
render() {
const { updateOrderQueue } = this.props
return (
<ul>
{this.props.posOrders.map(posOrder =>
<PosOrder
key={posOrder.id}
{...posOrder}
/>
)}
</ul>
)
}
}
PosOrderList.propTypes = {
posOrders: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.hash,
status: PropTypes.string,
name: PropTypes.string,
pickupNum: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
}))
}
// send data to component props
const mapStateToProps = (state) => {
return {
posOrders: state.posOrders,
}
}
export default connect(mapStateToProps)(PosOrderList)
// store
const store = configureStore(initialState)
export default function configureStore(initialState) {
return createStore(
displayApp,
initialState,
applyMiddleware(
createLogger({
stateTransformer: state => state.toJS()
}),
thunk,
// socketMiddleware
)
)
}
addSocketListeners(store.dispatch, store.getState)
Lastly, the store logs here: redux store
Any and all help on this would be very appreciated! Thank you!

When you compose your reducer with combineReducers, for each dispatched action, all subreducers get invoked, since every reducer gets a chance to respond to every action.
Therefore, all state gets initialized after the first action is dispatched.

Your reducers are working fine https://jsfiddle.net/on8v2z8j/1/
var store = Redux.createStore(displayApp);
store.subscribe(render);
store.dispatch({type: 'UPDATE_WS_STATUS',status:'CONNECTED'});
store.dispatch({type: 'ADD_POS_ORDER',id:'id'});
store.dispatch({type: 'UPDATE_WS_STATUS',status:'DISCONNECTED'});

Related

Access redux state value on a React + Redux + Hooks + Typescript web app

I am trying to access the redux state to display its value on my website. I am using React redux hooks with functional components and Typescript.
Situation:
I have a store with two reducers: UI and user. The initial state is:
{
user: {
authenticated: false,
credentials: {}
},
UI: {
loading: false,
errors: null
}
}
When the user signs in, the signinUser action takes place and correctly changes the redux state. For example, for an invalid signin, the redux state is:
{
user: {
authenticated: false,
credentials: {}
},
UI: {
loading: false,
errors: {
general: 'wrong credentials, please try again'
}
}
}
Problem:
I am trying to acces the UI.errors so I can display them on my website. i have a function in my Signin component thnamed submitForm that calls the signinUser action that correctly dispatches the actions. My problem is that after that I want to retrieve the state.ui.errors and I can't figure out how to.
I have tried all this:
componentWillRecieveProps(nextProps) { ... } this solution is for class components and I am using functional components
useSelector((state: StoreState) => state.UI); If I do it inside submitForm is invalid because React Hooks don't allow to call inside a function. If I do it outside, it fetches the old state.
Here are my files (the parts related to this issue)
store.tsx
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
// Reducers
import userReducer from './reducers/userReducer';
import uiReducer from './reducers/uiReducer';
const initialState = {};
const middleware = [thunk];
const reducers = combineReducers({
user: userReducer,
UI: uiReducer
});
const store = createStore(
reducers,
initialState,
compose(
applyMiddleware(...middleware),
(window as any).__REDUX_DEVTOOLS_EXTENSION__ &&
(window as any).__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
userActions.tsx
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from '../types';
import axios from 'axios';
// Interfaces
import { ISigninForm } from '../../utils/types';
// Redux
import { Dispatch } from 'redux';
import { useDispatch } from 'react-redux';
export const signinUser = (
userData: ISigninForm,
dispatch: Dispatch,
handleDialogClose: () => void
) => {
console.log('signinuser in userActions');
dispatch({ type: LOADING_UI });
axios
.post('/signin', userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem('FBIdToken', FBIdToken);
axios.defaults.headers.common['Authorization'] = FBIdToken;
getUserData(dispatch);
dispatch({ type: CLEAR_ERRORS });
handleDialogClose();
// history.push("/profile"); // this will redirect to a page not built yet
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data
});
});
};
export const getUserData = (dispatch: Dispatch) => {
console.log('getUserData');
axios
.get('/user')
.then((res) => {
console.log('/user', res);
dispatch({
type: SET_USER,
payload: res.data
});
})
.catch((err) => console.log('err', err));
};
uiReducer.tsx
import { SET_ERRORS, CLEAR_ERRORS, LOADING_UI, IAction } from '../types';
const initialState = {
loading: false,
errors: null
};
export default function (state = initialState, action: IAction) {
switch (action.type) {
case SET_ERRORS:
return {
...state,
loading: false,
errors: action.payload
};
case CLEAR_ERRORS:
return {
...state,
loading: false,
errors: null
};
case LOADING_UI:
return {
...state,
loading: true
};
default:
return state;
}
}
userReducer.tsx
import {
SET_USER,
SET_AUTHENTICATED,
SET_UNAUTHENTICATED,
IAction
} from '../types';
const initialState = {
authenticated: false,
credentials: {}
};
export default function (state = initialState, action: IAction) {
switch (action.type) {
case SET_AUTHENTICATED:
return {
...state,
authenticated: true
};
case SET_UNAUTHENTICATED:
return initialState;
case SET_USER:
console.log('SET_USER', action);
return {
authenticated: true,
...action.payload
};
default:
return state;
}
}
Signin.tsx
function Signin({ history }: RouteComponentProps): JSX.Element {
// States
const [dialogOpen, setDialogOpen] = React.useState(false);
const [errorsAPI, setErrorsAPI] = React.useState<ISigninErrors>({});
const [loading, setLoading] = React.useState(false);
// Dialog
const handleDialogOpen = () => {
setDialogOpen(true);
};
const handleDialogClose = () => {
setDialogOpen(false);
};
// Form
const { register, handleSubmit, errors } = useForm<ISigninForm>();
const submitForm = (data: ISigninForm) => {
signinUser(data, dispatch, handleDialogClose);
};
return (
// HTML content
);
}
export default withRouter(Signin);
Solution:
I had the solution in front of me this whole time, but I was not using the function in the right way.
const state = useSelector((state: StoreState) => state);
This is called inside the Signin function component. Then when I am returning the HTML object, I just call
{state.UI.errors !== null && 'general' in state.UI.errors && (
<p>{state.UI.errors.general}</p>
)}

I was trying to get input with redux,all is fine but I can't figure out how to get input values

Trying to get user input with action,all is working i get my console.logs about how inputVal changes,but when I try to print this in i get undefined in console
Should I use like mapDispatchToProps or I don't need this,since I'm passing actions as second param into mapStateToProps
actions:
export const inputChange = val => {
return {
type: INPUT_CHANGE,
payload: val
};
};
reducer:
import { INPUT_CHANGE } from './actionTypes';
const initialState = {
inputVal: ''
};
export default (state = initialState, action) => {
switch (action.type) {
case INPUT_CHANGE:
return {
...state,
inputVal: action.payload
};
default:
return state;
}
};
mainPage:
const mapStateToProps = state => {
console.log(state);
return state;
};
class MainPage extends Component {
onInput = e => {
this.props.inputChange(e.target.value);
console.log(this.props.inputChange(e.target.value));
};
render() {
console.log(this.props.inputVal);
return (
<div>
<input onChange={this.onInput}></input>
<p>{this.props.}</p>
</div>
);
}
}
export default connect(
mapStateToProps,
{
addToCart,
removeFromCart,
selectItem,
inputChange
}
)(MainPage);
combinedReducers:
import { combineReducers } from 'redux';
import AddItem from './addItem/reducer';
import InputReducer from './reducerInput';
export default combineReducers({
AddItem,
InputReducer
});
I've tried to this.props.inputVal.
Since you have combineReducers, you should use these keys to access in mapStateToProps.
From the redux docs:
The state produced by combineReducers() namespaces the states of each
reducer under their keys as passed to combineReducers()
You can control state key names by using different keys for the
reducers in the passed object. For example, you may call
combineReducers({ todos: myTodosReducer, counter: myCounterReducer })
for the state shape to be { todos, counter }.
So your mapStateToProps must be like:
const mapStateToProps = state => {
console.log(state);
return {
inputVal: state.InputReducer.inputVal
}
};
A minimal working code sandbox:
https://codesandbox.io/s/cold-meadow-pxtu3

Redux store doesn't update component props

I'm trying to call a API from my store to update the state of a component, here getting the price of a crypto-curency.
I use a clone of my state in return (nextState here) and the log of nextState is well fill with goods price, but my component get only the initialState.
Here the code :
My component
import React from 'react';
import { StyleSheet, Text, View, Button, ImageBackground,TouchableOpacity, Image } from 'react-native';
import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen';
import { connect } from 'react-redux'
class Bitcoin extends React.Component {
constructor(props){
super(props);
this.state = {
}
}
componentDidMount() {
const action = { type: 'PRICES', value: this.state.cryptos}
this.props.dispatch(action)
console.log(this.props.cryptos)
}
componentDidUpdate() {
console.log("Component did Update : ")
console.log(this.props.cryptos)
}
render() {
return (
<View>
<Text style={styles.title}>Bitcoin !</Text>
<Text> {this.props.cryptos[0].price} </Text>
</View>
)
}
}
const styles = StyleSheet.create({
title: {
marginTop: wp("10%")
},
});
const mapStateToProps = (state) => {
return {
cryptos: state.Crypto.cryptos
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatch: (action) => { dispatch(action) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Bitcoin)
My Reducer :
const initialState = { cryptos: [
{
title: "Bitcoin",
id: "BTC",
price: 0
}, {
title: "Ethereum",
id: "ETH",
price: 0
}, {
title: "Ripple",
id: "XRP",
price: 0
}], toast: 0}
function Crypto(state = initialState, action) {
let nextState
switch (action.type) {
case 'PRICES':
nextState = {...state}
fetch('https://min-api.cryptocompare.com/data/pricemulti?fsyms=ETH,BTC,XRP&tsyms=EUR&api_key=c3b60840403013f86c45f2ee97571ffdf60072fafff5c133ed587d91088451b6')
.then((response) => response.json())
.then((responseJson) => {
nextState.cryptos[0].price = responseJson.BTC.EUR.toString()
nextState.cryptos[1].price = responseJson.ETH.EUR.toString()
nextState.cryptos[2].price = responseJson.XRP.EUR.toString()
console.log("NextState :");
console.log(nextState.cryptos);
return nextState
})
.catch((error) => {
console.error(error);
});
return nextState
case 'TOAST':
nextState = {...state}
default:
return state
}
}
export default Crypto
Welcome to StackOverflow.
I guess you are new to Redux workflow. So here it is.
Actions describe an action. The reducer receive the action and specify how the store is changing.
Action must be plain javascript object. And reducer functions must be pure !
Here what is forbidden to do inside reducers :
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
In your example, by calling fetch. You're making an API Call.
I invite you to read this guide to know more about : How to introduce API call and asynchronous into your redux app. (https://redux.js.org/advanced/async-actions)

Redux action not dispatched. TypeError: Invalid attempt to spread non-iterable instance

In my application I want to add a 'ticket' to an array in the 'event' object. In the action I post the new ticket to the database, and after that I dispatch the action to the reducer. By using the Redux logger, I am able to retrieve the error:
The action of 'createTicket' is this:
// actions/tickets.js
export const TICKET_CREATE_SUCCESS = 'TICKET_CREATE_SUCCESS';
const ticketCreateSuccess = tickets => ({
type: TICKET_CREATE_SUCCESS,
tickets
});
export const createTicket = (eventId, data) => (dispatch, getState) => {
const jwt = getState().currentUser.token;
const id = getState().currentUser.userId;
const email = getState().currentUser.email;
const name = getState().currentUser.name;
request
.post(`${baseUrl}/events/${eventId}/tickets`)
.set('Authorization', `Bearer ${jwt}`)
.send(data)
.then(response => {
dispatch(ticketCreateSuccess({ ...response.body, user: { id, email, name } }));
})
.catch(error => error);
};
The reducer
// reducers/events.js
import { EVENT_FETCHED } from '../actions/events';
import { TICKET_EDIT_SUCCESS, TICKET_CREATE_SUCCESS } from '../actions/tickets';
export default (state = null, action = {}) => {
switch (action.type) {
case EVENT_FETCHED:
return action.event;
case TICKET_EDIT_SUCCESS:
return {
...state,
tickets: state.tickets.map(ticket => {
if (ticket.id === action.ticket.id) {
return action.ticket;
}
return ticket;
})
};
case TICKET_CREATE_SUCCESS:
console.log({ ...state, tickets: [...state.tickets, action.tickets] });
return { ...state, tickets: [...state.tickets, action.tickets] };
default:
return state;
}
};
The reducers are combined into :
import { combineReducers } from 'redux';
import currentUser from './currentUser';
import events from './events';
import event from './event';
import ticket from './ticket';
import tickets from './tickets';
import numberOfTickets from './numberOfTickets';
export default combineReducers({ currentUser, events, event, ticket, tickets, numberOfTickets });
Could it be that you're trying to spread your reducer state when its value is null:
export default (state = null, action = {}) => {
return {
...state, // Here
// rest
}
Your default state should probably be an object, e.g.:
const InitialState = {
tickets: []
};
export default (state = InitialState, action) => {
// Some code
case TICKET_CREATE_SUCCESS:
return {
...state,
tickets: [
...state.tickets,
action.tickets
]
}
}
just add this ...state || []
and you are good to go.
the problem is value of ...state equals null with empty array and when you try to iterate over null it creates an error.
so use and "OR" operator and it will work fine.

Redux: TypeError: e is undefined

https://github.com/reduxjs/redux/issues/3017
Problem: Occurs when I wrap my action creator with a dispatch in the container area where I utilize the connect method--I followed the style from redux documentation.
I am utilizing redux, and redux thunk. I am attempting to create a login action, so far it does not work when I dispatch an action, which dispatch's an another one.
LoginContainer.js
import CONFIG from "../../../config";
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {authenticateUser} from "../../../actions/authenticateUser";
import Login from '../../../components/views/login/Login'
import {store} from '../../../store';
function handleSubmit(e) {
e.preventDefault();
let calpersId = parseInt(e.target[0].value || e.target[1].value, 10) || 0;
store.dispatch(authenticateUser(calpersId))
}
const mapStateToProps = (state) => {
return {
authentication: state.authentication
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleSubmit: (e) => {dispatch(handleSubmit(e))}
}
}
const LoginContainer = connect(mapStateToProps, mapDispatchToProps)(Login);
export default LoginContainer;
authenticateUser.action.js
import CONFIG from '../config'
export const AUTHENTICATE_USER = 'AUTHENTICATE_USER'
export const initiateUserAuthentication = (token) => ({
type: AUTHENTICATE_USER,
token
})
export const AUTHENTICATATION_SUCCEEDED = 'AUTHENTICATATION_SUCCEEDED'
export const authenticatationSucceeded = (payload) => ({
type: AUTHENTICATE_USER,
payload
})
export const USER_ID_DOES_NOT_EXIST = 'USER_ID_DOES_NOT_EXIST'
export const userIdDoesNotExist = (uid) => ({
type: USER_ID_DOES_NOT_EXIST,
uid,
message: "User id does not exist"
})
export function authenticateUser(id) {
return function (dispatch) {
let guidMap = {
7103503579: "dad08fde-0ac1-404a-ba8a-cc7c76d5810f",
6632408185: "6632408185-guid",
6581985123: "6581985123-guid",
1226290314: "a3908aa7-c142-4752-85ea-3741cf28f75e",
4618604679: "4618604679-guid",
6452522440: "6452522440-guid",
3685610572: "3685610572-guid",
5564535492: "5564535492-guid",
5600493427: "5600493427-guid",
3996179678: "3996179678-guid",
7302651964: "7302651964-guid",
3148148090: "3148148090-guid",
5826752269: "5826752269-guid",
6827859055: "6827859055-guid",
1677401305: "1677401305-guid",
2640602392: "dbed1af6-0fc9-45dc-96a3-ab15aa05a7a2",
6474994805: "6474994805-guid"
};
let guid = guidMap[id]
return fetch(CONFIG.API.MY_CALPERS_SERVER.LOCATION + 'ept/development/rest/simulatedAuth.json?guid=' + guid, {
credentials: 'include'
})
.then(
response => response.json(),
error => console.log('An error occured.', error))
.then(json => {
document.cookie = "authentication=" + guid + "; max-age=" + (60 * 30);
dispatch(authenticatationSucceeded(json))
})
}
}
authenticateUser.reducer.js
import {AUTHENTICATE_USER, AUTHENTICATATION_SUCCEEDED} from "../actions/authenticateUser";
const initialState = {
calpersIds: [
5600493427,
6474994805,
6452522440,
5564535492,
6632408185,
4618604679,
5826752269,
3996179678,
7302651964,
1677401305,
6827859055,
3685610572,
6581985123,
3148148090
],
guidMap: {
7103503579: "dad08fde-0ac1-404a-ba8a-cc7c76d5810f",
6632408185: "6632408185-guid",
6581985123: "6581985123-guid",
1226290314: "a3908aa7-c142-4752-85ea-3741cf28f75e",
4618604679: "4618604679-guid",
6452522440: "6452522440-guid",
3685610572: "3685610572-guid",
5564535492: "5564535492-guid",
5600493427: "5600493427-guid",
3996179678: "3996179678-guid",
7302651964: "7302651964-guid",
3148148090: "3148148090-guid",
5826752269: "5826752269-guid",
6827859055: "6827859055-guid",
1677401305: "1677401305-guid",
2640602392: "dbed1af6-0fc9-45dc-96a3-ab15aa05a7a2",
6474994805: "6474994805-guid"
},
authToken: null,
isAuthenticated: false
};
//#TODO: All fetches, create a seperate reducer for store?
export function authenticateUser(state = initialState, action) {
switch(action.type) {
case AUTHENTICATE_USER:
return Object.assign({}, state, {
authToken: action.token,
})
case AUTHENTICATATION_SUCCEEDED:
return Object.assign({}, state, {
authToken: action.payload.guid,
isAuthenticated: true,
payload: action.payload
})
default:
return state;
}
};
You should'nt use connect mapDispatchToProps like you are doing.
This callback is supposed to create or use functions that will dispatch an action.
For your case you can use it like that:
const mapDispatchToProps = (dispatch) => {
return {
authenticate: calpersId => authenticateUser(calpersId)(dispatch)
}
}
And in your component have a function/method that handle the submit:
class Login extends Component {
...
handleSubmit = e => {
e.preventDefault();
const calpersId = parseInt(e.target[0].value || e.target[1].value, 10) || 0;
this.props.authenticate(calpersId)
}
...
By the way a reducer is supposed to represent the state of an entity. An entity named autenticateUser is pretty ambigious. You should propably named it user. You should read more redux examples to really catch the concept that at first a bit complicated to understand. There are good videos on Youtube.
Turns out I was calling an action creator which did not exist, I simply needed to pass my dispatch to the handler, and let it handle the the event.
Login.js
import CONFIG from "../../../config";
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {authenticateUser} from "../../../actions/authenticateUser";
import Login from '../../../components/views/login/Login'
function handleSubmit(e, dispatch) {
e.preventDefault();
let calpersId = parseInt(e.target[0].value || e.target[1].value, 10) || 0;
dispatch(authenticateUser(calpersId))
}
const mapStateToProps = (state) => {
return {
authentication: state.authentication
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleSubmit: (e) => {handleSubmit(e, dispatch)}
}
}
const LoginContainer = connect(mapStateToProps, mapDispatchToProps)(Login);
export default LoginContainer;
What is the proper way of doing this, I utillized bindActionCreators which yields the same result.

Resources