Vladimir Marton
Vladimir Marton

Reputation: 576

State change not re-rendering component

I couldn't find the answer anywhere because nothing was working for me, so I'm starting a new topic. Please, don't mark it as a duplicate


Router.js:

<Switch>
   <Route path="/" exact component={Home} />
   <Route exact path="/p/:uid" component={Profile} />
</Switch>

Profile.js

constructor(props) {
  super(props);
  this.state = {
    loading: true,
    profile_user_id: this.props.match.params.uid,
  };
}

Then, later in Profile.js, I trigger a fetch to get data from backend and save this data to state using this.setState({ ... }). When the Profile component is rendered, everything looks fine.

In Router.js, there is also a Link:

<Link to={"/p/" + this.state.ntuser.tag}>Profile</Link>

.. which should go to your own profile. So when your user ID is 1, and you visit the profile of the user with id 22, your URL will be /p/user22 and the link will point to /p/user1.

The problem is that even though profiles are rendered nicely, Profile component does not become re-rendered when you click the link (which should direct you to /p/user1 and show your profile instead). I tried to save location from react-router to state as well, so every time URL changes it will be caught in componentWillReceiveProps() and inside I update state. But still nothing. Any ideas?

PS: I'm using React.Component

Upvotes: 0

Views: 1184

Answers (3)

xadm
xadm

Reputation: 8418

console.log(this.props.match.params.uid) in constructor, componentDidMount() and componentDidUpdate() (UNSAFE_componentWillReceiveProps() is deprecated)

Number and places (of log calls) will tell you if component is recreated (many construcor calls) or updated (cDM calls). Move your data fetching call accordingly (into cDM or cDU ... or sCU).

You can save common data in component above <Router/> (f.e. using context api) - but this shouldn't be required in this case.


Solution

You can update state from changed props using componentDidUpdate() or shouldComponentUpdate(). componentDidUpdate should be protected with conditions to prevent infinite loop. See docs.

Simplest solution:

componentDidUpdate(prevProps) {
    if (this.props.some_var !== prevProps.some_var) {
        // prop (f.e. route '.props.match.params.uid') changed
        // setState() - f.e. for 'loading' conditional rendering
        // call api - use setState to save fetched data
        // and clearing 'loading' flag 
    }
}

shouldComponentUpdate() can be used for some optimalizations - minimize rerenderings.

Upvotes: 1

Errorname
Errorname

Reputation: 2459

Here is what happens:

  1. You go to /p/user22.
  2. React renders <Route exact path="/p/:uid" component={Profile} />.
  3. The Route renders the Profile component for the first time.
  4. The Profile component calls the constructor, at the time, this.props.match.params.uid is equal to "user22". Therefore, this.state.profile_user_id is now "user22".
  5. You click on the link to /p/user1.
  6. React renders <Route exact path="/p/:uid" component={Profile} />, which is the same Route.
  7. The Route then rerenders the same Profile component but with different props.
  8. Since, its the same Profile component, it does not update the state.

This explains why you still see the profile of user22

Upvotes: 0

Just_lbee
Just_lbee

Reputation: 166

Had same problem in my project, Didn't find any good workable idea exept making somethink like this:

import {Profile} from "../Profile "

<Link to={`your href`}
            onClick={userChanged}/>

..........

function userChanged() {
  const userCard= new Profile();
  userCard.getProfileData();
}

First of all you need to make your Profile component as export class Profile extends React.Component (export without default).

getProfileData is method where i get data from my api and put it state. This will rerender your app

Upvotes: 0

Related Questions