I'm wondering what is the right way to delete a nested field in redux actions. For example, I have this code:
const SUBSCRIBE = 'SUBSCRIBE';
const UNSUBSCRIBE = 'UNSUBSCRIBE';
export default function reducer(state = {}, action) {
const {
productName,
products,
componentName
} = action;
switch (action.type) {
case UNSUBSCRIBE: {
if (state[productName]?.[componentName]) {
const newState = { ...state };
delete newState[productName][componentName];
return newState;
} else {
return state;
}
}
default:
return state;
}
}
export function unsubscribe(productName, componentName) {
return {
type: UNSUBSCRIBE,
productName,
componentName
};
}
In UNSUBSCRIBE action I delete newState[productName][componentName] field, however this will also delete the field on the "old" state. So theoretically if there're other actions which use this field it may be lost for them because the state is mutated. Should I deep copy old state into newState and then delete newState[productName][componentName]?
You can do one of two:
create a copy of a productName state and delete componentName from that copy
if (state[productName]?.[componentName]) {
const newProductState = { ...state[productName] };
delete newProductState[componentName];
return {
...state,
[productName]: newProductState
};
} else {
return state;
}
Instead of deletion, you can mark the componentName as undefined (which I would personaly prefer to do)
if (state[productName]?.[componentName]) {
return {
...state,
[productName]: {
...state[productName],
[componentName]: undefined,
},
};
} else {
return state;
}
Related
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.
I am currently using React-Redux but for a pretty simple app.
The app just simply takes a user ID, password, and an address of a server that the user wants to get into. It gets into the server and runs a script in the server. But the functionality of the app is not important in my question.
I only need 3 states (username, password, and server_address) for the app.
However, I have three different reducers and actions that do the same thing just with the different state.
For example,
userReducer.js
// reducer functions takes a default state and an action to apply
import { UPDATE_USER } from '../actions/userActions'
export default function userReducer(state = '', { type, payload }) {
switch (type) {
case UPDATE_USER:
return payload;
default:
return state;
}
}
passwordReducer.js
// reducer functions takes a default state and an action to apply
import { UPDATE_PASSWORD } from '../actions/passwordActions'
export default function passwordReducer(state = '', { type, payload }) {
switch (type) {
case UPDATE_PASSWORD:
return payload;
default:
return state;
}
}
routerReducer.js // this is the server
// reducer functions takes a default state and an action to apply
import { UPDATE_ROUTER } from '../actions/routerActions'
export default function routerReducer(state = '', { type, payload }) {
switch (type) {
case UPDATE_ROUTER:
return payload;
default:
return state;
}
}
and actions that look like this:
export const UPDATE_PASSWORD = 'updatePassword'
export function updatePassword(newPassword) {
return {
type: UPDATE_PASSWORD,
payload: {
'newPassword': newPassword
}
}
}
It's same for the other two with the different variable.
Then in my component, I just connected mapActionsToProps to the component and put 3 functions that does the same thing (updating the state)
class Container extends React.Component {
constructor(props) {
super(props)
}
onUpdateUser = (e) => {
this.props.onUpdateUser(e.target.value)
}
onUpdatePassword = (e) => {
this.props.onUpdatePassword(e.target.value)
}
onUpdateRouter = (e) => {
this.props.onUpdateRouter(e.target.value)
}
...
using it like
This kinda works, but I am not sure if this is the right way to use React-Redux. First of all, they are duplicates and do not seem like a good practice. However, I can't think of a way to update each state in a React-Redux way without just putting similar codes.
Any help?
You could pass the event to your action.js
export const onInputChange = event => ({
type: 'UPDATE_INPUT',
payload: event
});
And simply grab the name and the value of the event in your reducer.js
const INITIAL_STATE = {
user: '',
password: ''
}
export const inputReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case: 'UPDATE_INPUT':
return { ...state, [action.payload.target.name]: action.payload.target.value };
default:
return state;
};
};
Then in your component
// ...
handleChange(event) {
this.props.onInputChange(event);
};
// ...
<input type='text' name='user' onChange={this.handleChange} />
You can use a single function just to send the key/value pairs you want to update.
export const UPDATE_USER_VALUE = 'updateUserValues'
export function updateUser(payload) {
return {
type: UPDATE_USER_VALUE,
payload: payload,
}
}
You must call that function like this:
onUpdateUser = (e) => {
this.props.onUpdateUser({
key: 'name',
value: e.target.value
})
}
onUpdatePassword = (e) => {
this.props.onUpdateUser({
key: 'password',
value: e.target.value
})
}
Then just update the values.
import { UPDATE_USER_VALUE } from '../actions/userActions'
const defaultState = {
username = '',
password = '',
server_address = ''
};
export default function passwordReducer(state = defaultState, { type, payload }) {
switch (type) {
case UPDATE_USER_VALUE:
return {
...state,
state[payload.key]: payload.value
};
default:
return state;
}
}
In my react/redux application have the following actions
export function getSeatingChartConfiguration(team) {
return function(dispatch) {
ref.child(team.key).once('value').then(function(snapshot) {
dispatch(loadSeatingChart(snapshot.val()));
});
};
}
export function saveSeatingChartSection(key, sectionData){
return function(dispatch) {
ref.child(key).once('value').then(function(snapshot) {
let data = snapshot.val();
let sections = data.sections;
let index = snapshot.val().sections.map( (el) => el.name).indexOf(sectionData.name);
if(index !== -1) {
sections[index] = sectionData;
} else {
sections.push(sectionData);
}
data.sections = sections;
ref.child(key).update(data, function(error) {
dispatch(loadSeatingChart(data));
});
});
};
}
Here is the reducer
export default function seatingChart(state = {}, action) {
switch(action.type) {
case actionTypes.LOAD_SEATING_CHART_CONFIGURATION:
return action.seatingChartConfiguration;
default:
return state;
}
}
I am not getting any errors when getSeatingChartConfiguration() is called but I have receiving Error: A state mutation was detected between dispatches, in the pathseatingChart.sections.2.points.What do I need to change in my action or reducer to not mutate the state.
Use Object.assign with initial state to avoid state mutation like below,
const initialState = { seatingChartConfiguration: [] }
export default function seatingChart(state = initialState, action) {
switch(action.type) {
case actionTypes.LOAD_SEATING_CHART_CONFIGURATION:
return Object.assign({}, state, { seatingChartConfiguration: action.seatingChartConfiguration });
default:
return state;
}
}
I'm setting up actions and reducers in my react-redux app. I need a function to update a property in the state and add objects to its list, if possible with the spread syntax. Here's what I have so far:
const defaultState = {
genres: {}
}
export default function(state = defaultState, action) {
switch(action.type) {
case 'ADD_GENRE':
return {
...state,
genres[action.name]: action.list //new code here
}
default:
return state;
}
}
I need the genres property to be dynamically accessible like an array using its property name like so:
const getMusicFromGenre = (genre) => {
return state.genres[genre];
}
The reducer should accept the following action, then modify the state accordingly:
// action
{
type: 'ADD_GENRE,
name: 'Rock',
list: ['Bohemian Rhapsody', 'Stairway to Heaven', 'Hotel California']
}
// old state
{
genres: {
"Pop": ['Billie Jean', 'Uptown Funk, 'Hey Jude']
}
}
// new state
{
genres: {
"Pop": ['Billie Jean', 'Uptown Funk, 'Hey Jude'],
"Rock": ['Bohemian Rhapsody', 'Stairway to Heaven', 'Hotel California']
}
}
I'm willing to use a different approach if necessary.
You're on the right track, but need to handle each level of nesting separately. Here's an example I wrote for http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html :
function updateVeryNestedField(state, action) {
return {
....state,
first : {
...state.first,
second : {
...state.first.second,
[action.someId] : {
...state.first.second[action.someId],
fourth : action.someValue
}
}
}
}
}
You may also want to read some of the articles on immutable data handling that I have linked at http://redux.js.org/docs/recipes/reducers/PrerequisiteConcepts.html#immutable-data-management and https://github.com/markerikson/react-redux-links/blob/master/immutable-data.md .
immutability-helper is a very useful library for doing state updates. In your situation it would be used like this, which will create a new array if there are no existing items, or concat the existing items with the action's list if there are pre-existing items:
import update from 'immutability-helper';
const defaultState = {
genres: {}
}
const createOrUpdateList = (prev, list) => {
if (!Array.isArray(prev)) {
return list;
}
return prev.concat(list);
// or return [...prev, ...list] if you prefer
}
export default function(state = defaultState, action) {
switch(action.type) {
case 'ADD_GENRE':
return update(state, {
genres: {
[action.name]: {
$apply: prev => createOrUpdate(prev, action.list)
}
}
});
default:
return state;
}
}
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;
}