Accessing a nested key inside reducer - javascript

hard for me to word this question. but how do I access a nested key inside redux? I'd like to apply the spread operator to the key "appointment" in 'FETCH_APPOINTMENT, but i don't know how to access it. Thanks
what i tried
case 'FETCH_APPOINTMENT':
return { ...state, appointment: action.payload.data };
case 'SOME_CASE':
return {
[state && state.appointment]: state && state.appointment,
..._.mapKeys(action.payload.data, 'id'),
};
result
{undefined: undefined}
the results are them not being able to define state.appointment

This should work. You can remove the state && state.appointment truthy check by adding a default value for appointment in your reducers initialState:
const initialState = {
appointment: 'A'
};
const reducers = (state = initialState, action) => {
switch(action) {
case "SOME_CASE": {
return {
[state.appointment]: {
...[state.appointment],
_.mapKeys(action.payload.data, 'id')
}
}
}
}
}

Related

Which method to use when updating state inside React.useReducer

Lets imagine a simple reducer:
const [state, setState] = React.useReducer(myReducer, {})
And myReducer with one single case (simplified version):
const myReducer = (state, action) => {
switch (action.type) {
case 'UPDATE_STATE': {
// Code to update here...
}
}
}
Now, which of the following statements is best suited to update a state - and why:
NOTE: payload = state.
METHOD 1
case 'UPDATE_STATE': {
const updatedState = action.payload
updatedState.object = true
return updatedState
}
METHOD 2
case 'UPDATE_STATE': {
const updatedState = action.payload
updatedState.object= true
return { ...state, updatedState }
}
METHOD 3
case 'UPDATE_STATE': {
const updatedState = action.payload
updatedState.object= true
return { ...state, ...updatedState }
}
METHOD 4
case 'UPDATE_STATE': {
return ({ ...state, object: true })
}
I think you should Use method 4. You are not mutating the state here, which is the React way.
case 'UPDATE_STATE': {
return ({ ...state, object: true })
}
Note: Spread operator ... creates different references up to one level only.
EDIT: Based on comments, here is a simple example of how you could be mutating state, even with the simplest of objects if you are not using ....
let state = { object : true};
let payload = state;
state.object = false;
console.log(state);
console.log(payload);
Even with ... you might have deeply nested objects. They will have the same problem, unless you want to spread at every level. There are libraries to help you with deep objets like immutable.js.

FindIndex() removing items from redux store

when using RTK Query I tried to build a remove and add to favorite feature. I'm having trouble as I can add to the state. but then the remove section, on .FindIndex() is always returning -1 Have it incorrectly used this function?
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
value: false,
cardFavId: [],
};
export const favouriteSlice = createSlice({
name: "favourite",
initialState,
reducers: {
makeFav: (state, action) => {
state.value = !state.value;
state.cardFavId = [...state.cardFavId, action.payload];
},
removeFav: (state, action) => {
console.log(action.payload, "payload remove pressed");
const index = state.cardFavId.findIndex(
(cardItem) => cardItem.id === action.payload
);
let newCardFav = [...state.cardFavId];
console.log(`Index Value ${index}`);
if (index >= 0) {
//itemCard has been Faved.. remove it.
newCardFav.splice(index, 1);
} else {
// Do nothing
console.warn("Cannot remove as its not been fav ");
}
state.cardFavId = newCardFav;
},
},
});
// Action creators are generated for each case reducer function
export const { makeFav, removeFav } = favouriteSlice.actions;
export default favouriteSlice.reducer;
First of all, your findIndex callback is incorrect, because you need to compare the item's id to action.payload.id and not just the action.payload, because payload is an object.
Second, an easier way to remove an item from an array (although less efficient sometimes, but it's easier to write) is to go something like
state.cardFav = state.cardFav.filter(cardItem => cardItem.id !== action.payload.id)
Meaning, you only keep the items with an id that's different than the one you want to delete

How can I get my Redux useSelector to update on store change?

I'm using the Redux Toolkit and I'm struggling to find a way to update state within my store that also triggers a reassignment for useSelector.
const slice = createSlice({
name: "state",
initialState: [],
reducers: {
addToArray: (state, action) => {
state.push(action.payload); // This updates the store but doesn't respect immutability?
}
}
});
I'm aware the above isn't entirely correct, and that something like
state = [...state, ...action.payload]
would be better, BUT for some reason I couldn't get it work correctly any other way. I'm simply trying to add an object to the array.
My component:
export default function App() {
const array = useSelector(selectArray);
return (
{array.map((x) => {
<div>{x.text}</div>
})
)
}
The issue is, whenever the dispatch is called, array doesn't update, which I'd like it to.
I think your issue is the way you push the new value into the array. That is not immutable and it appears the selector is detecting that the array hasn't changed, so it returns the previous value.
Try this:
const slice = createSlice({
name: "state",
initialState: [],
reducers: {
addToArray: (state, action) => {
state = [ ...state, action.payload ];
}
}
});
This demo should simulate what happens when mutably changing state vs immutably changing state.
const state = {
list: [1]
}
const addItemMutable = (item) => {
const prevState = { ...state }
state.list.push(item)
// Using JSON.stringify for better readability in output.
console.log(JSON.stringify(prevState.list), JSON.stringify(state.list))
console.log(prevState.list === state.list)
}
const addItemImmutable = (item) => {
const prevState = { ...state }
state.list = [ ...state.list, item ]
// Using JSON.stringify for better readability in output.
console.log(JSON.stringify(prevState.list), JSON.stringify(state.list))
console.log(prevState.list === state.list)
}
addItemMutable(2)
addItemImmutable(3)

How can I filter by ID and by name and city in react native with redux?

If I fetch this array of restos with redux:
[{
res_id: Int,
res_name: String,
res_category: String,
res_category_id: Int,
city_id: Int
}]
My action looks something like this:
export const getrestos = () => {
const resData = await response.json();
dispatch({
type: GET_RESTOS,
payload: resData
});
};
};
export const setFilters = filterSettings => {
console.log(filterSettings);
return { type: SET_FILTERS, filters: filterSettings };
};
And this is my reducer:
import { GET_RESTOS, SET_FILTERS } from '../actions/restos';
const initialState = {
restoList: [],
filteredRestos: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_RESTOS:
return {
restoList: action.payload
}
case SET_FILTERS:
const appliedFilters = action.filters;
const updatedFilteredRestos = state.restoList.filter(resto => {
if (appliedFilters.cityID || resto.city_id) {
resto => resto.city_id.indexOf(cityID) >= 0
return { ...state, filteredRestos: updatedFilteredRestos };
}
});
return { ...state, filteredRestos: updatedFilteredRestos };
default:
return state;
}
};
I have touchable categorys in a page, and when i touch one i want to fetch the corresponding restos for that category and show them in a flatlist. Apart from that i want to have a search bar that when I type I want to show restos by res_name and/or by res_category.
Ive tried to create selectors, but I dont understand how, i dont need an specific approach, but the most clean or efficient as possible.
Thanks in advance if anyone can give me a hint or solution!
EDIT
The problem is im getting undefined in updatedFilteredRestos.
Your reducers should be clean, dumb and all they do should be returning objects. This makes your components more testable and errors easier to catch. In my opinion, this is a perfect use-case for reselect. Here's a medium article: https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c But the true beauty of reselect is that it will memoize for you, i.e. if your states don't change, it uses a cached version of the data.
Anyway, you should clean up your restoReducer to something to this effect.
import { GET_RESTOS, SET_FILTERS } = "../actions/restos";
const initialState = {
restoList: [],
filteredRestos: []
};
const restoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_RESTOS:
return { ...state, restoList: action.payload };
case SET_FILTERS:
return { ...state, filteredRestos: action.payload };
default:
return state;
}
}
Then write your filtered resto selector:
// ../selectors/restos
import { createSelector } from "reselect";
// First, get your redux states
const getRestos = (state) => state.restos.restoList;
const getFilteredRestos = (state) => state.restos.filteredRestos;
// Next, create selectors
export const getFilteredRestoList = createSelector(
[getRestos, getFilteredRestos],
(restoList, filteredRestos) => {
// need to check for non-empty filters
// if it is, simply return the unfiltered `restoList`
if(!Array.isArray(filteredRestos) || !filteredRestos.length)
return restoList || [];
// If you do have valid filters, return filtered logic
return restoList.filter(r => filteredRestos.some(f => f.cityID === r.city_id));
);
Then, use this selector in your components:
// ../components/my-app
import { getFilteredRestoList } from "../selectors/restos";
// hook it up to your `mapStateToProps` as you would a normal state
// except this time, it's a special selector
const mapStateToProps = (state, ownProps) => {
restoList: state.restos.restoList,
filteredRestos: state.restos.filteredRestos,
filteredRestoList: getFilteredRestoList(state) //<-- this is your selector
}
Then inside your component, just reference it: this.props.filteredRestoList.

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