Access props.location object from redux-saga - reactjs

My end goal is to access this.props.location.pathname inside redux-saga when making API calls. Here's my current working solution, albeit with react raising an error. I'm using mxstbr/react-boilerplate-brand as my codebase.
In my wrapping component, App, I have the following line in my render method.
render() {
this.props.onUpdateLocation(this.props.location)
}
In my mapDispatchToProps I have following. Basically I'm just saving this.props.location into the React store:
function mapDispatchToProps(dispatch) {
return {
onUpdateLocation: (location) => {
dispatch(updateLocation(location));
},
dispatch,
};
}
Inside my redux-saga I access the location from state and just use it as need be; however, here's the error React raises.
warning.js:44 Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
I can't put it in componentWillMount because that only gets fired once when the app starts, and I can't put it in componentWillUpdate because this.props.location gets updated in the render method. I can't put it in componentDidUpdate because that's too late.
Am I just missing some easy obvious way to access the react-router location inside my redux-saga?

if you have <Route path='profile' component={ Profile } />
the Profile component can access the react-router props in the second argument ownProps of:
mapStateToProps(state, [ownProps]) and
mapDispatchToProps(dispatch, [ownProps])

TL;DR:
export class App extends React.Component {
componentWillMount() {
this.props.onUpdateLocation(this.props.location.pathname);
}
componentWillReceiveProps(nextProps) {
this.props.onUpdateLocation(nextProps.location.pathname);
}
render() {
// Render stuff here
}
}
function mapDispatchToProps(dispatch) {
return {
onUpdateLocation: (location) => {
dispatch(updateLocation(location));
},
dispatch,
};
}
My reducer receives the action and updates location in the state. Now I can access the current pathname using a selector that grabs location from the state.
Long Answer:
IGL's answer is pretty good info, especially the wildcard naming info that you can use in routes and is returned by the ownProps param of mapDispatchToProps. Here is how I solved the problem...
Initially I thought the warning was about be accessing this.props.location or something else; however, it was a much simpler issue. React doesn't like it when you call a function without it being because of an action like a click. The warning message, which suggest a solution, led me on the right track.
First to see what fired when and where, I placed each of the React lifecycle functions in my code, console logging when they were hit. componentWillMount, componentDidMount, componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, componentDidUpdate, componentWillUnmount.
I discovered that componentWillMount is fired on initial page load, and componentWillReceiveProps is fired on each navigation. With that in mind, I console logged this.props.location and discovered that componentWillReceiveProps still had the old location; however, it's takes one param, nextProps that has the new location. so, nextProps.location is what I want. I placed this in my App container, which takes other containers as its children, and now I can access the current location in my sagas where I use it to make API calls.

Related

Preserve react component state on unmount

React newbie here: I have noticed that the component state gets cleared on unmount. is there a way to prevent it? is it using redux?
As you say, when a component is unmount you can not access to the state. And thats the way it is because the lifecicle of the component.
What you can do is try to save the state of the component that was instantiated while it is mounted and every time it is updated.
You can use the storage and use simple javascript.
You can have the state in the parent or another ancester instance.
You can have the state in the redux store. Note that your component will receive the vars as props, so it wont be the state properly said.
You can use redux in order to manage the state and the states values through time. I recommend you the redux-devtools-extension and also this article about it.
You have a plethora of options. You can either use a state management tool, like redux, context API and so on, or you can pass-in a callback to your parent component and trigger it on childComponentWillUnmount like this:
ParentComponent.jsx:
childComponentWillUnmount = (data) => {
console.log('my data', data);
}
render(){
return <div>
<Child componentUnmountCallback={this.childComponentWillUnmount()}/>
<div>
}
<div>
Child.jsx
...
componentWillUnmount() {
this.props.childComponentWillUnmount(this.state);
}
...

React/Redux - How to call an action after component has rendered, based on a check - Getting error

I have, what should be a simple issue in which, when a user navigates to a specific route, the component fires an initial function call to grab a user by ID via a redux-observable in an epic. However, if the user navigates away from the page and then comes back, I need to be able to reload the page, based on a route parameter.
I have a component that utilizes an HOC to run the render() method, but it looks like a dumb component:
const ProfilePage = props => {
const { actions, user, loading } = props;
// Note: This if statement results in an error
if (user && user.id !== props.params.id) {
actions.initAction(props.params.id);
}
return (<div>Test</div>);
};
ProfilePage.propTypes = {
actions: PropTypes.object,
user: PropTypes.object,
loading: PropTypes.bool,
};
export default connect(
state => ({
user: selectUser(state),
loading: selectLoading(state),
}),
dispatch => ({ actions: bindActionCreators(Actions, dispatch) })
)(
PureRender(
onMount(props => props.actions.initAction(props.params.id))(ProfilePage)
)
);
This results in an error:
react-dom.development.js?61bb:506 Warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
^ This happens because of my if statement that checks the user id against the params id.
Does this component need to be converted into a class in order to utilize other life cycle methods that could prevent this error from happening and run my functionality accordingly?
Function component should be pure, you can think of them as the "render" method of a class component.
You can either use a class component and do side effects in componentDidMount / componentDidUpdate, or use hooks with useEffect.
hooks useEffect / class cycle methods
I am guessing the issue is with ProfilePage. Added error Cannot update during an existing state transition (such as withinrender). thrown mostly when a setState which is a async method, another setState gets called. For your case it's a functional component which you are using it in render method. So basically each time your component rerenders ProfilePage function gets called which calls the action. So you might wanna change ProfilePage this to a react component and use life cycle methods such as componentDidMount to fix your issue.
Or if you are using react 16 or above use hooks with useEffect.

`componentWillReceiveProps` explanation

I recently wanted to upgrade my knowledge of React, so I started from the component lifecycle methods. The first thing that got me curious, is this componentWillReceiveProps. So, the docs are saying that it's fired when component is receiving new (not necessarily updated) props. Inside that method we can compare them and save into the state if needed.
My question is: Why do we need that method, if changes in props of that component (inside parent render) will trigger the re-render of this child component?
One common use case are state (this.state) updates that may be necessary in response to the updated props.
Since you should not try to update the component's state via this.setState() in the render function, this needs to happen in componentWillReceiveProps.
Additionally, if some prop is used as a parameter to some fetch function you should watch this prop in componentWillReceiveProps to re-fetch data using the new parameter.
Usually componentDidMount is used as a place where you trigger a method to fetch some data. But if your container, for example, UserData is not unmounted and you change userId prop, the container needs to fetch data of a user for corresponding userId.
class UserData extends Component {
componentDidMount() {
this.props.getUser(this.props.userId);
}
componentWillReceiveProps(nextProps) {
if (this.props.userId !== nextProps.userid) {
this.props.getUser(nextProps.userId);
}
}
render() {
if (this.props.loading) {
return <div>Loading...</div>
}
return <div>{this.user.firstName}</div>
}
}
It is not a full working example. Let's imagine that getUser dispatch Redux action and Redux assign to the component user, loading and getUser props.
It 'serves' as an opportunity to react to the incoming props to set the state of your application before render. If your call setState after render you will re-render infinitely and that's why you're not allowed to do that, so you can use componentWillReceiveProps instead.
But... you are beyond CORRECT in your confusion, so correct in fact that they are deprecating it and other Will-lifecycle hooks Discussion Deprecation.
There are other ways to accomplish what you want to do without most of those Will-lifecycle methods, one way being don't call setState after render, just use the new incoming props directly in render (or wherever) to create the stateful value you need and then just use the new value directly, you can then later set state to keep a reference for the next iteration ex: this.state.someState = someValue, this will accomplish everything and not re-render the component in an infinite loop.
Use this as an opportunity to react to a prop transition before render() is called by updating the state using this.setState(). The old props can be accessed via this.props. Calling this.setState() within this function will not trigger an additional render.
Look at this article
the componentWillReceiveProps will always receive as param "NxtProps", componentWillReceiveProps is called after render().
some people use this method use this to compare nxtProps and this.props to check, if something should happen before the component call render, and to some validations.
check the react's documentation to know more about react lifecycle!
hope this could help you!
changes in props of that component (inside parent render) will trigger the re-render of this child component
You are absolutely right. You only need to use this method if you need to react to those changes. For instance, you might have a piece of state in a child component that is calculated using multiple props.
Small Example:
class Test extends Component {
state = {
modified: "blank"
};
componentDidMount(){
this.setState({
modified: `${this.props.myProp} isModified`
});
}
componentWillReceiveProps(nextProps) {
this.setState({
modified: `${nextProps.myProp} isModified`
});
}
render() {
return <div className="displayed">{this.state.modified}</div>
}
}
In this example, componentDidMount sets the state using this.props. When this component receives new props, without componentWillReceiveProps, this.state.modified would never be updated again.
Of course, you could just do {this.props.myProp + "IsModified"} in the render method, but componentWillReceiveProps is useful when you need to update this.state on prop changes.

Redux mapStateToProps called multiple times

I have this very simple Component, which is connected to redux state and returns {fruit, vegetables}. Everything works fine, but let's say I have a graph inside the Component and I if receive only updated vegetable from API the graph is being recreated each time.
Here's my component:
const Products = ({ fruit, vegetable }) =>
<div className='Products'>
<div>{vegetable.map(....logic here)}</div>
<div>{Math.random()}</div> // this is to illustrate the component is rendering every time
<Graph>Here will be a chart or a graph with fruit</Graph> //but it gets re-rendered even though there is no new fruit
</div>
const mapStateToProps = (state) => {
return {
fruit: state.data.fruit,
vegetable: state.data.vegetable,
}
}
export default connect(mapStateToProps)(Products)
It seems to me that every-time, no matter which states is updated it re-renders the whole components.
Is there a way to prevent that?
When a React component gets rendered, the whole tree of components below it also gets rendered - at the exception of the components which shouldComponentUpdate hook returns false. So in your case, if the Products component gets rendered, it is normal that the Graph component also does.
You have two options here:
if your Products component does not use the fruit prop outside of the Graph component, you can connect directly your Graph component to the fruitstate, and use the pure option of the connect function to avoid re-renders when fruit does not change
you can define the shouldComponentUpdate hook in your Graph component to manually skip unnecessary renders, or use a helper library to do it for you, for example the pure helper of the recompose library
The first option is where optimizing react/redux apps / avoiding unnecessary renders generally starts: connect your components to the store at the lowest level where it makes sense. The second option is more of an escape hatch - but still often useful.
As you mention you use stateless components, you can use a higher-order component to benefit from the shouldComponentUpdate hook. To understand how this works, here's how a simple implementation of it could look like this:
function pure(BaseComponent, shouldUpdateFn) {
return class extends Component {
shouldComponentUpdate(nextProps) {
return shouldUpdateFn(this.props, nextProps);
}
render() {
return <BaseComponent { ...this.props } />;
}
}
}
This would give you a pure HOC that you could reuse over your app to avoid unnecessary renders: it works by wrapping your stateless component into a new component with the desired hook. You'd use it like so, for example:
export default pure(Graph, (props, nextProps) => props.fruit !== nextProps.fruit)
Still, i highly encourage you in having a look at recompose, which has more fine-grained implementations of this, and would avoid you to reinvent the wheel.
To prevent a component to rerender when receiving new props, you can implement shouldcomponentupdate() in Graph.
Use shouldComponentUpdate() to let React know if a component's output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.
shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true. This method is not called for the initial render or when forceUpdate() is used.
Returning false does not prevent child components from re-rendering when their state changes.
Currently, if shouldComponentUpdate() returns false, then componentWillUpdate(), render(), and componentDidUpdate() will not be invoked. Note that in the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component.
If you determine a specific component is slow after profiling, you may change it to inherit from React.PureComponent which implements shouldComponentUpdate() with a shallow prop and state comparison. If you are confident you want to write it by hand, you may compare this.props with nextProps and this.state with nextState and return false to tell React the update can be skipped.
To help you implementing shouldComponentUpdate(), you can use eitherReact shallow-compare() or a custom shallow compare function
Given your current code.
React will update the whole component when state is changed.
So Graph Component will get updated.
If you don't want Graph Component to get updated you can add shouldComponentUpdate in your Graph Component and introduce checks there for re-rendering like as follows
shouldComponentUpdate: function(nextProps, nextState) {
// You can access `this.props` and `this.state` here
// and check them against nextProps and nextState respectively.
// return boolean(false) if you don't want the component to re-render.
}

Prevent react component from rendering twice when using redux with componentWillMount

I have a React component that dispatches a redux state change in its componentWillMount function. The reason is that when the component is loaded, it needs to get the id from the url (powered by react-router), and trigger an action that sets up the state with that id's data.
Here is the component:
class Editor extends React.Component {
componentWillMount() {
const { dispatch, params } = this.props
dispatch(editItem(params.id))
}
render() {
const item = this.props.item
console.log("Editing", item)
}
}
export default connect(state => ({item: state.item}))(Editor)
Here's the catch: render is getting called twice. item is undefined on the first call, and valid on the second. Ideally, it should only be called once this.props.item actually exists (after the editItem action has been dispatched and run).
According to the React docs: "If you call setState within this method, render() will see the updated state and will be executed only once despite the state change."
In redux, dispatch is the equivalent of calling setState, as it results in a state change. However, I'm guessing something in the way connect works is still causing render to be called twice.
Is there a way around this besides adding a line like if (!item) return; ?
One thing you might do is create a higher order component that handles the basic pattern of loading a different component (or no component) before the required props are loaded.
export const LoaderWrapper = function(hasLoaded, Component, LoaderComponent, onLoad) {
return props => {
if (hasLoaded(props)) {
return <Component {...props} />
}
else {
if (onLoad) onLoad(props)
return { LoaderComponent ? <LoaderComponent /> : null }
}
}
}
Then you can wrap your component before connecting it to get the desired behaviour.
export default connect(state => ({item: state.item}))(LoaderWrapper(
((props) => !!props.item),
Editor,
null,
(props) => props.dispatch(editItem(props.params.id))
))
You might want to add some currying magic to make sure you can compose these kinds of wrapper functions more nicely. Take a look at recompose for more info.
It looks like there's already an issue in the react-redux library.
https://github.com/rackt/react-redux/issues/210
What does editItem do? Does it add item to the redux state or is it there already?
If it is adding I imagine what is happening is that a render cycle happens with the current props, ie item being blank.
Then it gets rendered again when the props have changed, via setting the item.
One approach to fixing this sort of thing is to create a higher order component that wraps Editor and calls the dispatch action the rendering though is set either to a loading screen or and empty div until item is set. That way you can be assured that Editor will have an item.
But without knowing what editItem does it's sort of hard to know. Maybe you could paste the code for that?

Resources