Multiple reducers for the same state - javascript

I have a redux application where i initially async fetch some messages from my server.
I've hooked up the app to a signalR connection, and when the user submits a message, i need to to be added to the Redux state.
However it seems like i cant update the state, or at least im doing it wrong.
When the server send a new message to the client i call my action addMessagesSignalR
store.dispatch(addMessagesSignalR(messageFromServer));
The action is
export function addMessagesSignalR(Obj) {
let messageFromServer = Obj;
return {
type: 'FETCH_MESSAGES_SIGNALR',
payload: messageFromServer
};
}
And my reducer is:
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_MESSAGES':
return Object.assign({}, state, action.payload);
case 'FETCH_MESSAGES_SIGNALR':
return Object.assign({}, state, action.payload)
}
return state;
}
Im trying to merge the state of the fetched messages with the message recieved from signalR in my rootReducer:
messages: fetchMessages

Object.assign is used to merge objects, but your state appears to be an array. You need to use different syntax to merge arrays.
ES6 (using spread syntax):
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_MESSAGES':
return [...state, action.payload];
case 'FETCH_MESSAGES_SIGNALR':
return [...state, action.payload]
}
return state;
ES5 (using Array.concat):
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_MESSAGES':
return state.concat(action.payload);
case 'FETCH_MESSAGES_SIGNALR':
return state.concat(action.payload)
}
return state;

Related

How to separate the query results on the same page

I have a page, which has two sections:
1) List of questions;
2) Top-voted list of questions
These two queries use the same call to the backend API, the only difference is I pass an additional parameter, e.g. showPopular=true in the latter case.
When I try to render data, whatever action retrieves last overwrites data in both sections. How do I distinguish such data?
I am using ReactJS, Redux.
This is to retrieve data:
query={showPopular:true}
this.props.actions.loadQuestions(accountId, query);
I have
const mapStateToProps = (state) => {
return {
error: state.questions.error,
questions: state.questions.questions|| [],
isLoading: state.questions.isLoading
};
}
You should at least separate state/prop/reducer because this two list are totally different, and they obviously will render different DOM.
The mapState should have different props mapping:
const mapStateToProps = (state) => {
return {
error: state.questions.error,
questions: state.questions.questions|| [],
topQuestions: state.questions.topQuestions,
isLoading: state.questions.isLoading
};
}
You didn't post the reducer but it should also separate the two action or by using condition to update different state:
export default (state = initialState, action) => {
switch (action.type) {
case LOAD_QUESTION:
if(action.query.showPopular){
return state.set('topQuestions', action.data);
}else{
return state.set('questions', action.data);
}
default:
return state;
}
};

Can I have multiple states from one reducer in Redux?

Let's say I have the next reducer:
export default (state = [], { type, payload }) => {
switch (type) {
case FETCH_POKEMONS:
const objeto = {};
payload.forEach((conexion) => {
objeto[conexion.name] = conexion
});
return objeto;
case FETCH_POKEMON:
return { ...state, ...payload }
default:
return state
}
}
And I will have a combineReducers like this:
export default combineReducers({
pokemons: pokemonReducers,
});
But I want to have pokemons state for the FETCH_POKEMONS actions and another state called pokemon for the FETCH_POKEMON acton. How can I derivate two states in the combineReducers from one reducer file?
This is anti pattern, the closest thing to do here would be export 2 reducers from your file, one for each use case
export const reducer1 = (state = [], { type, payload }) => {
switch (type) {
case FETCH_POKEMONS:
const objeto = {};
payload.forEach((conexion) => {
objeto[conexion.name] = conexion
});
return objeto;
default:
return state
}
}
export const reducer2 = (state = [], { type, payload }) => {
switch (type) {
case FETCH_POKEMON:
return { ...state, ...payload }
default:
return state
}
}
If I'm understanding your question correctly, you have two actions, FETCH_POKEMONS and FETCH_POKEMON, and you want them to update two different states, pokemons and pokemon, respectively.
If these are separate states that don't affect one-another, you'll want to create 2 reducer functions, which I'll call pokemon and pokemons, which each manage their own state and then pass those reducers into combineReducers to combine them into a single application-level reducer. I think this is more likely what you're looking for.
If they are not separate states but instead interconnected properties, then use a single reducer, and give the state 2 properties, pokemon and pokemons, and only update the property you are trying to update in each action (i.e. leave state.pokemon with its previous value when performing FETCH_POKEMONS.
Your action creator seems fine. I am going to post one of my reducers to show how I do it.
import {ONIX_LOGIN_LOADING,ONIX_LOGIN_SUCCESS,ONIX_LOGIN_FAILURE,
ONIX_CONNECTIONS_LOADING,ONIX_CONNECTIONS_SUCCESS,ONIX_CONNECTIONS_FAILURE,
ONIX_PRODUCT_LOADING,ONIX_PRODUCT_SUCCESS,ONIX_PRODUCT_FAILURE
} from "../actions/onix-actions";
const defaultState = {
login:[],
connections: [],
product: []
};
export default function(state = defaultState, action){
switch(action.type){
case ONIX_LOGIN_LOADING:
return {...state, loginLoading:true};
case ONIX_LOGIN_FAILURE:
return {...state, loginLoading:action.isLoaded};
case ONIX_LOGIN_SUCCESS:
return {...state, loginLoading:false, login:action.data};
case ONIX_CONNECTIONS_LOADING:
return {...state, connectionsLoading:true};
case ONIX_CONNECTIONS_FAILURE:
return {...state, connectionsLoading:false};
case ONIX_CONNECTIONS_SUCCESS:
return {...state, connectionsLoading:false, connections:action.data};
case ONIX_PRODUCT_LOADING:
return {...state, productLoading:true};
case ONIX_PRODUCT_FAILURE:
return {...state, productLoading:false, productTitle:false};
case ONIX_PRODUCT_SUCCESS:
return {...state, productLoading:false, product:action.data};
}
return state
}
I like this format, because I can call my own variables off of the state for that part of my reducer. Then in the combine reducers I have the following:
import books from './books';
import onix from './onix';
const rootReducer = combineReducers({
books,
onix
});
export default rootReducer;
Now for all things onix I can call:
this.props.onix.login
or
this.props.onix.productTitle
and it will return the data I want for that part of my project. Did that answer your question?
EDIT: Here is the screenshot of my file structure for reducers

React reducer that does not update the state

From a Redux tutorial I've been going through they allow you to add a place multiple times. I changed the reducer to reject duplicates. My question is, (see code), do I have to return the state if no updates are made or is there some other way of indicating no state is changed?
function placeReducer(state = initialState, action) {
switch (action.type) {
case ADD_PLACE:
const existing = state.places.find((item) => item.value == action.payload);
if (existing) {
return {...state};
}
return {
...state,
places: state.places.concat({
key: Math.random(),
value: action.payload
})
};
default:
return state;
}
}
Just return the state, no need to create a new copy.

Firebase + React Reducers seemingly getting mixed info

I've run into an odd issue, where my redux store seems to be returning a duplicate of a different value? (Still learning terms so sorry if I mixed them up!)
I have 2 states. Users, and Added. I want to show to lists, one using the data from each one of them. currently, fetchUsers works fine, but fetchAdded shows Users for an unknown reason so both lists show the same data.
If I switch fetchUsers to use refAdded then it shows Added, so now it only shows the added array in both lists. I figured that means the actual calls are working cause it can get the data from Firebase, but I don't know why this would happen.
FetchUsers which gets a list of users from firebase looks like this:
export function fetchUsers() {
return (dispatch) => {
refUsers.on('value', snapshot => {
dispatch({
type: 'FETCH_USER',
payload: snapshot.val()
});
});
}
}
FetchAdded looks like this:
export function fetchAdded() {
return (dispatch) => {
refAdded.on('value', snapshot => {
dispatch({
type: 'FETCH_ADDED',
payload: snapshot.val()
});
});
}
}
The reducers look like this:
export default function(state = [], action) {
switch (action.type) {
case 'FETCH_USER':
return [action.payload];
case 'ADDED_USER':
return [action.payload, ...state];
case 'MOVE_USER':
const newState = [...state];
newState.splice(action.payload.index, 1);
return newState;
case 'MOVE_ITEM':
return [action.payload.user, ...state];
default:
return state
}
}
and fetch Added is:
export default function(state = [], action) {
switch (action.type) {
case 'FETCH_ADDED':
return [action.payload];
case 'MOVE_ITEM':
const newState = [...state];
newState.splice(action.payload.index, 1);
return newState;
case 'MOVE_USER':
return [action.payload.user, ...state]
default:
return state
}
}
I combine them both here:
const rootReducer = combineReducers({
users: UserReducer,
added: AddedReducer
});
and my firebase client exporting looks like this:
firebase.initializeApp(config);
export const refUsers = firebase.database().ref("users")
export const refAdded = firebase.database().ref("added")
export const auth = firebase.auth
export const provider = new firebase.auth.GoogleAuthProvider();
In my actual page where I display the 2 lists, this is what I have:
function mapStateToProps(state) {
return {
users: state.users,
added: state.added
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addUser, moveUser, moveItem, fetchUsers, fetchAdded }, dispatch);
}

How to Update specific slice of redux state in React

I seem to have hit a snag when updating state using redux and react-redux. When I update an individual slice of state, all of the others get removed. I know the answer to this will be simple but I can't figure it out and haven't found anything else online.
So to clarify, here's my reducer:
const initialState = {
selectedLevel: null,
selectedVenue: null,
selectedUnitNumber: null,
selectedUnitName: null,
selectedYear: null
}
export default (state = initialState, action) => {
console.log('reducer: ', action);
switch (action.type){
case 'CHOOSE_LEVEL':
return action.payload;
case 'CHOOSE_VENUE':
return action.payload;
case 'CHOOSE_UNIT':
return action.payload;
case 'SHOW_COURSES':
return action.payload;
}
return state;
}
And my combine reducer:
export default combineReducers({
workshopSelection: WorkshopSelectReducer
});
So my initial state looks like this:
workshopSelection: {
selectedLevel: null,
selectedVenue: null,
selectedUnitNumber: null,
selectedUnitName: null,
selectedYear: null
}
But when I use one of my action creators, for example:
export function chooseVenue(venue){
return {
type: 'CHOOSE_VENUE',
payload: {
selectedVenue: venue
}
}
}
I end up with state looking like this:
workshopSelection: {
selectedVenue: 'London',
}
All of the rest of the state within this object that wasn't affected by this action creator has been completely wiped out. Instead, I just want all other entries to stay as they are with their original values - null in this example, or whatever other value has been assigned to them.
Hope that all makes sense.
Cheers!
You are basically replacing one object (previous state) with another one (your payload, which is also an object).
In terms of standard JS, this would be the equlivalent of what your reducer does:
var action = {
type: 'CHOOSE_VENUE',
payload: {
selectedVenue: venue
}
};
var state = action.payload;
The simplest way to fix this would be using Object spread properties:
export default (state = initialState, action) => {
switch (action.type){
case 'CHOOSE_LEVEL':
case 'CHOOSE_VENUE':
case 'CHOOSE_UNIT':
case 'SHOW_COURSES':
// Watch out, fall-through used here
return {
...state,
...action.payload
};
}
return state;
}
... but since this is still in experimental phase, you have to use some other way to clone previous properties and then override the new ones. A double for ... in loop could be a simple one:
export default (state = initialState, action) => {
switch (action.type){
case 'CHOOSE_LEVEL':
case 'CHOOSE_VENUE':
case 'CHOOSE_UNIT':
case 'SHOW_COURSES':
// Watch out, fall-through used here
const newState = {};
// Note: No key-checks in this example
for (let key in state) {
newState[key] = state[key];
}
for (let key in action.payload) {
newState[key] = action.payload[key];
}
return newState;
}
return state;
}
Keep your payload object as flat on actions creators as shown below...
export function chooseVenue(venue){
return {
type: 'CHOOSE_VENUE',
selectedVenue: venue
}
}
and modify your reducer as below (given example is for updating the venue, do the same for other cases too...)
export default (state = initialState, action) => {
let newState = Object.assign({}, state); // Take copy of the old state
switch (action.type){
case 'CHOOSE_LEVEL':
case 'CHOOSE_VENUE':
newState.selectedVenue = action.selectedVenue; // mutate the newState with payload
break;
case 'CHOOSE_UNIT':
case 'SHOW_COURSES':
default :
return newState;
}
return newState; // Returns the newState;
}

Categories