Managing state of multiple list item with ID in redux - reactjs

I am having multiple list item in my DOM whenever I click a list item I call the API for that particular item and store it in my Redux store and when I click another item in DOM I add it to my array in redux store.
The problem I am facing is when I click the same list item again I don't want to hit the API again I want to show data for that particular list item already stored in my redux store how should I do it?
My Reducer Code
import * as actionTypes from '../actions/actionTypes';
const initialState = {
fareRules: [],
error: false
};
const reducer = (state = initialState, action) => {
switch(action.type) {
case actionTypes.SET_FARE_RULES:
return {
...state,
fareRules: [
...state.fareRules,
{
id: action.id,
rules: action.fareRules[0][0]
}
]
}
case actionTypes.GET_FARE_RULES_FAILED:
return {
...state,
error: true
}
default:
return state;
}
}
export default reducer;

In your handler for handling a click on the item, you need to check if the fare rules for that item already exist in the store (your component needs to have access to the store).
If the fare rules for that item do no exist, add them (fire the relevant action), otherwise display them.

Related

redux store change not immediately visible to component

Im trying to create a formbuilder using drag and drop functions. Im maintaining the state using Redux. I have two items in initial state(allItems array and dragItem). My reducer looks like the following:
import { ActionTypes } from '../constants/ActionTypes.js'
const intialState = {
allItems: [],
dragItem: ""
}
export const builderReducer = (state = { intialState }, action) => {
switch (action.type) {
case ActionTypes.addItem:
return {
...state,
allItems: action.payload
}
case ActionTypes.dragItem:
return {
...state,
dragItem: action.payload
}
default:
return state
}
}
drag Item is updated on dragstart event everytime the user drags a component . all items is updated evrytime the user drops the component in workspace. once i drop the component store gets updated correctly . However im not immediately able to collect the changed store value . it is taking two drag and drops to get the store value . it says undefined at the first place i try to use it . on second drag and drop i get both the values together . Below image will explain what i am trying to do .

How can a state be cleared when moving away from a page

I have created a Quiz app that tracks the correct answers in state called correct_answer. The issue is when the user leaves one quiz and moves on to the next one, The quiz answers are still stored from the last quiz.
I have tried using LOCATION_CHANGE from react-router-redux, but I am not sure I am using it correctly.
import { LOCATION_CHANGE } from "react-router-redux";
const initialState = {
questions: [],
answers: [],
correct_answer: []
};
export default function(state = initialState, action) {
switch (action.type) {
case "GET_QUESTIONS":
return { ...state, questions: action.payload };
case "GET_ANSWERS":
return { ...state, answers: action.payload };
case "CORRECT_ANSWER":
return {
...state,
correct_answer: [...state.correct_answer, action.payload]
};
case LOCATION_CHANGE:
return {state = initialState};
default:
return state;
}
}```
The app needs to clear the correct_answers state anytime the user moves away from the page.
Keep in mind that the redux store is an omnipresent data structure. The data persists regardless of any ui changes in your app, which includes local state changes in a component and mounting/unmounting components (unless you tear down your reducer, but that's not what you're doing at all).
As mentioned in the comments, it's your job to clear your state. Create an action that will reset the reducer. How you implement it is based on your exact implementation of your Quiz component(s).
How does mounting/unmounting/prop changes work when you switch quizes? Are you mounting an entirely new component or are you feeding new data into an existing component?
If the next quiz is an entirely new instance, then you call it when you unmount the prior quiz:
componentWillUnmount() {
this.props.resetQuizState() // your action that resets the data in your store
}
If it is the same component but new props are passed in:
handleNextQuizClick() {
this.props.resetQuizState()
// and then rest of data manipulation/calling/parsing
}
render() {
return (
<button onClick={this.handleNextQuizClick}>
next quiz
</button>
}

How to use redux saga in editable table efficiently

I have a multi page react application in which one endpoint has to show data in tabular form. Show I take GET_INFO action on componentWillMount of that endpoint. Now I have a reducer called table_info which has table_data array and shouldTableUpdate boolean in it.
My table is editable with edit and delete icon in every row. I am facing problem in update, on update I call reducer with action UPDATE_TABLE_ROW and if success than I do something like following :
//reducer.js
const initialState = {
table_data:{}, shouldTableUpdate:false;
}
export default function myReducer(state=initialState, action){
switch(action.type){
case UPDATE_SUCCESS:
// how to handle edited row here?
// also when I print my state of this reducer
// state becomes nested, so if one does lots of updates
// it will be become very heavy...
return {...state, shouldTableUpdate:true}
}
}
Can you tell how to handle update, delete, add on table using redux saga efficiently ? On googling I get naive examples only, so came to SO.
Note: Can't show the actual code as it's for my company project. Sorry for that.
Thanks.
Can you tell how to handle update, delete, add on table using redux saga efficiently ?
Well you can plainly manipulate the state object using a reducer only.
Comments:
table_data is a list and not an object.
I don't think you'll be needing shouldTableUpdate since state change in store will trigger a component update if state field is mapped in mapStateToProps.
So here's a basic template of adding, updating and deleting items via reducer.
const initialState = {
table_data: [],
};
export default function myReducer(state=initialState, action){
switch(action.type) {
case ADD_ITEM:
return {
...state,
table_data: [
...state.table_data,
action.item, // item to be added
]
};
case UPDATE_ITEM:
let updatedItem = action.item;
// do something with updatedItem
return {
...state,
table_data: table_data.map(e => (
e.id === updatedItem.id ? updatedItem : e
)),
};
case DELETE_ITEM:
const index = state.table_data.findIndex(e => e.id === action.item.id);
const numItems = state.table_data.length;
return {
...state,
table_data: [
// exclude index
...table_data.slice(0, index),
...table_data.slice(index+1, numItems),
]
};
default:
return state;
}
}

Multiple checkbox search filter using react redux

I want to create multiple checkbox search filter in my react-redux application.
I have added checkboxes, which will make request to api, but the issue is, every time when I clicked on checkbox, new data is coming from api, which is overwriting old data in the state.
How can I retain my old state ? or Is there any other way to do this ?
This is my reducer
import * as types from '../constants';
const InitialState = { data: [], };
export const dataReducer = (state= InitialState , action = null) =>
{
switch(action.type) {
case types.GET_DATA:
return Object.assign({}, state, {data:action.payload.data });
default:
return state;
}
}
It's a bit unclear in the question as to how you want to keep the old data while still fetching new data, but given that data is an array, I'll assume you mean you want to merge them.
export const dataReducer = (state= InitialState , action = null) =>
{
switch(action.type) {
case types.GET_DATA:
return Object.assign({}, state, {data: [...state.data, ...action.payload.data] });
default:
return state;
}
}
This is making the assumption that you can always just append the new data. If not you will need to be more selective about which items get merged into the array before assigning it to the state.
export const dataReducer = (state= InitialState , action = null) =>
{
switch(action.type) {
case types.GET_DATA:
let data = [...action.payload.data]
// merge in the relevant items from state.data, e.g.
for (let item in state.data.filter(it => it.shouldBeKept)) {
data.push(item)
}
return Object.assign({}, state, { data });
default:
return state;
}
}
Obviously, you will know best how to identify the items to keep or not, modify the logic hear to best suit your need. For example, it might make morse sense for you to start with let data = [...state.data] and selectively merge items from action.payload.data, or to start with an empty array and pick and choose from either arrays. The important part is that you construct a new array, and not add items to the existing array in the state.

Updating state managed by another reducer

In my React app, my appReducer manages global stuff such as notifications, user info, etc.
One of the modules in the app is the inventory module which has its own reducer i.e. inventoryReducer. And in the redux store, I combine all the reducers.
When a user makes an inventory entry, in addition to handling the inventory transaction, I want to display an on-screen notification which is handled in the appReducer. How do I update the state of displayNotification which is under appReducer from the inventoryReducer?
The following is my app reducer:
import 'babel-polyfill';
import * as types from '../actions/actionTypes';
const initialState = {
displayNotification: {}
};
export default (state = initialState, action) => {
switch (action.type) {
case types.DISPLAY_NOTIFICATION :
return Object.assign({}, state, {
displayNotification: action.value
})
default: return state
}
}
And this is the inventoryReducer:
import 'babel-polyfill';
import * as types from '../actions/actionTypes';
const initialState = {
inventory: []
};
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_INVENTORY :
return Object.assign({}, state, {
inventory: action.inventoryItem
})
case types.DISPLAY_NOTIFICATION :
return Object.assign({}, state, {
app.displayNotification: action.value // <-- Is this how I access `displayNotification` which is managed by the `appReducer`?
})
default: return state
}
}
My update inventory action needs to dispatch both SET_INVENTORY and DISPLAY_NOTIFICATION. I'm trying to understand how I can update displayNotification from inventoryReducer where displayNotification is actually managed by the appReducer.
Following up with what azium said:
I think what you're trying to do is the wrong approach. What's stopping you from a) listening to SET_INVENTORY in your appReducer or b) dispatch both actions from your component?
As far as I understand, in Redux each reducer is allocated a slice of the entire state object and their operations are restricted in that slice. They are not allowed to access the state slice managed by any other reducer, and they shouldn't do that.
The concept description of Redux is that it is a predictable state container. But when I look at what we are trying to achieve in this question, if we were to access/modify state managed by another reducer-B in our reducer-A, the predictability and maintainability of the app are compromised according to me.
Without compromising on anything or moving undesired logic into our components, we can achieve what we need.
Option 1
Inside appReducer
you create a type SET_INVENTORY which does what DISPLAY_NOTIFICATION does. You can have multiple subscriptions for the single action that dispatches type SET_INVENTORY (in appReducer and inventoryReducer).
As shown below, in appReducer, if the action type is either SET_INVENTORY or DISPLAY_NOTIFICATION, the reducer updates the key displayNotification.
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_INVENTORY :
case types.DISPLAY_NOTIFICATION :
return Object.assign({}, state, {
displayNotification: action.value
})
default: return state
}
}
Option 2
Create a method that couples the dispatching of two actions,
let's say you have an action
function setInventory(inventoryItem) {
return {
type: types.SET_INVENTORY,
inventoryItem
};
}
and another action
function displayNotification(value) {
return {
type: types.DISPLAY_NOTIFICATION,
value,
};
}
create a thunk to couple them:
export function notifyAndSetInventory(notify, inventoryItem) {
return dispatch => {
dispatch(displayNotification(notify));
return dispatch(setInventory(inventoryItem));
};
}
In Redux's official document there's a chapter called 'Beyond combineReducers'. It mentioned sharing data between slice reducers.
Sharing data between slice reducers
I personally prefer the third solution mentioned in the link, which is adding a third customized reducer to handle the "special" cases where data needs to be shared across slices, then use reduce-reducers to combine the new customized reducer and the original combined reducer (i.e. appReducer + inventoryReducer).
const crossSliceReducer = (state, action) => {
if (action.type === 'CROSS_SLICE_ACTION') {
// You can access both app and inventory states here
}
return state;
}
// Combine the reducers like you did before
const combinedReducer({app: appReducer, inventory: inventoryReducer});
// Add the cross-slice reducer to the root reducer
const rootReducer = reduceReducers(combinedReducer, crossSliceReducer)

Resources