Should mapDispatchToProps dispatch initialization actions? - javascript

Suppose a stateless, functional UserProfile component that displays user data for the given url. Suppose it is being wrapped with connect(mapStateToProps, mapDispatchToProps)(UserProfile). Finally, suppose a reducer that reduces into state.userProfile. Anytime the url changes, I need to re-initialize the state.userProfile, so a solution that comes to mind is to do so from within the mapDispatchToProps like so:
function mapDispatchToProps(dispatch, ownProps) {
dispatch(fetchUser(ownProps.userId))
return {
...
}
}
Provided that the thunked fetchUser ignores repeated calls by comparing with current state, is this an acceptable practice? Or are there problems associated with calling dispatch immediately from this map function?

This is unsupported and can break at any time.
mapDispatchToProps itself should not have side effects.
If you need to dispatch actions in response to prop changes, you can create a component class and use lifecycle methods for this:
class UserProfile extends Component {
componentDidMount() {
this.props.fetchUser(this.props.id)
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.props.fetchUser(this.props.id)
}
}
// ...
}

Related

Understanding mapStateToProps & mapDispatchToProps in React-Redux

I'm trying to understand the connect() method of react-redux. Usually it takes two function as argument: mapStateToProps() & mapDispatchToProps(). I write a example for myself, here is connect() section of my User component:
//imports...
class User extends Component {
/* constructor, JSX, other functions... */
}
const mapStateToProps = (state) => {
return {
users: state.UserReducer
};
};
const mapDispatchToProps = (dispatch) => ({
deleteUser: (id) => dispatch(deleteUser(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(User);
According to Docs I have taken the following two conclusions about mapStateToProps() & mapDispatchToProps():
mapStateToProps(): it makes that state available in our component. i.e. it is used to pass reducer to component.
mapDispatchToProps(): it maps component relevant functions to action functions, i.e. with this function We can perform the action that we want in our component.
is my conclusions correct?
React components accept data from outside via props.
maptStateToProps and mapDispatchToProps, literally, pass the selected state properties and actions that are needed inside your component as props.
The state values and actions passed to the component are available in the props of the component.
In your example, you can use this.props.users or this.props.deleteUser().

How do I avoid re-rendering a connected React PureComponent due to mapDispatchToProps functions?

whenever mapDispatchToProps is called, it generates new functions to return. For example:
const mapDispatchToProps = function(dispatch, ownProps) {
return {
addToStack: (episodeId, stackId) => {
return dispatch(StackAction.addEpisodeToStack(episodeId, stackId));
},
};
}
Every time the mapDispatchToProps is called, it will generate a new object, with a new arrow function.
In my application I often have to avoid re-rendering my components. Using a PureComponent is often the way to go. However, since the functions are always different, PureComponent won't help and I'd have to create a shouldComponentUpdate strategy. There, I'd have to "blacklist" all of the mapDispatchToProps functions and ignore all of them. I'd have to add every new function to the list so it'd avoid re-rendering.
here is an example of the blacklist shouldComponentUpdate boilerplate:
const blacklist = [
'addToStack',
]
shouldComponentUpdate(nextProps, nextState) {
for (let i in nextProps) {
if (blacklist.includes(i)) continue;
if (nextProps[i] !== this.props[i]) {
return true;
}
}
for (let i in nextState) {
if (nextState[i] !== this.state[i]) {
return true;
}
}
return false;
}
I've come up with a new solution
const dispatch;
const mapDispatchToPropsFunctions = {
addToStack: (episodeId, stackId) => {
return dispatch(StackAction.addEpisodeToStack(episodeId, stackId));
},
};
const mapDispatchToProps = function(dispatchArg, ownProps) {
dispatch = dispatchArg
return mapDispatchToPropsFunctions;
}
this way the functions are constant and won't trigger a re-rendering of a PureComponent and I don't have to maintain a shouldComponentUpdate function.
however this seems wrong to me.
Is there a "default" way of dealing with this problem?
I generally advise to not try to recreate functions that capture props values like that, but instead have a handler method on your class that passes prop values to the action creator. I also advise that people not write mapDispatch functions directly, but use the "object shorthand" for connect().
Example:
const actions = {addToStack : StackAction.addEpisodeToStack};
class MyComponent extends React.Component {
addToStack = () => {
this.props.addToStack(this.props.episodeId, this.props.stackId);
}
}
In your specific snippet, it looks like you're not even referencing any props values in mapDispatch anyway, so there was no need to declare the ownProps parameter. (connect will only call a mapDispatch function multiple times if the ownProps parameter is requested. Otherwise, it only calls mapDispatch once, when the component is created.)
Turns out that the actual solution is to not use mapDispatchToProps at all.
In the react-redux docs there is a reference to a shorthand method:
If an object is passed, each function inside it is assumed to be a
Redux action creator. An object with the same function names, but with
every action creator wrapped into a dispatch call so they may be
invoked directly, will be merged into the component’s props.
So the solution is to, instead of providing a function that binds dispatch to your action creators, just provide to connect an object containing your action creators as second argument. Connect will only bind them once and use the same functions all the time, avoiding unnecessary re-renders.
This should be in fact the preferred way of supplying action creators to connect.

Save local state in React

In my App I use several Container components. In each Container, there are Buttons.
Depending on the state of the App, the Buttons are clickable (or not). Whether a Button is disabled or not, is managed in the local state of each Container.
The results and state of the App can be saved and loaded.
And here comes the problem:
When I save (or load) the App, its rather hard to "extract" the state of the Buttons from each Container. Saving in the global state (Redux)is rather easy.
But how can I save the local state from each Container and how can I feed it back to each Container?
Reading the local state is managed through a parent Component which calls methods from a child Component. I am aware that this is an antipattern, but it works.
export class SomeConmponent {
....
onClickSaveProjecthandler(event) {
const localStateProjectSettings = this.childProjectSettings.getLocalState();
const localStateLayerFilter = this.childLayerFilter.getLocalState();
return {
"ProjectSettings": localStateProjectSettings,
"Filter": localFilter
};
}
render() {
return(
<ProjectSettingsContainer onRef={ref => (this.childProjectSettings = ref)}/>
)
}
}
Any better suggestions?
As you already mentioned, using redux to have a single point of truth is a great ideia. And to "feed" the state back to containers, you have to map state and props to your components.
This is a container example brought from the oficial doc:
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(setVisibilityFilter(ownProps.filter))
}
}
}
const FilterLink = connect(
mapStateToProps,
mapDispatchToProps
)(Link)
export default FilterLink
The connect does all the magic.
Sounds like you could use Redux and you somehow miscomprehended its architecture as being synonymous with global state only. One of the reasons Redux was made was to address the issue of saving states of multiple independent components.
However, here's an entirely different take on the answer: why not use a global state serializer and connect each component to it? If you don't like the idea of referring to a global variable, a better alternative would be to create some sort of dependency injection (DI) framework that works with React. I've created a library a while back, called AntidoteJS, that does exactly this. You don't need to use it, but it shows how you how it can be done. Just take a look at the source.
Here is something quick and dirty. I haven't tested it, but it shows the basic idea:
import { inject } from "antidotejs"
export class MyComponent extends React.Component {
#inject("StateSerializer")
serializer
constuctor(props, children) {
super(props, children);
this.serializer.load(props.id);
}
setState(newState) {
super.setState(newState);
this.serializer.save(newState);
}
}

Handle async actions that do not store result in state

I stumbled upon a requirement on a section of a vanilla JS webapp that requires a single JSON "definitions" object to render. Definitions are loaded via an HTTP request at the very beginning, read, parsed and handed down to another layer of the app. The object itself never changes throughout its life cycle.
I'm now trying to model this scenario in ReactJS, using Redux + redux-thunk. I created a thunk/async action the fetches the JSON object, extracts what it needs and ends up updating the state with that -
but it does not store the object itself in the state. This seems like the right, logical approach since, as mentioned, the definitions are never modified in any way. I'd argue it's simply not state, in a strict sense.
However, by taking that decision I ended up struggling while implementing the actual React.Component. Almost every single example I've seen out there in the wild for async cases like this one:
Defines a thunk action that fires some API call.
Stores whatever they got back (or after some alterations) in a state property.
Maps that property to this.props in the Component with mapStateToProps and connect.
In my case, I don't really have a state property to bind to. So I ended up returning the definitions object in my async action and using the component's local state to get what I needed.
class ContainerComponent extends React.Component {
state = { definitions: {} };
componentDidMount() {
const { dispatch } = this.props;
dispatch(fetchDefinitions())
.then((definitions) => this.setState({ definitions }));
}
render() {
return (<PresentationalComponent definitions={this.state.definitions} />);
}
}
export default connect()(ContainerComponent);
Not saying that this.setState should be avoided, but this looks an awful lot like what I had before even introducing Redux: an API call returning a promise - only with a lot more meddling indirections.
componentDidMount() {
const { dispatch } = this.props;
fetch(`${API_URL}/definitions`)
.then((res) => res.json())
.then((definitions) => this.setState({ definitions }));
}
So, how should I go about this? Is there any particular thing I am missing here? Any pattern I should be following? Perhaps, avoiding Redux entirely for this matter?
You are right in that having a component state isn't necessarily a bad thing, but I believe you are confused on where to store that data once the API call is made.
You mention that it is not necessarily state, but I would argue otherwise. Prior to making the API call, your application does not have that data. You may have certain UX/UI indications at the start up of your application that, for example could indicate on if the data is being fetched: definitions.all.isFetching.
In your componentDidMount, dispatching the action to fetch the data is correct. Once the action is fired, and the success response is received, your reducer should save the definitions to your redux store like
import { assign, get } from 'lodash';
const all = (
state = { isFetching: false, data: [] },
action,
) => {
switch (action.type) {
case types.LIST_DEFINITIONS:
return assign({}, state, { isFetching: true });
case types.LIST_DEFINITIONS_SUCCESS:
return assign({}, state, get(action, 'result.data'), { isFetching: false });
default: return state;
}
};
Then in your component, you would connect your redux store
function mapStateToProps(state){
return {
definitions: state.definitions.all.data
};
}
export default connect(mapStateToProps, { listDefinitions })(ContainerComponent);
Also note I moved the action out in my example and am placing it into the connect with mapDispatchToProps shorthand.

React/Redux, implementing multiple actions with Redux Thunk

I am learning a react/redux and have an application with two main pieces of state:
An array of items
An object that contains user-specified filters for those items
I have three functions/actions, createFilter, updateFilter, and deleteFilter that modify the state of #2. I have an action filterItems that modifies #1 based on the state of #2. So whenever #2 changes, this action needs to be dispatched.
This is the component I am working with:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { createFilter } from '../actions/actions'
import { updateFilter } from '../actions/actions'
import { deleteFilter } from '../actions/actions'
import { filterItems } from '../actions/actions'
class ItemList extends Component {
createFilter(input) {
this.props.createFilter(input)
this.props.filterItems()
}
updateFilter(input) {
this.props.updateFilter(input)
this.props.filterItems()
}
deleteFilter() {
this.props.deleteFilter()
this.props.filterItems()
}
...
// Render method
...
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ createFilter, updateFilter, deleteFilter, filterItems }, dispatch)
}
function mapStateToProps({ itemList }) {
return { itemList }
}
export default connect(mapStateToProps, mapDispatchToProps)(ItemList)
What I have found is that when one of the filter methods are sent, the store (state #2) is not yet updated by the time filterItems() is called.
So I need to asynchronously execute the filter functions, and once the store is updated call filterItems.
I am struggling on how to do this with react-thunk. If the first function was an ajax promise I would use .then():
export function updateFilterAndEvaluate(input) {
return (dispatch, getState) => {
updateFilter(input).then(dispatch(filterItems(getState().filters)))
}
}
But these are just functions, and don't have a .then() method. I am trying to figure out what my best course of action is for this implementation. Can I wrap Redux actions in a promise? Am I misusing Thunk? Or should I attempt a different pattern entirely?
I have an action filterItems that modifies #1 based on the state of #2.
This is, generally speaking, an anti-pattern. Since the result array can be computed from the source array and the currently active filters, you shouldn’t be keeping it in the state.
Redux actions should generally look like “events” (e.g. what happened). “Filter was created” and “filter was updated” are good actions. “Filter them now!” looks more like a command, this is usually a sign that it shouldn’t have been an action in the first place, and should be something the components do as they select the data to render.
Instead, do the filtering as part of your mapStateToProps() function when you prepare data for the components. If it gets expensive, look into using Reselect to compute derived data efficiently.
As for your specific question,
What I have found is that when one of the filter methods are sent, the store (state #2) is not yet updated by the time filterItems() is called.
This is incorrect and indicates some other problem in your code. (It’s hard to tell where because the example is incomplete). In Redux, dispatch() is synchronous (unless you have some middleware that delays or batches it which usually isn’t the case), so you don’t need to “wait” for it to finish if it just operates on the local data.
However, in any case, filterItems() is not a very good fit for an action, and I suggest you to look into filtering in mapStateToProps() as I wrote above.

Categories

Resources