Redux reducer, append multiple api paginated requests to state - javascript

I am building an app. This app needs to request a woocommerce API for product data. The API will only allow 100 items at a time, so I have had to call this request 4 times with dynamic page parameters.
My aim for this data is to have a reducer combine the data into one array that can be filtered in a react component.
My problem at the moment is that my reducer is adding each API call to state in its own array. So rather than have a big array with 300 ish products in, I have 1 array that contains 4 arrays each with 100 products in. Please see the image below.
State data result
Here is the action:
export function fetchJuiceData(page) {
return dispatch => {
dispatch(getDataPending("juicedata"));
return axios
.get(
"API_call/end_point/&page=" +
page
)
.then(response => {
dispatch(getDataSuccess("juicedata", response));
})
.catch(err => {
dispatch(getDataFailure("juicedata", err));
});
};
}
Which gets run 4 times with async:
const juiceDataPages = 4;
var i;
for (i = 0; i < juiceDataPages; i++) {
await dispatch(fetchJuiceData(i + 1));
}
My reducer:
const juiceDataReducer = (state = initState, action) => {
switch (action.type) {
case "FETCH_JUICEDATA_PENDING": {
return { ...state, fetching: true };
}
case "FETCH_JUICEDATA_REJECTED": {
return { ...state, fetching: false, error: action.payload };
}
case "FETCH_JUICEDATA_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
juiceData: [...state.juiceData, action.payload]
};
}
default: {
return state;
}
}
};
I am not the greatest coder in the world and would love your input. I have been banging my head against a wall for days now. TIA.

It seems like the payload you're putting in is an array by itself. So try using the spread operator on it too like so:
case "FETCH_JUICEDATA_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
juiceData: [...state.juiceData, ...action.payload]
};
}
This way only the items inside of the payload are added instead of the whole array.

Related

Updating array in reducer

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.

How to update multiple values with an array inside Reducer

I have the following code to update the currentScore of a rubricItem object. This works fine.
case SAVE_SCORELIST_SUCCESS:
const scoreItem = action.payload.scoreItem;
return {
...state,
loading: false,
editing: false,
rubricItems: {
...state.rubricItems,
[scoreItem.rubricItemId]: {
...state.rubricItems[scoreItem.rubricItemId],
currentScore: scoreItem.currentScore,
}
}
};
However, I may receive an array object holding scores for multiple rubricItems instead of updating a single rubricItem with a single scorItem as I did above.
I know I can use .map() to iterate through the array:
scoreItems.map(si=>{})
But, I do not know how I can integrate it into this:
case SAVE_SCORELIST_SUCCESS:
const scoreItems = action.payload.scoreItems;
return {
...state,
loading: false,
editing: false,
rubricItems: {
...state.rubricItems,
[scoreItems[x].rubricItemId]: {
...state.rubricItems[scoreItems[x].rubricItemId],
currentScore: scoreItems[x].currentScore,
}
}
};
Any ideas?
You can try this:
First you need to iterate over scoreItems and make a map object of updated score items.
Once you have done that, you can use the spread operator with the current score items in state.
case SAVE_SCORELIST_SUCCESS:
let updatedScoreItems = {};
action.payload.scoreItem.forEach(scoreitem => {
updatedScoreItems[scoreItem.rubricItemId] = {
...state.rubricItems[scoreItem.rubricItemId],
currentScore: scoreItem.currentScore,
}
})
return {
...state,
loading: false,
editing: false,
rubricItems: {
...state.rubricItems,
...updatedScoreItems
}
};
Instead of mapping over scoreItem, map over the rubricItems which will be cleaner.
const updatedRubricItems = items.rubricItems.map(rubricItem => {
const scoreForRubric = scoreItems.find(si => si.rubricItemId === rubricItem.id);// i assume you have some id for your rubric item
if(scoreForRubric){
return {...rubricItem, currentScore: scoreForRubric.currentScore}
}else {
return rubricItem
}
});
return {
...state,
loading: false,
editing: false,
rubricItems: updatedRubricItems
};

Redux state updates two seperate objects

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));
}

Show a loading page when data is fetching - React Redux

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

Replace using Date.now() inside a reducer

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.

Categories