State change not re-rendering component - reactjs

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

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.

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

Here is what happens:
You go to /p/user22.
React renders <Route exact path="/p/:uid" component={Profile} />.
The Route renders the Profile component for the first time.
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".
You click on the link to /p/user1.
React renders <Route exact path="/p/:uid" component={Profile} />, which is the same Route.
The Route then rerenders the same Profile component but with different props.
Since, its the same Profile component, it does not update the state.
This explains why you still see the profile of user22

Related

react router, how to make page refresh on url change?

I am making a movie app, where the each movie has its own dedicated page. On this page I have provided similar movies that can be clicked on and ideally would take you to its own movie page. I have a route that looks like this
<Route name="movie" path="/movie/:movieID" exact ><MoviePage /></Route>
the link to this route is in each and every movie component that I have created. The issue is that when I click on a similar movie the url changes to the url of the similar movie and I am able to access the similar movie's properties in the developer console, however the page itself does not change. But once refreshed the page's contents change to those that correspond to the url.
I cannot find a way to force refresh the page in order to get the new movie's information to be displayed.
The issue is likely that the MoviePage component needs to react to the movieID route param updating.
Given Route:
<Route name="movie" path="/movie/:movieID" exact>
<MoviePage />
</Route>
MoviePage:
Use an useEffect hook to handle any logic/data fetching/etc based on the movieID param updating.
const { movieID } = useParams();
React.useEffect(() => {
// movieID on initial render or subsequent render when updated
// logic to use movieID and resynchronize any data.
}, [movieID]);
If still using class components, then use the componentDidUpdate lifecycle method. (Assumes MoviePage wrapped in withRouter HOC to receive route props, specifically the match prop)
componentDidUpdate(prevProps, prevState) {
if (prevProps.match.params.movieID !== this.props.match.params.movieID) {
// logic to use movieID and resynchronize any data.
}
}

Update Redux State based on Reach Router Component

I want to update the state of my application when a router component is rendered. For example, if I go home "/" I will render homepage, but I want to trigger a state change so that other components like my footer and header are aware we are on the home page vs internal page. Currently, I am parsing the url manually when site is loaded to detect this, however, as the site grows this is unfeasible. I have also seen onEnter as a solution, however, this is not available for reach router.
<Router basepath={process.env.PUBLIC_URL} className="app" >
<LandingPageBody path="/" default/>
//update redux state to show we are on homepage
</Router>
You can use componentDidMount in LandingPageBody component. It is a react lifecycle method it will be triggered when component will mount. You can update state from inside of this method.
If you are using functional components, make use of useEffect function with dependency on props.match.location. Check for condition in that and dispatch action if the condition is true.
(React.useEffect(() => { if(props.match.location === 'desired path') dispatch(action) }, [props.match.location]))
This way when ever your route component loads, it will check for route on every route change and let your redux store know about the current path.
For Stateful component, you can do the same in componentDidUpdate lifecycle.

Passing data from the Redux store, as Props, to an unknown number of routes

Here is the situation:
I am getting an unknown amount of data from a service and putting it in my Redux store
Each data-point will have its own route /:rid
The route is setup like this <Route path=':rid' component={Restaurant}/> using react-router (v3) -- see full render method below
I'd like to be able to pass the correspond data about the specific restaurant, the one whose route was navigated to, to the Restaurant component as props so it can render the component with the correct information for that restaurant
Right now my solution is to pass all the restaurants from the store into the Restaurant component as props this.props.restaurants. Then in componentWillReceiveProps I loop over all the restaurants in this.props.restaurants and check to see if the rid of each restaurant is equal to the this.props.routeParams ie :rid. If it is I then set that state to contain the data I want to show and reference this.state.name as opposed to the data being on `this.props.name'
Is there another way to do this? Right now it's not a performance issue but I can imagine looping over and arbitrarily large data set could lead to so serious load times. Also, it just seems like there should be a way for react-router to pass in this data as props so I can keep this component stateless.
Ideally, something like this would happen:
a request is made to /1234
react-router in my index.js consults/queries the redux store and finds the data for the restaurant with rid 1234 and passes it as props to the component it renders
I imagine it looking something like this <Route path=':rid' component={<Restaurant {...matchedRestaurant} />}/>
Perhaps this questioning can be asked in short like, how do I make a unknown number of routes such that when one is navigated to it is rendered with the data for that corresponding restaurant as props?
Restaurant.js:
componentWillReceiveProps(nextProps) {
this.props.restaurants.forEach((restaurant) => {
if(restaurant.rid == nextProps.routeParams.rid) this.setState({name: restaurant.name})
})
}
index.js:
render(
(
<Provider store={store}>
<Router history={hashHistory}>
<Route path='/' component={App}>
<IndexRoute component={RestaurantList} />
<Route path=':rid' component={Restaurant}/>
</Route>
</Router>
</Provider>
),
document.getElementById('root')
)
https://github.com/caseysiebel/corner-team/blob/master/src/index.js
Instead of having react-router figure this out for you, you should be using selectors (and potentially a package like reselect). Reselect even has a section on how to base your selector on props (in this case like the routerParams.rid): https://github.com/reactjs/reselect#accessing-react-props-in-selectors
For the non-Reselect solution, you could simply change the connect in your Restaurant component like so:
#connect((state, props) => {
return {
restaurants: state.restaurant.restaurants.find((restaurant) => {
return restaurant.rid == props.routeParams.rid
}),
}
})
As #Sean Kwon commented, you should also normalize your data which would make this selector trivial:
#connect((state, props) => {
return {
restaurants: state.restaurant.restaurants[props.routeParams.rid],
}
})
Assuming you have connected your action via mapDispatchToProps, you organize your store and async actions so that this can be possible.
componentDidMount() {
this.props.fetchRestaurant(this.props.params.rid)
}
The store will then update your component with the corresponding restaurant data. This way, you're calling some kind of action to get the corresponding data whilst reducing the need to use the component state, which you should try to avoid in order to keep your state centralized.
Otherwise, for a quick and dirty solution, you can just do this really quickly.
componentWillReceiveProps(nextProps) {
var name = this.props.restaurant.find(res => res.rid === nextProps.routeParams.rid)
this.setState({name: name})
}

Infinite componentDidUpdate() calls with react-router

I am new to react-router and right now I have following routes in my app:
<Router history={browserHistory}>
<Route path="/" component={MainLayout}>
<Route path="/v/:username/:reponame/:page/:perPage" component={Results} />
</Route>
</Router>
As you can see, there's a MainLayout component that includes an <input type="text"> which is used to connect to Github API and retrieve list of issues for a certain repo.
Then the Results component steps in. Here's the code for it's componentDidMount():
componentDidMount() {
const {
username,
reponame,
page,
perPage
} = this.props.params;
this.sendRequest(username, reponame, page, perPage);
}
sendRequests essentially contains the ajax query for fetching the output data, after which it's being set into the component's state:
this.state = {
data: [], // here
lastPage: -1,
errorMessage: ''
}
Now this works pretty well till the very moment when one wants to change the value of an input.
As I see, the Result component doesn't unmount. Instead, it invokes componentWillReceiveProps() and updates the existing component. AFAIK it is safe to perform side calls in componentDidUpdate() so I just copied the code above from componentDidMount() and pasted it in there. This way, though (and it is absolutely reasonable) componentDidMount() is being invoked over and over again.
The only workaround I came up with at the moment is comparing old and new data in the sendRequest() itself and invoke setState() inside of it only if it differs via deep-equal package, so it looks like this:
if (!equal(res, this.state.data)) {
this.setState({
...this.state,
data: res,
lastPage
});
}
Is this considered to be an ok pattern or there is a better way to solve this issue?
You should not use setState inside the cDM lifecycle. as it might trigger re-render, which will cause your infinite loop.
Updating the state after a component mount will trigger a second render() call and can lead to property/layout thrashing.
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md

Where and How to request data asynchronously to be passed down as props with React Router (v 1)

After reading many questions regarding this topic I am still unsure as to which is the best way to asynchronously fetch data which later will be passed down as props to the child routes with React Router v1.0.0 and up.
My route config looks something like this:
import { render } from 'react-dom';
// more imports ...
...
render(
<Router>
<Route path="/" component={App} />
<IndexRoute component={Dashboard}/>
<Route path="userpanel" component={UserPanel}/>
</Router>,
document.getElementById('container')
)
In my App component I have code which asynchronously fetches data from the backend and will incorporate it into its state, if fetching was successful. I use componentDidMount for this within App.
The state of App will look like this contrived example:
{
user: {
name: 'Mike Smith',
email: 'mike#smith.com'
}
}
I would want to pass the user part of state as props to my IndexRoute and the userpanel route. However I am not sure how I should do this.
A few questions come to mind:
Should I place the async data request somewhere else within my code?
Should I use the React Router api (like onEnter) instead of React lifecycle methods for the data fetching?
How can I pass the state (user) of App to the Dashboard and UserPanel components as props?
I am unsure how to do this with React.cloneElement as seen in other answers.
Thanks for the help in advance.
What you are asking for is persistent data between routes and that's not the job of the router.
You should create a store (in flux terms), or a model/collection (in MVC terms) - the usual approach with react is something flux-like. I recommend redux.
In the redux docs it has an example of fetching a reddit user:
componentDidMount() {
const { dispatch, selectedReddit } = this.props
dispatch(fetchPostsIfNeeded(selectedReddit))
}
Personally I don't think flux/redux is the easiest approach to implement, but it scales well. The essential concept is even if you decide to use something else:
You are correct, as Facebook suggests, async fetching goes best in componentDidMount.
If you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests, perform those operations in this method.
Next you need to set this data in a store/model which can be accessed from other components.
The nice thing about redux (with react-redux) is that for each component you can say "Here are the actions this component is interested in" and then that component can simply call the action like UserActions.fetchUserIfNeeded() and the action will figure out whether it already has the user or if it should be fetched, and afterwards it will re-render and the prop will be available.
Answer to Q4: What are you trying to clone and why? If it's a child see this answer.
You can do one thing when your application start at that time you will call the API and fetch the data and register your Route like
my index.js is entry file then
here I have used React-Router 0.13.3 you can change the syntax as per new Router
fetchData(config.url+'/Tasks.json?TenantId='+config.TenantId).then(function(items)
{
var TaskData=JSON.parse(JSON.stringify(items.json.Tasks));
var Data=[];
Object.keys(TaskData).map(function(task){
if(TaskData[task].PageName !=='' && TaskData[task].PageUrl !=='')
{
Data.push({PageName:TaskData[task].PageName,path:TaskData[task].PageName+'/?:RelationId',PageUrl:TaskData[task].PageUrl});
}
});
Data.push({PageName:'ContainerPage',path:'/ContainerPage/?:RelationId',PageUrl:'./pages/ContainerPage'});
var routes=require('./routes')(Data);
$("#root").empty();
Router.run(routes,function(Handler){
React.render(<Handler />,document.getElementById('root'));
});
React.render(<UserRoles />, document.getElementById("userrole"));
}).catch(function(response)
{
showError(response);
});
I have pass the data to routes.js file like var routes=require('./routes')(Data); and my routes.js file look like
export default (data =>
<Route name="App" path="/" handler={App}>
<NotFoundRoute handler={require('./pages/PageNotFound')} />
<Route handler={TaskList} data={data} >
</Route>
{ data.map(task =>
<Route name={task.PageName} path={task.path} handler={require(task.PageUrl)}>
</Route>
) }
</Route>
);
I am not entirely sure I understand the question, but I just recently passed properties to the children of my routes as well. Pardon me if this is not the best way of doing it, but you'll have to clone your children and edit them and then pass down the copies not the children. I'm not sure why react and the react router make you do this, but try this:
let children (or whatever you want to name it) = React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {name of property: property value});
});
Afterwards, you should be able to access those properties in this.props in the sub routes. Please ask if you have any questions because this is pretty confusing.

Resources