Create reducer to update state for different components in redux - reactjs

I am creating this UI using react and redux. Below is the image of the UI.
This is image of UI
Now it currently displays the data when 'Categories' is clicked. Then when I click on unmapped link ,it shows unmapped products of 'Categories'. But when I click on 'Addons' and then on unmapped link ,it still shows the unmapped products of 'Categories'. How do I write the reducer so that it shows the unmapped products of 'Addons' as well. In my react main file I use (this.props.menu_items) that's why it shows the data of 'Categories' when clicked unmapped after Addons.
This is my reducer file.
import { AppConstants } from '../constants';
const initialState = {
state: [],
menu_items: [],
menu_items_copy: [],
addon_items: [],
unmapped: false,
};
export default (state = initialState, action) => {
switch (action.type) {
case AppConstants.getMenuItems:
return {
...state,
}
case AppConstants.getMenuItemsSuccess:
return {
...state,
menu_items: action.menu_items,//This contains the data showed when
// 'categories' is clicked
menu_items_copy: action.menu_items,
unmapped: false,
}
case AppConstants.getAddonsItems:
return {
...state,
}
case AppConstants.getAddonsItemsSuccess:
return {
...state,
menu_items: action.addon_items,//Here I update the data to addons data
}
case AppConstants.showAllProducts:
return {
...state,
menu_items: action.menu_items,
unmapped: false
}
case AppConstants.getUnmappedMenuItemsSuccess:
return {
...state,
unmapped: true,
menu_items: state.menu_items_copy.filter((item) => {-->How do I write
here for action.addon_items also
return (item.productList.length == 0)
})
}
case AppConstants.hideError:
return {
...state,
error: null
}
default:
return state
}
};

The only state I would change via reducer when unmapped is clicked would be to set unmapped: true / false.
There is no need to filter the items array and store it back into the state, as you already have all the info you need to derive your combined items at the point where you pass it to the view.
Have a read about derived state and selectors here http://redux.js.org/docs/recipes/ComputingDerivedData.html
In order to do this you would use a selector to combine the relevant parts of your state to produce a single list of items that is derived from both the items arrays, depending on the unmapped flag. The reselect library is a great helper to do this while memoising for performance, it will only recompute if any of the inputs change, otherwise returns the previously cached value
import {createSelector} from 'reselect'
const selectMenuItems = state => state.menu_items
const selectAddonItems = state => state.addon_items
const selectUnmapped = state => state.unmapped
const selectItemsToShow = createSelector(
selectMenuItems,
selectAddonItems,
selectUnmapped,
(menuItems, addonItems, unmapped) => {
// if unmapped is set - combine all items and filter the unmapped ones
if (unmapped) {
return menuItems.concat(addonItems).filter(item => !item.productList.length)
}
// else just return the menu items unfiltered
return menuItems
}
)
// this selector can then be used when passing data to your view as prop elsewhere
// or can be used in `connect` props mapping as in the linked example)
<ItemsList items={selectItemsToShow(state)} />

Related

React Redux: update local state with a new value on action dispatch

I have an action that creates a 'supplier' in the database, and when it finishes it calls another action that is supposed to create a supplier locally (through redux).
This is very basic but I am a bit lost on how to add the new supplier to the already existing supplier state.
Here is the action (The newly created supplier is passed to it):
export const createSup = (sup) => {
return {
type: "CREATE_SUP",
sup,
};
};
Here is my reducer file along with the error line (Everything working fine except for the CREATE_SUP case):
const initialState = {
value : {},
loaded: false,
error: false
}
const supReducer = (state = initialState, action) => {
switch (action.type) {
case 'LOAD_SUPPLIERS':
return {
value: action.sups,
loaded: false, //this might need to be set to true
error: false
}
case 'LOAD_SUPPLIERS_ERROR':
console.log("load suppliers error")
return {
...state,
loaded: false,
error: true
}
case 'LOAD_SUPPLIERS_SUCCESS':
return {
...state,
loaded: true,
error: false
}
case 'CREATE_SUP':
return {
value: {...state.value, action.sup}, //************ERROR *************//
loaded: true,
error: false
}
default:
return state;
}
};
export default supReducer;
It gives me an error mark under action.sup (I guess it expects only one value). Should I not worry about returning the old value of the state, and just return the new supplier? (I think I am missing a point or two on how exactly reducers work).
Thank you for any help.
My guess would be that you meant to use the spread operator, like so:
case 'CREATE_SUP':
return {
value: { ...state.value, ...action.sup },
loaded: true,
error: false
}
action.sup isn't an object property, it's an object. I assume you just want to apply those properties to your state object.
I should also note that your reducer should typically always return a state object in the same "shape" for every action. So if your initial state object is { prop1: 1, prop2: 2, prop3: 3 }, it should always return something similar to that, just with different values. I say this because it looks like your "LOAD_SUPPLIERS" action is kinda doing its own thing and not copying the current state. Just remember that your state object is meant to be accessed "globally" (I use this term loosely) with Redux. The components that use your Redux state will expect a certain object shape and certain properties to always be there, so you need to make sure you always return that with your reducer. Hope that makes sense.

Change state from reducer not from root state using React-Redux when data is inherited

Good afternoon. I am writing an application using react-redux and faced a dilemma. I have already re-thought it several times and can't choose how to organize the project and data structure correctly and conveniently. Design data by inheriting or composing data. I initially went along the path of composition, but I realized that it is inconvenient when there is a one-to-one relationship. I decided to change it to inheritance, because it seemed logical from the point of view of data organization, but there was a big difficulty with reducers, more precisely, I am confused that it turns out to be a single root reducer with a lot of actionTypeskeys . I remember about performance, when elements inherit a data chain from a common ancestor, that this is very bad. And yet I chose this path and I have a question: Please tell me if it is possible to split into several reducers for each level of nesting data. Example
onst initState: IPages = {
idActive: 0,
pages: [
{
id: 1,
title: `Tab #1`,
workspace: {
idActiveDraggableElements: [],
idActiveLines: [],
attributes: {
height: string,
width: string,
viewBox: [0, 0, 5000, 5000]
},
draggableElements: [], // more data
lines: [], // more data
}
},
]
}
Reducer:
export function pagesReducer(
state: IPages = initState,
action: IPageActionTypes
) {
switch (action.type) {
case "ADD_PAGE":
let uniqId = getUniqKeyIdOfArrayList(state.pages);
return {
...state,
pages: state.pages.concat({id:uniqId, title:`Вкладка - ${uniqId}`})
}
case "REMOVE_PAGE": return {
...state,
pages: state.pages.filter(item => item.id !== action.id)
}
case "CHOSE_PAGE": return {
...state,
idActive: action.id
}
case "RENAME_PAGE":
let indexPage = state.pages.findIndex(item => item.id === action.id);
state.pages[indexPage].title = action.title;
return {
...state
}
// ===================
// LONG LIST WHAT BAD...
// It's a bad idea to add editing to the `workspace` field and then `draggableElements`. `lines`
// ... but I understand that this will happen, because I don't know if there is another way.
default:
return state
}
}
Can I edit the `workspace' node without updating the entire application state?
Thanks you for any help.
For data modeling aspect for a 1-to-1 relationship, you can choose either to reference by id or to embed the data. It depends on your query pattern.
In your case which is embedding, you can make use of memoized selectors.
Ideally, since you have an idActive, update your pages data structure to be an object instead of a list.
Like so:
{
pages: {
'1': {
workspace: { ... },
}
}
}
Then for your reducer, think of it as slicing a tree (or nested attribute). Your reducer would then look something like:
function workspaceReducer(state, action) {
// TODO
}
function pagesReducer(state, action) {
switch (action.type) {
case 'UPDATE_WORKSPACE': {
const { id } = action;
const page = Object.assign({}, state.pages[id]);
return {
...state,
pages: {
...state.pages,
[id]: {
...page,
workspace: workspaceReducer(page.workspace, action)
}
}
}
}
}
}
Then to prevent unnecessary re-renders, using memoized selectors,
it would be like:
import { createSelector } from 'reselect';
const pages = state => state.pages;
const activePage = state => state.idActive;
const getActivePage = createSelector(
activePage,
pages,
(id, pages) => pages[id]
);
const getWorkspace = createSelector(
getActivePage,
page => page.workspace
);

Is there a way to reuse a reducer logic in redux?

I am using react/redux to build my app. There are multiple buttons and the app needs to keep track of states of whether these buttons were clicked. These buttons are all using the same reducer logic, which is to alternate the state from true to false whenever the button is clicked. The only differentiator is the name of the state. Is there a way to reduce my code by defining a "global" reducer that can be applied to multiple states? Please refer to this image for an example
Thank you!
This is how I would do it. Instead of dispatching unique action for every button type like MENU_BTN, SEARCH_BTN, etc.. I would dispatch { type: 'TOGGLE_BUTTON', payload: 'menu' } and in reducer
case TOGGLE_BUTTON:
return {
[payload]: !state[payload]
...state
}
You can then toggle buttons like this
const toggleButton = key => ({
type: 'TOGGLE_BUTTON',
payload: key
})
dispatch(toggleButton('menu'))
dispatch(toggleButton('search'))
That way you can keep track of as many buttons as you want. Your state will then look something like this
...
buttons: {
menu: true,
search: false
}
...
and you can easily write selector for every button
// Component displaying state of menu button //
let MyComponent = ({ menuButtonState }) => (
<div>
Menu button is {menuButtonState ? 'on' : 'off'}
</div>
)
// Helper function for creating selectors //
const createGetIsButtonToggledSelector = key => state => !!state.buttons[key]
// Selector getting state of a menu button from store //
const getIsMenuButtonToggled = createGetIsButtonToggledSelector('menu')
// Connecting MyComponent to menu button state //
MyComponent = connect(state => ({
menuButtonState: getIsMenuButtonToggled(state)
})(MyComponent)
You can create a generic higher-order reducer that accepts both a given reducer function and a name or identifier.
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const { name } = action
const isInitializationCall = state === undefined
if (name !== reducerName && !isInitializationCall) return state
return reducerFunction(state, action)
}
}
const rootReducer = combineReducers({
counterA: createNamedWrapperReducer(counter, 'A'),
counterB: createNamedWrapperReducer(counter, 'B'),
counterC: createNamedWrapperReducer(counter, 'C')
})
The above createdNamedWrapperReducer() method should work out of the box.
See Redux's recipes for Reusing Reducer Logic for a more detailed explanation or more examples.

Using checkbox with Redux

I'm having problems with a checkbox in my code in two areas, The first one, in my reducer, I want to see if the current state of the checkbox is "true" or "false" but I keep getting syntax errors on the if.
const initialState = {
viewCheckbox: false
}
export default (state = initialState, action) => {
switch (action.type){
case 'VIEW_CHECKBOX':
return {
...state
if (viewCheckbox == false) {
viewCheckbox: true
} else {
viewCheckbox: false
}
}
default:
return: state
}
}
My second problem is with the mapDispatchToProps, I'm using a table to create multiple checkboxes and I want to be able to differentiate each one of them by ID, and when I do it like this, it checks every checkbox on the table.
const mapDispatchToProps = (dispatch) => ({
handleViewCheckbox: id => ev => {
dispatch(viewCheckboxSubmit(id, ev.target.checked))
}
})
And when I create the checkbox I do it like this:
<FormControlLabel
control={
<Checkbox
checked={checkedView}
onChange={handleViewCheckbox(n.id,checkedView)}
/>
}
label='See'
/>
You can't do an if inside an object like that. You can use the spread operator as you have done, add solo variables in which the key matches the variable you want to use, or you can add keys directly. try something like this:
case 'VIEW_CHECKBOX':
return {
...state,
viewCheckbox: !viewCheckbox
}
They're all going to toggle on and off because you're using the same bit of state to manage whether or not it is checked. If you want to go about it this way, you could try storing whether each one is checked in an object and just keep updating that.
{
checkboxid1: false,
checkboxid2: false,
checkboxid3: true // checked
}
then the checked prop for your checkbox component would look like..
checked={this.props.isChecked[n.id]}
assuming you pulled that object out in mapStateToProps and called it isChecked

reducer: adding to array data

if i pull some data from an external source fro the initial state, then want to add additional information like for example 'liked'?
i've tried adding to the products array but its go messy, I'm thinking i should have an additional array for liked items then put the product id in this, the only thing is i need it to reflect in the product that it has been liked and I'm mapping the product data to the item.
whats the best way to go about this ?
const initialState = {
isFetching: false,
products: [],
};
should i add favs: [] ?
how would i reflect the liked state to my product as I'm mapping the products array to the product component? and the liked state is now in the favs?
i tried doing this to add it to the product array but it got really messy (something like this)
case ADD_LIKED:
state.products[action.index]['liked'] = true;
return state;
state.products[action.index]['liked'] = true;
The problem here is that you are mutating the state inside the reducer which is one of the things you should never do inside a reducer.
You'll find that writing functions which don't mutate the data are much easier if you break them down into smaller parts. For instance you can start to split your application up.
function productsReducer(products = [], action) {
// this reducer only deals with the products part of the state.
switch(action) {
case ADD_LIKED:
// deal with the action
default:
return products;
}
}
function app(state = {}, action) {
return {
isFetching: state.isFetching,
products: productsReducer(state.products, action)
}
}
In this case I would definitely want to write a little immutability helper.
function replaceAtIndex(list, index, replacer) {
const replacement = replacer(list[index]);
const itemsBefore = list.slice(0, index),
itemsAfter = list.slice(index + 1);
return [...itemsBefore, replacement, ...itemsAfter];
}
You can complement this with a generic function for changing objects in lists.
function updateInList(list, index, props) {
return replaceAtIndex(list, index, item => {
return { ...props, ...item };
});
}
Then you can rewrite your function in the immutable form
switch(action) {
case ADD_LIKED:
return updateInList(products, action.index, { liked: true });
default:
return products;
}
You could even get fancy by partially applying the function. This allows you to write very expressive code inside your reducers.
const updateProduct = updateInList.bind(this, products, action.index);
switch(action) {
case ADD_LIKED:
return updateProduct({ liked: true });
case REMOVE_LIKED:
return updateProduct({ liked: false });
default:
return products;
}

Resources