Change in the optional parameter is not reloading the component - reactjs

Below is the route :
<Route exact path="/products/version/:id/:version" component={component} />
I am trying to redirect from : http://localhost:8080/products/version/600178221/3
to
http://localhost:8080/products/version/600178221/5
using :
this.props.history.push('/products/version/600178221/5');
Issue : URL is updating, but component is not re-loading.

React won't re-render your component because only the props changed but the component that has to be rendered is still the same. React only updates what is necessary to update.
If you get your product data from a remote backend (using ajax) I recommend you use componentDidUpdate. This is also recommended in the official documentation as an alternative for getDerivedStateFromProps for side effects:
If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
React documentation
componendDidUpdate(prevProps) {
const { match } = this.props;
if (prevProps.match.params.id !== match.params.id || prevProps.match.params.version !== match.params.version) {
this.fetchDataFromApi();
}
}

Along with componentDidMount, You also need to implement the componentWillReceiveProps or use getDerivedStateFromProps(from v16.3.0 onwards) in Products page since the same component is re-rendered with updated params and not re-mounted when you change the route params, this is because params are passed as props to the component and on props change, React components re-render and not re-mounted.
EDIT: from v16.3.0 use getDerivedStateFromProps to set/update state based on props(no need to specify it in two different lifecyle methods)
```static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.match.params.product !== prevState.currentProductId){
const currentProductId = nextProps.match.params.product
const result = productlist.products.filter(obj => {
return obj.id === currentProductId;
})
return {
product: result[0],
currentId: currentProductId,
result
}
}
return null;
}```

Related

How to use react context api with getDerivedStateFromProps?

Context provides a way to pass data through the component tree without having to pass props down manually at every level. This is great!
but I'm wondering how to use it with getDerivedFromProps()
For example, if I have a prop sent via Context in top level of the app, that said it's window.location.href, and I need to take action in the child component based on the href, e.g. fetch the data.
Using getDerivedStateFromProps(), I have to write something like the following:
getDerivedStateFromProps(nextProps, state) {
var stateRev = null
var pathname = hrefToPath(nextProps.href)
if (pathname != state.pathname) {
stateRev = {}
Object.assign(stateRev, {
pathname,
book: source.find()
})
}
return stateRev
}
However, if I write the code like the above, I have to send the window.location.href through the levels. What I need to know is if the prop in the context changed, I need to update the state.
I see no way to know the prop in the context changed or not. Is there anything I need to know about the context api and getDerivedStateFromProps?
Thank you.
If you want to consume context in lifecycle methods you can use contextType. The problem with this approach is that getDerivedStateFromProps is static and cannot access instance variables.
So solution I see is to wrap your component in High Order Component, like this
const WithContext = (Component) => {
return (props) => (
<CustomContext.Consumer>
{value => <Component {...props} value={value} />}
</CustomContext.Consumer>
)
}
In this case you'll get context as part of props
getDerivedFromProps is not for that
DOCS - tips for getDerivedFromProps: 'If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.'
Also 'This method doesn’t have access to the component instance.' - then no this.context available.
If you need to react on context prop change - use Context.Consumer. Use componentDidUpdate to compare props (consumer provides context value as prop) and conditionally fetch data.

Force remounting component when React router params changing?

I've written a simple app where the remote resources are fetched inside componentDidMount functions of the components.
I'm using React Router and when the route changes completely, the previous component is unmounted well then the new one is mounted.
The issue is when the user is staying on the same route, but only some params are changed. In that case, the component is only updated. This is the default behaviour.
But it's sometimes difficult to handle the update in all the children components where previously only componentDidMount was needed...
Is there a way to force the remounting of the component when the user is staying on the same route but some params are changing?
Thanks.
Do the following
the route shoul look like this.
<Route path='/movie/:movieId' component={Movie} />
When you go to /movie/:movieID
class Movie extends Component {
loadAllData = (movieID) => {
//if you load data
this.props.getMovieInfo(movieID);
this.props.getMovie_YOUTUBE(movieID);
this.props.getMovie_SIMILAR(movieID);
}
componentDidMount() {
this.loadAllData(this.props.match.params.movieId);
}
componentWillReceiveProps(nextProps){
if(nextProps.match.params.movieId !== this.props.match.params.movieId) {
console.log(nextProps);
this.loadAllData(nextProps.match.params.movieId);
}
}
render(){
return( ... stuff and <Link to={`/movie/${id}`} key={index}>...</Link>)
}
}

Set state in React component from props and update when props change?

My React component sets a state property called userInGroup based on props.
this.state = {
userInGroup: this.props.user
? this.props.user.groups.includes(props.group._id)
: false,
};
This works but does not update when the props change and the value of userInGroup should also change, until I refresh the page. How can I make this update reactively?
Maybe I could use componentWillUpdate or componentDidUpdate but then Id be repeating the logic used by userInGroup. Is this repetition inevitable?
You need to use getDerivedStateFromProps. The other methods are now deprecated and deemed unsafe.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
This method exists for rare use cases where the state depends on changes in props over time. For example, it might be handy for implementing a component that compares its previous and next children to decide which of them to animate in and out.
static getDerivedStateFromProps(props) {
return {
userInGroup: props.user
? props.user.groups.includes(props.group._id)
: false,
}
}
Yes you need to make use of componentWillReceiveProps along with constructor/componentDidMount, when you want to update state when props change since constructor/componentDidMount are called only once when the component mounts, and componentWillReceiveProps() is invoked before a mounted component receives new props or the Parent component updates
You could write a function that contains the logic
this.state = {
userInGroup: this.getUserStatus();
};
componentWillReceiveProps(nextProps) {
if(nextProps.user !== this.props.user) {
this.setState({userInGroup: this.getUserStatus(nextProps)})
}
}
getUserStatus = (newProps) => {
const data = newProps || this.props
return data.user
? data.user.groups.includes(data.group._id)
: false
}

Is updating ancestor components during componentWillReceiveProps explicitly supported?

I know React supports calling setState during componentWillReceiveProps:
componentWillReceiveProps() is invoked before a mounted component receives new props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
But the documentation doesn't say anything about actions that trigger updates to ancestor components like changing the router location or dispatching redux actions.
That's what I'm doing in one of my views, and it's causing bugs:
class MyView extends React.Component<void, Props, void> {
setMissingQuery = (props: Props = this.props) => {
const {date, location: {query: {view}}} = props
if (!view || !Number.isFinite(date.getTime())) {
const {router} = this.context
const {location: {pathname, query}} = this.props
const newQuery = {...query}
if (!view) newQuery.view = 'month'
if (!Number.isFinite(date.getTime())) newQuery.date = moment().format(DATE_FORMAT)
router.replace({
pathname,
query: newQuery,
})
}
}
componentWillMount() {
this.setMissingQuery()
}
componentWillReceiveProps(nextProps: Props) {
this.setMissingQuery(nextProps)
if (nextProps.selectedShift && nextProps.selectedShift !== this.props.selectedShift) {
const {selectedShift, dispatch} = nextProps
dispatch(setEditingShiftProps(selectedShift))
}
}
...
}
But if it happens to call both router.replace and dispatch(setEditingShiftProps(selectedShift)) in a single call to componentWillReceiveProps, my React UI stops responding and I see the following error in the console:
Uncaught Error: findComponentRoot(..., .0.0.2.0.0.$1/=11=2$0.$0.0.0.0.1.1.1.1:$0.0.0.$header_0.0): Unable to find element. This probably means the DOM was unexpectedly mutated (e.g., by the browser), usually due to forgetting a <tbody> when using tables, nesting tags like <form>, <p>, or <a>, or using non-SVG elements in an <svg> parent. Try inspecting the child nodes of the element with React ID ``.
I have also seen this same error when I mistakenly dispatched actions during a componentWillUpdate, but I have switched to using componentDidUpdate. The error happens because I am rendering a react-big-calendar component, and somehow the control flow causes React to call its componentWillReceiveProps to be called twice before calling its componentDidUpdate.
If I wrap the router.replace and dispatch calls in a setTimeout, the error goes away.

Re-render React component when prop changes

I'm trying to separate a presentational component from a container component. I have a SitesTable and a SitesTableContainer. The container is responsible for triggering redux actions to fetch the appropriate sites based on the current user.
The problem is the current user is fetched asynchronously, after the container component gets rendered initially. This means that the container component doesn't know that it needs to re-execute the code in its componentDidMount function which would update the data to send to the SitesTable. I think I need to re-render the container component when one of its props(user) changes. How do I do this correctly?
class SitesTableContainer extends React.Component {
static get propTypes() {
return {
sites: React.PropTypes.object,
user: React.PropTypes.object,
isManager: React.PropTypes.boolean
}
}
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
render() {
return <SitesTable sites={this.props.sites}/>
}
}
function mapStateToProps(state) {
const user = userUtils.getCurrentUser(state)
return {
sites: state.get('sites'),
user,
isManager: userUtils.isManager(user)
}
}
export default connect(mapStateToProps)(SitesTableContainer);
You have to add a condition in your componentDidUpdate method.
The example is using fast-deep-equal to compare the objects.
import equal from 'fast-deep-equal'
...
constructor(){
this.updateUser = this.updateUser.bind(this);
}
componentDidMount() {
this.updateUser();
}
componentDidUpdate(prevProps) {
if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID (this.props.user.id !== prevProps.user.id)
{
this.updateUser();
}
}
updateUser() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Using Hooks (React 16.8.0+)
import React, { useEffect } from 'react';
const SitesTableContainer = ({
user,
isManager,
dispatch,
sites,
}) => {
useEffect(() => {
if(isManager) {
dispatch(actions.fetchAllSites())
} else {
const currentUserId = user.get('id')
dispatch(actions.fetchUsersSites(currentUserId))
}
}, [user]);
return (
return <SitesTable sites={sites}/>
)
}
If the prop you are comparing is an object or an array, you should use useDeepCompareEffect instead of useEffect.
componentWillReceiveProps() is going to be deprecated in the future due to bugs and inconsistencies. An alternative solution for re-rendering a component on props change is to use componentDidUpdate() and shouldComponentUpdate().
componentDidUpdate() is called whenever the component updates AND if shouldComponentUpdate() returns true (If shouldComponentUpdate() is not defined it returns true by default).
shouldComponentUpdate(nextProps){
return nextProps.changedProp !== this.state.changedProp;
}
componentDidUpdate(props){
// Desired operations: ex setting state
}
This same behavior can be accomplished using only the componentDidUpdate() method by including the conditional statement inside of it.
componentDidUpdate(prevProps){
if(prevProps.changedProp !== this.props.changedProp){
this.setState({
changedProp: this.props.changedProp
});
}
}
If one attempts to set the state without a conditional or without defining shouldComponentUpdate() the component will infinitely re-render
You could use KEY unique key (combination of the data) that changes with props, and that component will be rerendered with updated props.
componentWillReceiveProps(nextProps) { // your code here}
I think that is the event you need. componentWillReceiveProps triggers whenever your component receive something through props. From there you can have your checking then do whatever you want to do.
I would recommend having a look at this answer of mine, and see if it is relevant to what you are doing. If I understand your real problem, it's that your just not using your async action correctly and updating the redux "store", which will automatically update your component with it's new props.
This section of your code:
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Should not be triggering in a component, it should be handled after executing your first request.
Have a look at this example from redux-thunk:
function makeASandwichWithSecretSauce(forPerson) {
// Invert control!
// Return a function that accepts `dispatch` so we can dispatch later.
// Thunk middleware knows how to turn thunk async actions into actions.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
You don't necessarily have to use redux-thunk, but it will help you reason about scenarios like this and write code to match.
A friendly method to use is the following, once prop updates it will automatically rerender component:
render {
let textWhenComponentUpdate = this.props.text
return (
<View>
<Text>{textWhenComponentUpdate}</Text>
</View>
)
}
You could use the getDerivedStateFromProps() lifecyle method in the component that you want to be re-rendered, to set it's state based on an incoming change to the props passed to the component. Updating the state will cause a re-render. It works like this:
static getDerivedStateFromProps(nextProps, prevState) {
return { myStateProperty: nextProps.myProp};
}
This will set the value for myStateProperty in the component state to the value of myProp, and the component will re-render.
Make sure you understand potential implications of using this approach. In particular, you need to avoid overwriting the state of your component unintentionally because the props were updated in the parent component unexpectedly. You can perform checking logic if required by comparing the existing state (represented by prevState), to any incoming props value(s).
Only use an updated prop to update the state in cases where the value from props is the source of truth for the state value. If that's the case, there may also be a simpler way to achieve what you need. See - You Probably Don't Need Derived State – React Blog.

Resources