i am trying to update state in reducer with a array of objects. Following is what i am doing.
Action.js file
export const apiSuccess = (data, forCust) => {
return {
type: ACTIONS.validateSuccess,
data: data,
forCust
};
};
export const apiFailed = (error, forCust) => {
console.log('pandetail failed', error)
return {
type: ACTIONS.ValidateFailed,
data: error?.details?.data,
forCust
};
};
here forCust can be customer or admin
Here is the reducer
const initialState = {
otherDetails: {},
individualDetails: [{ forCust: 'customer' }, { forCust: 'admin' }],
};
const reducer = (state = initialState, action = {}) => {
switch (action.type) {
case ACTIONS.validateSuccess:
console.log('Action in reducer', action)
debugger;
return {
...state,
otherDetails: action?.data,
individualDetails: state.individualDetails.map((item) => {
if(item.forCust === action.forCust){
item = action
}
return item
}),
}
case ACTIONS.validateFailed:
return {
...state,
otherDetails: action?.data,
individualDetails: state.individualDetails.map((item) => {
if (item.forCust === action.forCust) {
item = action
}
return item
}),
}
default:
return state;
}
};
export default reducer;
Now, when action is dispatched it dispatches with success and other parameter which is forCust to check if the dispatched action is for admin or customer. Now i have two fields in the same component but different parameters which calls the action(admin/customer). If the validateSuccess with customer is called i am trying to update customer details in individualDetails array and if admin is called i am trying to update only the admin state array object.
Now, what happens is that i am not able to maintain the old state ie when customer changes and admin details is there, it updated the whole array, not only the customer.
Also if somehow one of them get failed, failed action is dispatched and then update the array corresponding to the forCust field.
any help will be appreciated.
Related
I have influencer data object. This object is beeing pulled from database with action FETCH_INFLUENCER and put inside two different objects: influencer and formInfluencer in redux store. And then I have action SET_INFLUENCER that is supposed to create new instance of the state and update influencer object in redux. For some reason though it updates both influencer and formInfluencer. I really struggle with finding answer here since I think I did everything to prevent pointing of two different variables to the same object and still it happens.
reducer:
case 'FETCH_INFLUENCER_FULFILLED':
return { ...state, fetching: false, fetched: true, influencer: action.payload.data, formInfluencer: Object.assign([], action.payload.data) }
case 'SET_INFLUENCER':
return { ...state, influencer: action.payload }
actions:
export function fetchInfluencer(id) {
return {
type: "FETCH_INFLUENCER",
payload: axios.get('/api/influencer/' + id, {headers: {Authorization: 'Bearer ' + localStorage.getItem('token')}})
}
}
export function setInfluencer(influencer) {
return {
type: "SET_INFLUENCER",
payload: influencer
}
}
dispatch:
handleUserChange(e) {
let influencer = [...this.props.influencer]
influencer[0].user[e.target.name] = e.target.value;
this.props.dispatch(setInfluencer(influencer))
}
mapping state to props:
const mapStateToProps = (state) => {
return {
influencer: state.influencers.influencer,
formInfluencer: state.influencers.formInfluencer
}
}
export default connect(mapStateToProps)(InfluencerDetails)
If You have any idea why this could be happening I would be happy to hear the answer.
You shouldn't mutate state (if you don't mutate it, then it is no problem that you have multiple variables pointing to the same object).
Instead of:
handleUserChange(e) {
let influencer = [...this.props.influencer]
influencer[0].user[e.target.name] = e.target.value;
this.props.dispatch(setInfluencer(influencer))
}
You should do a bit more work:
handleUserChange(e) {
const newUser = {
...this.props.influencer[0].user,
[e.target.name]: e.target.value
};
const newInfluencer = {
...this.props.influencer[0],
user: newUser
};
const newInfluencers = [...this.props.influencer];
newInfluencers[0] = newInfluencer;
this.props.dispatch(setInfluencer(newInfluencers));
}
I have 2 parts of the page, left side is the collection of Ids and second part is displaying the data.
First time when the page is loading all the data (of all the Ids
) is displayed. But user can select only 1 id and can see the data of that particular id but it takes time to fetch the data. So between the time of clicking the id and displaying the data - the page shows the previous data (which is fairly confusing) and suddenly the data is changed after the fetch is completed. I want to show the loading page when the id is clicked till the data appears.
How to do that.
On Id select below function is executed and then it goes to the reducer for dispatch action.
onIdSelection(key, obj) {
if (key && key.id) {
this.props.dispatch(actions.fetchDataActionCreator(key.id));
}
}
In the reducer:
export const fetchDataActionCreator = (siteId) => {
return (dispatch) => {
return dispatch({
type: START_FETCH_DEVICES,
payload: api.get({
url: '/api/ndp/v1/collector/telemetry/sites/' + siteId + '/devices',
config: {
apiPrefix: 'none'
}
}).then(payload => dispatch({
type: FINISH_FETCH_DEVICES,
meta: {siteId},
payload})
)
});
};
};
In the reducer:
case START_FETCH_DEVICES:
return {...state, dataLoading: true};
case FINISH_FETCH_DEVICES:
return {...state, dataLoading: false, payload: action.payload};
On the data side:
componentWillReceiveProps(nextProps) {
const {dataUpdated} = nextProps;
if (dataUpdated) {
this.props.dispatch(actions.fetchDataActionCreator(this.props.id));
}
}
Here when I get the data in the nextProps - there is the whole data not on the Id selected.
How to just show the "loading" div till the data is ready for display.
You should add split FetchData action into 2 actions:
1. First type: START_FETCH_DEVICES
2. Second type: FINISH_FETCH_DEVICES
To orchestrate dispatching these two action use action creators.
For example
export const fetchDataActionCreator = (id) => (dispatch) => {
dispatch ({type: START_FETCH_DEVICES});
api.get({
url: '/api/ndp/v1/id/' + id,
config: {
apiPrefix: 'none'
}
}).then(payload => dispatch({
type: FINISH_FETCH_DEVICES,
meta: {id},
payload})
// I'm not sure that api.get can return Promise, but most async fetch api do so
)
}
Now you have fetchDataActionCreator as action creator which returns function tacking dispatch as first argument. When connecting your component to Redux use bindActionCreators to properly wrap fetchDataActionCreator.
Reducer should set some variable in state to notify app that data is loading.
For example
function reducer(state, action) {
switch (action.type) {
case START_FETCH_DEVICES:
return {...state, dataLoading: true}
case FINISH_FETCH_DEVICES:
return {...state, dataLoading: false, payload: action.payload}
default:
return state;
}
}
And your component can display Loading message when state.dataLoading is true
I am trying to use redux to add key value pairs to my store. However, Im not sure how to accomplish this. In short, i am retrieving data from firebase, I want to add that data to my redux store, but I have to do it one item at a time. My desired structure of my state object is something like this:
reminders
- reminder key 1
- reminder title
- reminder date 1
- reminder key 2
- reminder title
- reminder date 1
and so on.
But I cant figure out how to add children to my state.reminders object
Here is my action:
const fetchReminders = (uid) => async dispatch => {
firebaseReminders.child(uid).orderByChild("date").on("value", snapshot => {
snapshot.forEach(function(child) {
console.log(child.val())
dispatch({
type: 'fetchReminders',
value: child.val(),
key: child.key
});
})
});
};
so this would dispatch the action for every single item that I retrieve from the database, and then in my reducer I want to add that item to the state tree using action.key as the key. Currently I have
const remindersReducer = (state = initialState, action) => {
switch(action.type) {
case "fetchReminders":
return Object.assign({}, state, {
reminders: action.value
});
default: return state;
}
};
which is not correct. How can I add a child node to my state.reminders object with the key of action.key, and the value of action.value
let initialState = {
reminders: {}
}
const remindersReducer = (state = initialState, action) => {
switch(action.type) {
case "fetchReminders":
return Object.assign({}, state, {
reminders: {
...state.reminders,
[action.key]: action.value
}
});
default: return state;
}
};
let state1 = remindersReducer(initialState, {
type: 'fetchReminders',
key: 'reminderKey1',
value: 'reminderValue1'
});
console.log(state1)
let state2 = remindersReducer(state1, {
type: 'fetchReminders',
key: 'reminderKey2',
value: 'reminderValue2'
});
console.log(state2)
let state3 = remindersReducer(state2, {
type: 'fetchReminders',
key: 'reminderKey3',
value: 'reminderValue3'
});
console.log(state3)
The snippet should help you achieve what you want to do.
You can assign an object as the key of action.key by using the following format:
{
[action.key]: action.value
}
Its called Computed Property Names.
Starting with ECMAScript 2015, the object initializer syntax also
supports computed property names. That allows you to put an expression
in brackets [], that will be computed and used as the property name.
Source
Documentation said i should avoid state mutation by using new Date, etc inside reducers. Help me please with advice how should it be done.
Action:
const RECEIVE_PRICES = 'RECEIVE_PRICES';
function receivePrices(prices) {
return {
type: RECEIVE_PRICES,
receivedAt: Date.now(),
prices,
};
}
REDUCER:
...
case RECEIVE_PRICES: {
let { prices } = action;
prices = prices.map((p) => {
const baseQuote = p.symbol.split('/');
return { ...p, baseCurrency: baseQuote[0], quoteCurrency: baseQuote[1] };
});
prices.sort(
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
);
return {
...state,
prices,
pricesLoading: false,
pricesError: null,
};
}
default:
return state;
}
In Redux, all side-effects (not just api calls) should take place inside Action Creators. You should move this logic into the action creator and have the caller pass the necessary parameters.
Is this a reasonable solution for data sharing between two states/reducers?
//combineReducers
function coreReducer(state = {}, action){
let filtersState = filters(state.filters, action);
let eventsState = events(state.events, action, { filters: filtersState});
return { events: eventsState, filters : filtersState};
}
export const rootReducer = combineReducers(
{
core : coreReducer,
users
}
);
If so, how can one guarantee the order in which reducer functions are executed if both answer to the same dispatched event and the second reducing function depends on the new state of the first one?
Let's say that we dispatch a SET_FILTER event that appends to activeFilters collection in the filters Store and later changes the visibility of items in the events Store with respect to the activeFilters values.
//ActiveFilters reducer
function filtersActions(state = {}, action){
switch (action.type) {
case SET_FILTER:
return Object.assign({}, state, {
[action.filterType]: action.filter
})
case REMOVE_FILTER:
var temp = Object.assign({}, state);
delete temp[action.filterType];
return temp;
case REMOVE_ALL_FILTERS:
return {};
default:
return state
}
}
I think I found the answer - Computing Derived Data - Reselect
http://redux.js.org/docs/recipes/ComputingDerivedData.html
/--------container--------/
import {getGroupsAndMembers} from '../reducers'
const mapStateToProps = (state) => {
return {
inputValue: state.router.location.pathname.substring(1),
initialState: getGroupsAndMembers(state) <-- this one
}
}
/--------reducers--------/
export function getGroupsAndMembers(state){
let { groups, members } = JSON.parse(state)
response = {groups, members}
return response;
}
GroupsContainer.propTypes = {
//React Redux injection
pushState: PropTypes.func.isRequired,
// Injected by React Router
children: PropTypes.node,
initialState:PropTypes.object,
}
don't forget to follow the guidelines for 'connect'
export default connect(mapStateToProps,{ pushState })(GroupsContainer)
If you have two reducers, and one depend on a value from a first one, you just have to update them carefully, and the best solution will be just to use a special function, which will first set the filtering, and then query corresponding events. Also, keep in mind that if events fetching is asynchronous operation, you should also nest based on filtering type -- otherwise there is a chance of race condition, and you will have wrong events.
I have created a library redux-tiles to deal with verbosity of raw redux, so I will use it in this example:
import { createSyncTile, createTile } from 'redux-tiles';
const filtering = createSyncTile({
type: ['ui', 'filtering'],
fn: ({ params }) => params.type,
});
const events = createTile({
type: ['api', 'events'],
fn: ({ api, params }) => api.get('/events', { type: params.type }),
nesting: ({ type }) => [type],
});
// this function will just fetch events, but we will connect to apiEvents
// and filter by type
const fetchEvents = createTile({
type: ['api', 'fetchEvents'],
fn: ({ selectors, getState, dispatch, actions }) => {
const type = selectors.ui.filtering(getState());
return dispatch(actions.api.events({ type }));
},
});