I have children list (reducer) and families list (reducer). The families list contains array of children.
How to update the child in the family's array of children when update a child from the children list using Redux?
This is my children reducer update case:
export function children(state = [], action) {
// ...
case types.UPDATE_CHILD: {
const childIndex = state.findIndex(child => child.id === action.id);
state[childIndex] = action.updatedChild;
return state;
}
// ...
}
When redux action fired, by default it goes through all of the reducers.
In general reducer architecture, we use something like { switch } syntax, and if the reducer don't have a responded { case } to the action, the action will go through { default } option in reducer.
const actionA = payload => (dispatch, getState) => dispatch({ type: 'a', payload })
const reducerA = (state = {}, action) => {
switch (action.type) {
case 'a':
// action will be reduced here
return action.payload
default:
return state;
}
}
const reducerB = (state = {}, action) => {
switch (action.type) {
case 'b':
return action.payload
default:
// action will be reduced here
return state;
}
}
Therefore, just add { types.UPDATE_CHILD } case at family reducer.
const reducerB = (state = {}, action) => {
switch (action.type) {
case 'b':
return action.payload
case 'a':
// now action will be reduced here
return action.payload
default:
return state;
}
}
Another thing about your reducer, that is you manipulate state directly at the reducer and not at the return, change it to -
case types.UPDATE_CHILD: {
return state.filter(child => child.id === action.id ? action.updatedChild : child);
}
Related
populate my initialState with json api call from a component or from here
const initialState = {
myvalues: [] ---->here i want to populate this array
};
const reducer = (state = initialState, action) => {
const newState = { ...state };
switch (action.type) {
case "Update":
console.log(newState);
// newState.myvalues = action.key.title.value;
default:
return newState;
}
};
export default reducer;
To populate your initialState with data from an API, you can create e.g. an FETCH_INIT_DATA_ACTION, which get's dispatched right after you initialised your store.
// ...
const store = createStore(/* ... */)
store.dispatch({ type: 'FETCH_INIT_DATA_ACTION' })
// ...
whereas FETCH_INIT_DATA_ACTION triggers a redux-thunk, saga, effect or whatever you want to use.
You can do it like this:
const reducer = (state = initialState, action) => {
switch (action.type) {
case "Update":
return { ...state, myvalues: action.payload }
default:
return state;
}
};
And when you dispatch it you should put your API data in payload.
I am trying to use stackoverflow api to make my first react redux project. I need to maintain a state like the following:
{
selectedTag: reactjs,
selectedSortOrder: activity,
items:[]
}
My reducer is given below:
const initialState = {
selectedTag: 'C#',
selectedSortOrder: 'activity', items: []
}
function SelectTag(state = initialState, action) {
switch (action.type) {
case SELECTTAG:
// console.log(state);
return Object.assign({}, state, { selectedTag: action.selectedTag });
default:
return state;
}
}
function SelectSortOrder(state = initialState, action) {
switch (action.type) {
case SELECTSORTORDER:
//console.log(state);
return Object.assign({}, state, { selectedSortOrder: action.selectedSortOrder });
default:
return state;
}
}
function ReceivePosts(state = { items: [] }, action) {
switch
(action.type) {
case RECEIVESORTEDPOSTS:
case RECEIVEPOST:
console.log(state);
return Object.assign({}, state, { items: action.items })
default:
return state
}
}
const rootReducer = combineReducers({ ReceivePosts, SelectTag, SelectSortOrder })
And mapStateToProps is:
const mapStateToProps = (state) => {
const selectedTag = state.SelectTag.selectedTag;
const items = (state.ReceivePosts.items);
const tags = (state.ReceiveTags.tags);
const selectedSortOrder = state.SelectSortOrder.selectedSortOrder;
return {selectedTag, items, tags, selectedSortOrder};
}
I have 2 problems here:
a. State does not remember all the data. For eg. suppose I select the tag first and then get items, my state has only items. SelectedTag is not set in the state.
b. I am not sure why mapStateToProps needs the reducer name. Eg: const selectedTag = state.SelectTag.selectedTag;
Actually it should be state.selectedTag. But my code expects the reducer name "SelectTag" to fetch the state value.
What am I doing wrong?
You haven't configured your reducers correctly. The initialState is assigned to all of your reducers which isn't required
const initialState={
selectedTag:'C#',
selectedSortOrder:'activity',
items:[]
}
function SelectTag(state = initialState.selectedTag, action){
switch(action.type){
case SELECTTAG:
return action.selectedTag
default:
return state;
}
}
function SelectSortOrder(state = initialState.selectedSortOrder, action){
switch(action.type){
case SELECTSORTORDER:
return action.selectedSortOrder
default:
return state;
}
}
function ReceivePosts(state = {items:[]}, action){
switch(action.type){
case RECEIVESORTEDPOSTS:
case RECEIVEPOST:
console.log(state);
return Object.assign({}, state, {items:action.items})
default:
return state
}
}
const rootReducer = combineReducers({ReceivePosts, SelectTag, SelectSortOrder})
And in mapStateToProps you would use it like
const mapStateToProps = (state) => {
const selectedTag = state.SelectTag;
const items = (state.ReceivePosts.items);
const tags = (state.ReceiveTags.tags);
const selectedSortOrder = state.SelectSortOrder;
return {selectedTag, items, tags, selectedSortOrder};
}
1. Try this code change
const initialState = {
selectedTag: 'C#',
selectedSortOrder: 'activity',
items: []
}
function SelectTag(state = initialState.selectedTag, action) {
switch (action.type) {
case SELECT TAG:
return {
...state,
selectedTag: action.selectedTag
}
default:
return state;
}
}
function SelectSortOrder(state = initialState.selectedSortOrder, action) {
switch (action.type) {
case SELECTSORTORDER:
return {
...state,
selectedSortOrder: action.selectedSortOrder
}
default:
return state;
}
}
function ReceivePosts(state = { items: [] }, action) {
switch (action.type) {
case RECEIVESORTEDPOSTS:
case RECEIVEPOST:
return {
...state,
items: action.items
}
default:
return state
}
}
const rootReducer = combineReducers({ ReceivePosts, SelectTag, SelectSortOrder });
2. I am not sure why mapStateToProps needs the reducer name. Eg: const selectedTag = state.SelectTag.selectedTag;
Its because when you use combinereducers, you are combining multiple slices of data, then you need to specify the slice from which you want to fetch the data.
const rootReducer = combineReducers({
receivePosts = ReceivePosts,
selectTag = SelectTag,
selectSortOrder = SelectSortOrder
});
Issue: You have not configured your initialstate properly, you are using the same initialstate in SelectTag and also in SelectSortOrder, if the initial state is same then why do you need two reducers?
Is it okay to add more info to an action so component specific reducers (and sagas/whatever side effect lib you're using) can filter them?
Example:
function reducerComponentA(state, action) {
switch (action.type) {
case START_FETCH:
return {
...state,
isLoading: true,
};
break;
case START_FETCH_SUCCESS:
return {
...state,
isLoading: false,
};
break;
}
return state;
}
and
function reducerComponentB(state, action) {
switch (action.type) {
case START_FETCH:
return {
...state,
isLoading: true,
};
break;
case START_FETCH_SUCCESS:
return {
...state,
isLoading: false,
};
break;
}
return state;
}
Notice how both reducers observes the same action and act on them (show a loading animation). Now if the screen/component that these reducers are related to are both in memory, the START_FETCH will cause to both of them to show the loading animation, maybe even overlapping (because it's global). Is filtering actions by screen/component a good solution?
Like this:
function reducerComponentA(state, action) {
if (action.currentScreen === 'ScreenA') {
switch (action.type) {
...
}
}
return state;
}
This seems to more of a problem on React Native, because if you're using a Navigator, there's a chance multiple screens will be loaded at the same time.
You can 'mount' reducer to the different slices of the state. To achieve this, you can add path to the action, and in the reducer, update corresponding slice of the state.
It can be similar to:
function reducer(state, action) {
if (action.type === '...') {
return _.set(_.deepClone(state), `${action.path}.isLoading`, false)
} else return state;
}
In other words, action determines which part of the state reducer will be operating with.
Note that this example above is extremely inefficient and only for demo purpose. Instead of cloning the state, some immutability helpers should be used: kolodny/immutability-helper, mweststrate/immer, other.
UPD
Imagine you have action and reducer for an input state:
const UPDATE_VALUE = 'UPDATE_VALUE';
const updateValue = (value) => ({ type: UPDATE_VALUE, value })
function reducer(state, action) {
if (action.type === UPDATE_VALUE) {
return { ...state, input: action.value }
} else return state;
}
And you want to use this action/reducer for many different inputs. The action can be supplied with
a property path that indicates which part or the state should be updated, and eventually which input
will receive new props:
const UPDATE_VALUE = 'UPDATE_VALUE';
const updateValue = (value, path) => ({ type: UPDATE_VALUE, value, path })
function reducer(state, action) {
if (action.type === UPDATE_VALUE) {
return { ...state, [action.path]: action.value }
} else return state;
}
This can be used then:
dispatch(updateValue(event.target.value, 'firstNameInput'))
dispatch(updateValue('Doe', 'lastNameInput'))
The code at the beginning of the answer is a generic version of the latter.
I have a reducer that looks like this:
const chart = combineReducers({
data,
fetchProgress,
fetchError,
updateProgress,
updateError,
});
I now would like to not only a chart but multiple charts.
const charts = (state = {}, action = {}) => {
if (action.type == FETCH_CHART || action.type == ...) {
let newChart = chart(state[action.id], action);
return Object.assign({}, state, {[action.id]: newChart});
}
return state;
}
Is there something conceptually wrong to do this?
If no, is there a better way to achieve the same result?
There is nothing wrong with the concept. In fact, I'd say this is my preferred approach when needing to store similar data in the redux store
To improve it, you could wrap it in a higher-order reducer to handle the id part of it. Something like:
const handleIds = (reducer) => (state = {}, action) => {
if (action.id) {
let idState = state[action.id]
let newState = reducer(idState, action)
if (newState !== idState) {
return { ...state, [action.id]: newState }
}
}
return state
}
This will pass on any action with an id and merge the resulting state into it's state with that id as it's key, if the state has changed.
Then your reducer becomes:
const singleChart = (state = {}, action = {}) => {
if (action.type == FETCH_CHART || action.type == ...) {
let newChart = chart(state, action);
return newChart;
}
return state;
}
const charts = handleIds(singleChart)
Then combine it into your store:
const chart = combineReducers({
data,
fetchProgress,
fetchError,
updateProgress,
updateError,
charts
});
Personally I would breakdown the logic to further sub reducers in order to have a better separation of concerns. In case you will add multiple charts and in case you will need to add more logic/settings/data to your actions, you will end up to modify too much your single reducer.
I follow with a small example where you could have 3 charts.
// bubbleChartReducer.js
export function bubble (state = {}, action) {
switch (action.type) {
case 'FETCH_BUBBLE_CHART':
return {
[action.id]: new chart(action.id, action)
}
default:
return state
}
}
// pieChartReducer.js
export function pie (state = {}, action) {
switch (action.type) {
case 'FETCH_PIE_CHART':
return {
[action.id]: new chart(action.id, action)
}
default:
return state
}
}
// linearChartReducer.js
export function pie (state = {}, action) {
switch (action.type) {
case 'FETCH_LINEAR_CHART':
return {
[action.id]: new chart(action.id, action)
}
default:
return state
}
}
// chartsReducer.js
import { bubble } from 'bubbleChartReducer'
import { pie } from 'pieChartReducer'
import { linear } from 'linearChartReducer'
import { combineReducers } from 'redux'
export combineReducers({
bubble,
pie,
linear
})
I have a table with check boxes. When a user selects a check box, the id is sent to an action creator.
Component.js
handlePersonSelect({ id }, event) {
const { selectPerson, deselectPerson } = this.props;
event.target.checked ? selectPerson(id) : deselectPerson(id);
}
action.js
export function selectPerson(id) {
console.log("selected")
return {
type: SELECT_PERSON,
payload: id
};
}
export function deselectPerson(id) {
return {
type: DESELECT_PERSON,
payload: id
};
}
I'm able to go all the way up to this step, at which I'm lost:
This is the offending code:
import {
SELECT_PERSON,
DESELECT_PERSON,
FETCH_PEOPLE
} from '../actions/types';
const INITIAL_STATE = { people:[], selectedPeople:[]};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_PEOPLE:
return {...state, people: action.payload.data};
case SELECT_PERSON:
return [action.payload, ...state ];
case DESELECT_PERSON:
return _.without({state, selectedPeople:action.payload});
}
return state;
}
The selectedPeople state should add/subtract the id's based on each case. As of now, everytime I deselect the id, my whole "people" table disappears.
You should return the complete state from the reducer in all cases, so:
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_PEOPLE:
return {...state, people: action.payload.data};
case SELECT_PERSON:
return {...state, selectedPeople: [ ...state.selectedPeople, action.payload]};
case DESELECT_PERSON:
return {...state, selectedPeople: _.without(state.selectedPeople, action.payload)};
default
return state;
}
}
Also note you didn't have a DESELECT_PERSON action, but instead 2 SELECT_PERSON actions. Possibly a typo.