I'm a beginner in react / redux.
I've finished a basic component <HeatMap /> in my app, with its actions / reducer / store and it works well.
And I'll render another <HeatMap /> with different settings (props).
What I'm trying to do is to separate this 2 component, because when i dispatch an update action in one, the other one performed it simultaneously.
Question 1
I tried this to separate the states in store
import heatMap from './heat-map1'
import {combineReducers} from 'redux';
export let reducers = combineReducers({
heatMap1: heatMap,
heatMap2: heatMap
});
combineReducers and connectthe 2 heatmap in different object in store
export default connect((state)=> {
return {
onState: state.heatMap1.onState,
config: state.heatMap1.config
}
})(CHMSHeatMap1)
and
export default connect((state)=> {
return {
onState: state.heatMap2.onState,
config: state.heatMap2.config
}
})(CHMSHeatMap2)
is this correct?
Question 2
Because 2 component both react when action is dispatched
I'm thinking about separating the shared actions, but I don't think it's a good idea. Or maybe the issue is not here.
So can you tell me what cause this problem and how to solve it?
Here are my reducer
import * as actionTypes from '../actions/heat-map';
import Immutable from 'immutable';
const onState = {
fetching: 'FETCHING',
error: 'ERROR',
drawn: 'DRAWN'
};
const initialState = {
onState: onState.fetching,
config: {}
};
export default function heatMapReducer(state = initialState, action) {
let immutableState = Immutable.fromJS(state);
switch (action.type) {
case actionTypes.INITIALIZING:
return immutableState.set('onState', onState.drawn).set('config', action.payload.initConfig).toJS();
case actionTypes.FETCH_DATA_REQUEST:
return immutableState.set('onState', onState.fetching).toJS();
case actionTypes.FETCH_DATA_SUCCESS:
return immutableState.set('onState', onState.drawn).setIn(['config','series',0,'data'],Immutable.fromJS(action.payload.mapData.data)).toJS();
case actionTypes.FETCH_DATA_FAILURE:
return immutableState.set('onState', onState.error).set('config', action.payload.mapData).toJS();
default:
return state;
}
}
Action is simple
export function initializeConfig(initConfig) {
return {
type: INITIALIZING,
payload: {
text: 'Initializing',
initConfig
}
}
}
export function requireMapData() {
return {
type: FETCH_DATA_REQUEST,
payload: {
text: 'Loading'
}
};
}
..........
//Async Action for fetching map data and redraw the map
export function fetchMapData(address) {
return function (dispatch) {
//dispatch requireMapData action to set the map in loading state
dispatch(requireMapData());
return fetch(address)
.then(fetchUtil.checkHttpStatus) //check if 404
.then(fetchUtil.parseJSON)
.then(mapData => dispatch(fetchDataSucceed(mapData)))
.catch(error => dispatch(fetchDataFailed(error)));
}
}
Thank you my friend.
You cannot duplicate your reducers in the manner you've depicted. Both are going to respond in the exact same way to the exact same actions.
The solution is to have all of your heat map data in the same reducer state. e.g.
const initialState = {
heatMap1: {},
heatMap2: {}
};
export default heatmap(state = initialState, action) {
// etc
Now if you want to use the same actions for both heat maps, you'll need to have an action property specifying which heap map you're targeting. If you have several heat maps, I'd recommend an array of heat maps with each action containing an index or id to target a particular heat map. e.g.
function updateHeatMap(index, value) {
return {
type: UPDATE_HEATMAP,
index: index,
value: value
}
}
You can also take a look at the multireducer module (https://github.com/erikras/multireducer). It was designed to solve exactly the scenario you propose.
So you would be able to configure your store as such:
import multireducer from 'multireducer';
import heatMap from './heat-map1'
import {combineReducers} from 'redux';
export let reducers = combineReducers({
multireducer: multireducer({
heatMap1: heatMap,
heatMap2: heatMap
})
});
After that, you would then need to use connectMultireducer() instead of redux's standard connect() in order to connect the specific slice of the store to particular components like so:
export default connectMultireducer((state)=> {
return {
onState: state.heatMap.onState,
config: state.heatMap.config
}
})(CHMSHeatMap)
And finally in order to get the correct part of the state to each of those components you would pass in the key when rendering them as such:
<CHMSHeatMap multireducerKey="heatMap1"/>
<CHMSHeatMap multireducerKey="heatMap2"/>
Obviously it's better to read the actual docs at the multireducer repo, but that should give a brief overview. Basically the module is just abstracting the process of adding a key-based lookup to each reducer that is created through the multireducer function.
I suggest original concept of multireducer working without any libraries.
The base idea is unique Symbol action types and self-contained Redux-module like this:
import * as services from './../../../api/services';
const initialState = {
list: [],
};
function getListReducer(state, action) {
return {
...state,
list: action.payload.list,
};
}
function removeItemReducer(state, action) {
const { payload } = action;
const list = state.list.filter((item, i) => i !== payload.index);
return {
...state,
list,
};
}
export default class List {
constructor() {
// action types constants
this.GET_LIST = Symbol('GET_LIST');
this.REMOVE_ITEM = Symbol('REMOVE_ITEM');
}
getList = (serviceName) => {
return async (dispatch) => {
const list = await services[serviceName].get();
dispatch({
type: this.GET_LIST,
payload: {
list,
serviceName,
},
});
};
}
removeItem = (index) => {
return (dispatch) => {
dispatch({
type: this.REMOVE_ITEM,
payload: {
index,
},
});
};
}
reducer = (state = initialState, action) => {
switch (action.type) {
case this.GET_LIST:
return getListReducer(state, action);
case this.REMOVE_ITEM:
return removeItemReducer(state, action);
default:
return state;
}
}
}
More information read there.
Related
I have a redux store with multiple teams.
const store = {
selectedTeamId: 'team1';
teams: {
team1: { ... },
team2: { ... },
team3: { ... },
},
};
At any given time a teamId is set.
Now given that I must select the team using the ID each time I call mapStateToProps(), I feel this is cumbersome.
Instead of doing this all the time:
mapStateToProps({ selectedTeamId, teams }) {
return {
team: teams[selectedTeamId],
}
}
Can I pre-process the store using some middleware instead of repeating this pattern in map state to props?
Approach suggested by Redux docs is to create a selector for currently active team and reuse it across all components
// selector itself is a pure function of state
// usually put in separate file, or in file with reducer
const activeTeamSelector = state => state.teams.teams[state.teams.selectedTeamId]
// in connect
const mapStateToProps = (state) => ({
activeTeam: activeTeamSelector(state),
})
That, of course, if you are using combineReducers and teams reducer is called teams in state. If you aren't, and selectedTeamId and teams are contained right in your store, following will work
const activeTeamSelector = state => state.teams[state.selectedTeamId]
Notice how I only had to change selector for this, and not every mapStateToProps in all the components
read more about Normalizing Store State and Computing Derived Data in Redux docs
Using a middleware for this scenario isn't performant (if I understood your question correctly :) ). I will outline 3 options you can use to achieve this:
Option 1
return both selectedTeamId and teams in mapStateToProps, this will allow you to find the team you need for each selected id:
mapStateToProps({ selectedTeamId, teams }) {
return {
selectedTeamId,
teams
}
}
That way you can access these props in render:
render() {
const { teams, selectedTeamId } = this.props;
return <Team team={teams.find(team => team.id === selectedTeamId)} />
}
Note: <Team /> is just a component I made for demonstration
Option 2
you can use reselect library to avoid recomputing this prop:
import { createSelector } from 'reselect'
const teams = state => state.teams;
const selectedTeamId = state => state.selectedTeamId;
const subtotalSelector = createSelector(
teams,
selectedTeamId,
(teams, selectedTeamId) => items.find(team => team.id === selectedTeamId)
)
Option 3
Create an action that will dispatch 'SELECT_TEAM' with the teamId
export function setSelectedTeam(id) {
return { type: types.SELECT_TEAM, payload: id };
}
Create a reducer for that type and return selectedTeam:
[types.SELECT_TEAM]: (state, payload)=> {
return {
...state,
selectedTeam: state.teams.find(team => team.id === payload.id)
};
},
That way you can have a selector for selectedTeam
export const getSelectedTeam = state => state.selectedTeam;
Hope it helps
I eventually used reselect, with thanks to the recommendation of #jank.
One of things I wanted to do was abstract away the need for selectors to appear in mapStateToProps. In order to do that, I wrapped redux connect. This allows insertion of a denormalizer function before mapStateToProps.
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
const getActiveTeamId = state => state.activeTeamId;
const getAllTeams = state => state.teams;
const teamSelector = createSelector(
getActiveTeamId,
getAllTeams,
(activeTeamId, teams) => teams[activeTeamId],
);
function denormalizer(mapStateToProps) {
return state => {
return mapStateToProps({ team: teamSelector(state) });
};
}
export default function reConnect(mapStateToProps = null, actions = null) {
const denormalizedMapStateToProps = denormalizer(mapStateToProps);
return function callConnect(Component) {
return connect(denormalizedMapStateToProps, actions)(Component);
};
}
I'm currently developing an app with React Native. The state of the app is quite complex, but managable due to Redux and Normalizr. I now have to implement a functionality for the user to filter items.
In order for the user to filter items, I enriched the server response in the Normalizr schema:
export const subCategorySchema = new schema.Entity(
"subCategories",
{},
{
idAttribute: "uuid",
processStrategy: entity => {
const newEntity = Object.assign({}, { name: entity.name, uuid: entity.uuid, chosen: false });
return newEntity;
}
}
);
The corresponding reducer now looks like this:
const initialState = {};
const subCategoriesReducer = (state = initialState, action) => {
if (action.payload && action.payload.entities) {
return {
...state,
...action.payload.entities.subCategories
};
} else {
return state;
}
};
These the subcategories now get displayed in the UI using this SwitchListItem component, which gets it's items through a selector:
import React, { Component } from "react";
import { Switch, Text, View } from "react-native";
import PropTypes from "prop-types";
import styles, { onColor } from "./styles";
export default class SwitchListItem extends Component {
static propTypes = {
item: PropTypes.object
};
render() {
const { name, chosen } = this.props.item;
return (
<View style={styles.container}>
<Text style={styles.switchListText}>{name}</Text>
<Switch style={styles.switch} value={chosen} onTintColor={onColor} />
</View>
);
}
}
I'm now about to implement the <Switch /> component's onValueChange() function, which is where my question arose:
What is the best way to toggle a boolean value in a normalized state tree?
I came up with two solutions, which I will describe below. Please let me know if you think any one of these is good. If not I would love to get advice on what I could do better :)
Solution 1: Extending the reducer:
My first solution for the problem was to extend the reducer to listen to TOGGLE_ITEM actions. This would look something like this:
const subCategoriesReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_ITEM:
if (action.payload.item.uuid in state) return { ...state, ...action.payload.item };
}
if (action.payload && action.payload.entities) {
return {
...state,
...action.payload.entities.subCategories
};
} else {
return state;
}
};
This is my preferred solution as it does not need a lot of code.
Solution 2: Enriching the selector that passes the items to the SwitchList:
The other solution would be to enrich the objects while being passed to the list using a selector with it's key for the state. Then I could create an action that uses this key to update the state like this:
const toggleItem = (item, stateKey) => ({
type: TOGGLE_ITEM,
payload: {entities: { [stateKey]: item } }
})
I would love to read an answer, preferably opinionated, if you have a lot of experience with Redux. Also, if you think my way of enriching the data in the normalizr is bad and you can come up with a better way, please let me know! Thank you very much for any advice!
I did it in a completely different way.
I created an array that holds the uuids of the toggled items. Therefore I only need to look, whether the item is in the toggled array.
Just like this:
const initialState = {};
export const byId = (state = initialState, action) => {
if (action.payload && action.payload.entities && action.payload.entities.itemClassifications) {
return {
...state,
...action.payload.entities.itemClassifications
};
} else {
return state;
}
};
export const chosen = (state = [], action) => {
if (action.type === TOGGLE_ITEM && action.meta === ITEM_CLASSIFICATION) {
if (state.includes(action.payload.uuid)) {
return state.filter(uuid => uuid !== action.payload.uuid);
} else {
return [...state, action.payload.uuid];
}
} else {
return state;
}
};
const itemClassificationsReducer = combineReducers({
byId,
chosen
});
export default itemClassificationsReducer;
export const getAllItemClassificationsSelector = state =>
Object.values(state.itemClassifications.byId);
export const getAllItemClassificationsNormalizedSelector = state => state.itemClassifications.byId;
export const getChosenItemClassificationsSelector = state => state.itemClassifications.chosen;
export const enrichAllItemClassificationsSelector = createSelector(
getAllItemClassificationsSelector,
itemClassifications =>
itemClassifications.map(val => ({ ...val, stateKey: ITEM_CLASSIFICATION }))
);
export const getItemClassificationsFilterActiveSelector = createSelector(
getChosenItemClassificationsSelector,
itemClassifications => itemClassifications.length > 0
);
I have the following reducer
import { EntityState, createEntityAdapter } from '#ngrx/entity';
import { createFeatureSelector } from '#ngrx/store';
export const pageAdapter = createEntityAdapter<Page>({
//selectId: (collection: Collection) => collection.id,
});
export interface State extends EntityState<Page> {
}
const defaultPage = {
ids: ['kHnryus'],
entities: {
'kHnryus': {
id: '83nryus',
title: 'How to create a blog with Angular4 Ngrx',
description: 'How to create a blog with Angular4 Ngrx',
}
},
success_create: false
}
export const initialState: State = pageAdapter.getInitialState();
// Reducer
export function pageReducer(
state: State = initialState,
action: actions.PageActions) {
switch (action.type) {
case actions.ADD_ALL: {
return pageAdapter.addAll(action.pages, state);
};
case actions.SUCCESS: {
return {success_create: true}
};
default:
return state;
}
}
// Create the default selectors
export const getPageState = createFeatureSelector<State>('page');
export const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = pageAdapter.getSelectors(getPageState);
I want to get the boolean variable success_create of the state in my component.
Basically , I want that if there is SUCCESS, I should be able to get a success_create true in the component class. I have no idea as to how to do this and even if it's possible.
If it is, please how can I achieve this?
First make sure to remove defaultPage constant as you are not maintaining it within your reducers and you are already using #ngrx/entity for that. The success_create should be defined as follow:
export interface State extends EntityState<Page> {
success_create: boolean;
}
export const adapter: EntityAdapter<Item> = createEntityAdapter<Page>({
selectId: (page: Page) => page.id,
sortComparer: false
});
export const initialState: State = adapter.getInitialState({
success_create: false,
});
Then, after your default selectors add a third one that uses getPageState selector and gets one step deeper into your state:
export const getSuccessCreate = createSelector(
getPageState,
(state: State) => state.success_create
);
Then make your component/service listening to it directly like:
this.created$ = this.store.pipe(select(fromPages.getSuccessCreate));
note: the pipe is optional here and if used then select should be imported from #ngrx/store as it is done in the official ngrx demo app. pipe allow you to work with lettable rxjs operators. you can read more about it here.
In #ngrx/store 2.0 we could provide the root reducer as a function and from there we split our logic inside the application. After I updated to #ngrx/store 4.0 I cannot use this feature any more from what I can see the reducers need to be a map of reducers which will create objects under the same keys in the state. Is there a way to use the old behavoir in #ngrx/store 4.0 In my state components are aware one of another and I need to be able to split my state dynamically also I need to be able to dispatch actions to the right reducer in my own way. Also app is splitted in multiple lazy loaded routes which in some cases reuse the data from another feature.
StoreModule.provideStore(reducer, {
auth: {
loggedIn: true
}
})
StoreModule.forRoot(reducers, {
initialState: {
auth: {
loggedIn: true
}
}
})
I need reducers to be a function which gets the full state and dispatches it to the correct reducer, Is there a way to achieve this behavior?
After I had a second look over ngrx repo I figured it out. To achieve the wanted result we need to replace the #ngrx/store reducer factory with a new implementation. I injected a new reducer factory and right now the application works as before. Simple code sample on how to replace the reducer factory it.
// This factory replaces #ngrx combine reducers so we can manage how we split the keys inside the state
export function combineReducersFactory(
reducers: any,
initialState: any = {}
): ActionReducer<any, Action> {
return function combination(state = initialState, action) {
const nextState: any = reducers(state, action);
return nextState !== state ? nextState : state;
};
}
export const NG_RX_STORE_PROVIDER = [
StoreModule.forRoot(rootReducer, createEmptyState()),
];
export const NG_RX_REDUCER_FACTORY = [
{
provide: REDUCER_FACTORY,
useFactory: () => combineReducersFactory
}
];
#NgModule({
imports: [
...NG_RX_STORE_PROVIDER
],
declarations: [...APP_COMPONENTS, ...AG_GRID_COMPONENTS],
providers: [...NG_RX_REDUCER_FACTORY]
})
export class AppModule {
}
You can set up a meta reducer to receive every event and manipulate the state from its root. Here is an example way to set it up:
const myInitialState = {
// whatever you want your initial state to be
};
export function myMetaReducer(
reducer: ActionReducer<RootStateType>
): ActionReducer<RootStateType> {
return function(state, action) {
if (iWantToHandleThisAction) {
state = doWhatIWantWith(state);
}
return reducer(state, action);
};
}
#NgModule({
imports: [
StoreModule.forRoot(myInitialState, { metaReducers: [myMetaReducer] })
]
})
export class AppModule {}
The StoreModule forRoot() function accepts a reducerFactory which can be used as follows:
export function myReducerFactory(reducers: any, initState: any) {
return (state = myInitialState, action) => myCustomReducer(state, action);
}
#NgModule({
// ...
imports: [
StoreModule.forRoot(null, { reducerFactory: myReducerFactory })
]
// ...
})
export class AppModule {
}
This works for me:
// your old reducer that handled slicing and dicing the state
export function mainReducer(state = {}, action: Action) {
// ...
return newState;
}
// new: metaReducer that just calls the main reducer
export function metaReducer(reducer: ActionReducer<AppState>): ActionReducer<AppState> {
return function (state, action) {
return MainReducer(state, action);
};
}
// new: MetaReducer for StoreModule.forRoot()
export const metaReducers: MetaReducer<any>[] = [metaReducer];
// modified: app.module.ts
#NgModule({
// ...
imports: [
// neglect first parameter ActionReducerMap, we don't need this
StoreModule.forRoot({}, {
metaReducers: metaReducers,
initialState: INITIAL_STATE // optional
}),
]
})
My component get some properties via props with the function:
const mapStateToProps = state => {
const { entities: { keywords } } = state
const {locale} = state
return {
keywords: keywords[locale]
}
}
I got state keywords using ajax, in the same component:
componentDidMount() {
this.props.loadKeywords()
}
My component gets rendered twice. First, before the ajax resolves, so in my render method I got undefined:
render() {
const { keywords } = this.props.keywords
...
Which is the proper way to solve it? I changed componentDidMount to componentWillMount without success.
Right now, based on the real-world example, I have initialized keywords state with an empty object:
function entities(state = { users: {}, repos: {}, keywords: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
My reducer:
import { combineReducers } from 'redux'
import { routerReducer as router } from 'react-router-redux'
import merge from 'lodash/merge'
import locale from './modules/locale'
import errorMessage from './modules/error'
import searchText from './modules/searchText'
// Updates an entity cache in response to any action with response.entities.
function entities(state = { users: {}, repos: {}, keywords: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
export default combineReducers({
locale,
router,
searchText,
errorMessage,
entities
})
My action:
import { CALL_API, Schemas } from '../middleware/api'
import isEmpty from 'lodash/isEmpty'
export const KEYWORDS_REQUEST = 'KEYWORDS_REQUEST'
export const KEYWORDS_SUCCESS = 'KEYWORDS_SUCCESS'
export const KEYWORDS_FAILURE = 'KEYWORDS_FAILURE'
// Fetches all keywords for pictos
// Relies on the custom API middleware defined in ../middleware/api.js.
function fetchKeywords() {
return {
[CALL_API]: {
types: [ KEYWORDS_REQUEST, KEYWORDS_SUCCESS, KEYWORDS_FAILURE ],
endpoint: 'users/56deee9a85cd6a05c58af61a',
schema: Schemas.KEYWORDS
}
}
}
// Fetches all keywords for pictograms from our API unless it is cached.
// Relies on Redux Thunk middleware.
export function loadKeywords() {
return (dispatch, getState) => {
const keywords = getState().entities.keywords
if (!isEmpty(keywords)) {
return null
}
return dispatch(fetchKeywords())
}
}
All based on the Real world redux example
My Solution
Given initial state to keywords entity. I'm getting json like this through ajax:
{'locale': 'en', 'keywords': ['keyword1', 'keyword2']}
However as I use normalizr with locale as id, for caching results, my initial state is as I describe in the reducer:
function entities(state = { users: {}, repos: {}, keywords: { 'en': { 'keywords': [] } } }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities)
}
return state
}
What I don't like is the initial if we have several languages, also remembering to modify it if we add another language, for example fr. In this
keywords: { 'en': { 'keywords': [] } }
should be:
keywords: { 'en': { 'keywords': [] }, 'fr': { 'keywords': [] } }
This line looks problematic:
const { keywords } = this.props.keywords
It's the equivalent of:
var keywords = this.props.keywords.keywords;
I doubt that's what you intended.
Another thing worth checking is keywords[locale] in your mapStateToProps() which will probably initially resolve to undefined. Make sure your component can handle that, or give it a sensible default.