Simultaneous mount of multiple components overrides redux state - redux

In my react-redux application, when I mount 2 components together, state of first is being over-ridden by second.
In my first component, I have following state and actions defined:
function mapStateToProps(state) {
return {
comp1: state.comp1,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: {
action1: bindActionCreators(action1, dispatch),
action2: bindActionCreators(action2, dispatch),
}
};
}
And the second component looks something like this:
function mapStateToProps(state) {
return {
comp2: state.comp2,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: {
action3: bindActionCreators(action3, dispatch),
action4: bindActionCreators(action4, dispatch),
}
};
}
But when both the components are finally mounted, only second components state and actions remain. And 1st component's state and action become undefined.
I simply render the components this way:
<Comp1/>
<Comp2/>
Redux version: 3.7.2
React-redux version: 5.0.6
Any help is appreciated. Thanks in advance!

Related

MapDispatchToPros with event

I have this part of my code:
constructor(props) {
super(props)
this.state = {
symbol: '',
side: '',
status: ''
};
this.onInputChange = this.onInputChange.bind(this);
this.onValueChangeSide = this.onValueChangeSide.bind(this);
this.onValueChangeStatus = this.onValueChangeStatus.bind(this);
this.onFormSelect = this.onFormSelect.bind(this);
}
onInputChange(event) {
this.setState({ symbol: event.target.value });
}
onValueChangeSide(event) {
this.setState({ side: event.target.value});
}
onValueChangeStatus(event) {
this.setState({ status: event.target.value});
}
onFormSelect(event) {
this.props.requestAccountsFilter(this.state.symbol, this.state.side,
this.state.status);
}
The requestAccountsFilter is an Action. Its code is:
export function requestAccountsFilter(symbol, side, status) {
return {
type: ACCOUNT_FILTERS,
payload: {
symbol,
side,
status
}
};
}
That approach works fine.
Furthermore, i want to make my component Stateless so i create a container. My problem is that i don't know how to dispatch my action with the above functionality.
I write this:
const MapDispatchToProps = dispatch => (
{
requestAccountsFilter: (symbol, side, status) => {
dispatch(requestAccountsFilter(symbol, side, status));
}
}
);
but it didn't work.
How to dispatch my action in the MapDispatchToProps??
The purpose of mapDispatchToProps is not to actually dispatch actions directly.
mapDispatchToProps is used to bind action creators with dispatch and pass these new bound functions as props to the component.
The main benefit of using mapDispatchToProps is that it makes our code cleaner by abstracting away the store's dispatch method from components. Therefore we can call props that are functions without acces to dispatch like so
this.props.onTodoClick(id);
Whereas if we didn't use mapDispatchToProps then we would have to pass dispatch separately to components and dispatch actions like so:
this.props.dispatch(toggleTodo(id));
You would use mapDispatchToProps as shown in your example code, and then elsewhere write:
mapDispatchToProps must be a pure function and cannot have side effects.
Dispatching actions from inside the function would be considered a side effect. A function has one or more 'side effects' when the evaluation of the function changes state outside of itself (changes global state of app)
Instead use lifecycle hooks to dispatch actions in response to prop changes on components:
class exampleComponent extends Component {
componentDidMount() {
this.props.fetchData(this.props.id)
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.props.fetchData(this.props.id)
}
}
// ...
}

mapStateToProps and mapDispatchToProps: getting IDE to "see" the props

Problem: IDE does not resolve props passed to the component via connect()
Note: this is not a bug, but an inconvenience to the coder
Say I have this React component connected to Redux via connect():
class SomeComponent extends Component {
render() {
return (
{this.props.someObject ? this.props.someObject : ''}
);
}
}
function mapStateToProps(state) {
return {
someObject: new SomeObject(state.someReducer.someObjectInfo),
};
}
function mapDispatchToProps(dispatch) {
return {
// ...
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatsList);
I'm using the IntelliJ IDE, and any prop connected to the component in the above manner, such as someObject, will get an unresolved variable warning. And if someObject has some properties/methods, they will neither be resolved nor show up in code suggestions (which are really helpful).
A workaround
Pass state and dispatch themselves as props:
function mapStateToProps(state) {return {state};}
function mapDispatchToProps(dispatch) {return {dispatch};}
Define my variables in the constructor (as opposed to via props):
constructor(props) {
this.someVar = props.state.someReducer.someVar;
this.someObj = new SomeObject(props.state.someReducer.someObjectInfo;
}
Update the variables manually whenever props change:
componentWillReceiveProps(nextProps) {
someObject.update(nextProps.state.someReducer.someObjectInfo);
}
The drawback is having additional boilerplate logic in componentWillReceiveProps, but now the IDE happily resolves the variables and code suggestion works.
Question
Is the workaround preferable? I'm using it, like it so far, and have not observed any other drawbacks thus far. Is there a better way to get the IDE to understand the code?
Motivation (verbose; only for those interested in why I want to accomplish the above)
The Redux tutorials show a simple way to connect state/dispatch to props, e.g.:
function mapStateToProps(state) {
users: state.usersReducer.users
chats: state.chatsReducer.chats
}
function mapDispatchToProps(dispatch) {
addUser: (id) => dispatch(usersActions.addUser(id))
addChatMsg: (id, msg) => dispatch(chatsActions.addChatMsg(id, msg)
}
In the example above, the coder of a component will need to know every relevant reducers' names and their state variables. This can get messy for the coder. Instead, I want to abstract these details away from the component. One way is with a "module" class that accepts state and dispatch, and provides all get/set methods:
class Chats {
// Actions
static ADD_MESSAGE = "CHATS/ADD_MESSAGE";
constructor(globalState, dispatch) {
this.chatsState = globalState.chats;
this.dispatch = dispatch;
}
// Get method
getChats() {
return this.chatsState.chats;
}
// Set method
addChatMessage(id, msg) {
return this.dispatch({
type: Chats.ADD_MESSAGE,
id,
msg
};
}
// Called by componentWillReceiveProps to update this object
updateChats(nextGlobalState) {
this.chatsState = nextGlobalState.chats;
}
}
Now, if a Component requires the Chats module, a coder simply does this:
class SomeComponent extends Component {
constructor(props) {
this.chats = new Chats(props.state, props.dispatch);
}
componentWillReceiveProps(nextProps) {
this.chats.updateChats(nextProps);
}
// ...
}
And now, all Chats get/set methods and properties will be available, and will be picked up by the IDE.
I think newest Idea can now understand component properties defined via propTypes and provides code completion for them. So you just declare propTypes. And it is not even a workaround, it's a good practice in my opinion.
class ChatsList extends Component {
static propTypes = {
someObject: PropTypes.shape({
color: PropTypes.string,
someFunc: PropTypes.func
}),
someDispatcher: PropTypes.func
};
render() {
return (
{this.props.someObject ? this.props.someObject : ''}
);
}
}
function mapStateToProps(state) {
return {
someObject: new SomeObject(state.someReducer.someObjectInfo),
};
}
function mapDispatchToProps(dispatch) {
return {
someDispatcher: Actions.someDispatcher
// ...
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatsList);
Also, passing the entire state is a bad idea, since a component will receive props and get re-renderend if anything changes in the entire state (unless you provide shouldComponentUpdate)

Redux pattern for populating the state, and then building components from that state?

I've got a component that builds search/sort filters that can be selected. I want the selected state of those filters to be tracked in redux so that the search builder can subscribe and see when they change and update appropriately. the thing I'm trying to figure out how to do (in a way that doesn't feel weird) is populate the filter objects into the state. Eg, right now in the <Search /> component I have something like:
<OptionPicker
group={'searchFilters'}
options={{word: 'price', active: true},
{word: 'distance', active: false},
{word: 'clowns', active: false}}
/>
So how to get those props into state to be used without triggering multiple element renders. I'm also rendering the app on the server as well, so for the initial attachment render, the state already has the options.
In the OptionPicker component I've got:
class OptionPicker extends Component {
constructor(props) {
super(props)
if (!props.optionstate) {
this.props.addOptionState(props)
}
}
render() {
return {this.props.optionstate.word.map((word) => <Option ... />)}
}
}
function mapStateToProps(state, props) {
return {
optionstate: state.optionstate[props.group],
};
}
function mapDispatchToProps(dispatch) {
return {
addOptionState: (props) => {
dispatch(addOptionState(props));
},
optionToggled: (group, word) => {
dispatch(updateOptionState(group, word));
}
};
}
export default connect(mapStateToProps,mapDispatchToProps)(OptionGroup);
This kinda works, but there exists a time when render is called before the redux state has been populated, which throws an error. I could guard against that, but none of this feels "right". Shouldn't that prop always be there? Is there a pattern for this that I'm missing?
I agree with you in that the prop should always be there. The pattern I use for this is to set up an initial state and to pass it to the reducer function:
export const INITIAL_STATE = {
optionstate: { /* all filters are present but deactivated */ }
};
export default function (state = INITIAL_STATE, action) {
// reduce new actions into the state
};

How to show injected props by Redux on console

I can change the states and update the UI by using Redux. But how to show injected props by Redux on console by using console like console.log(this.props) in run-time. I cannot. I've never seen the props.
Is there a way to show component (class) props -that are assigned from Redux store like the code below-?
function mapStateToProps(state) {
return { iconSize: state.iconSize }
}
function mapDispatchToProps(dispatch) {
return {
setIconSize: (size) => dispatch(setIconSize(size))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Main)
In this example you can rewrite mapStateToProps function:
function mapStateToProps(state) {
const props = { iconSize: state.iconSize };
console.log(props);
return props;
}
It will log props each time this function is invoked.

Redux action reuse

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.

Resources