React router - calling same method outside componentDidMount - reactjs

I have a React application where pages are linked using React router. The user is provided with several links. Each link is handled through the router.
All of the corresponding pages have similar logic before render function, so I used a URL parameter, a single Route path, and the same target component. The URL parameter is supposed to be passed to the backend service.
Since the target component is the same and only distinguishing factor is the URL parameter, once the component is rendered for any of the links, the lifecycle methods like componentWillMount, componentDidMount do not execute again. So, even if I click on another link whatever is the state created by the first hit, same is used for other links. REST call is within componentDidMount. Am I missing something?
<Route path="/location/:locationType" component={MapSelectedLocation}/>
MapSelectedLocation is supposed to handle several locationType and invoke REST service based on that.
The expected result is to execute the same code for different locationType. How can I achieve this?

You need to use componentDidUpdate lifecycle method to do the calculation or each props/state change. Put the check in this method and compare the prevProps and new props value.
Also this method will not get called on initial rendering
Like this:
componentDidMount() {
this.doCalculation();
}
componentDidUpdate(prevProps) {
if(this.props.match.params.locationType != prevProps.match.params.locationType) {
this.doCalculation()
}
}
doCalculation() {
// do the calculation here
}

Related

Correct way to fetch data with server side rendering (Next.js, when to use componentDidMount and when to use componentWillMount)

I am using Next.js, in the _app.js page (where we set a general webapp state) I have 2 types of data that I need to have in the header (so for every component).
1) The first type is the header info, this info should be rendered before the app loads. I will use get getInitialProps to call the endpoints and put them into the props and then to componentWillMount to add them to the state.
2) The second type is the data for the Search component, it as a lot of data and I don't particularly mind loading it while the app is already rendered as It is not displayed in the first user visual.
So I am gussing that here it is much better prefered to use componentDidMount and asynchorslly call the endpoint that fetches the serach object data and than adding setting the state with it.
The purpose of this questionis double:
1) Review - Am I thinking about this correctly or did I miss something?
2) Question - now, when the data is loaded after first render, I am passing the data like so: _app.js -> Layout -> Menu -> SearchBar
so my question is, in my searchBar I need to do something like
componentDidMount() {
this.setState({ options: this.props.searchBarSource })
}
But, because _app.js is filling this data with an async call, wouldn't this mean that I will always get an empty object?
WWhat is the correct way to solve this? If I setTimeOut to around 3 seconds and then set the data is it a normal solution or very hacky and there are better ways?
What your are trying to do makes sense. Coming to NextJS, you need to be very deliberate about which data should load server side and which shouldn't.
Here are some pointers:
There is no need to setState in the search component, you can
use this.props.searchBarSource as Kunukn mentioned.
[Note - If you are combining getInitialProps and client side (shallow routing), when the user navigates to a new client side route, the initialProps will be undefined. A workaround might be to store initialProps in state or Local Storage.]
The point of getInitialProps is to load async data before the
component mounts. You will always receive the resolved Promise on
componentDidMount, so your this.props.searchBarSource will never
be empty (or more correctly, pending).

Component Not Re-rendering on clicking of a Link

I am using Link property of react to goto the same url, But with the diffrent param. But my api is not calling again as i put the api call part in the componentDidMount().On Click of a url my url param change. Below is the code.
componentDidMount(){
let profile_id = window.localStorage.getItem('profile_id');
if(profile_id && profile_id!==''){
this.props.fetchSingleProfile(profile_id)
}
}
First time, api call and data rendered successfully. Below is how i am using Link.
<Link to={this.userId}>Next</Link>
Now on click of link,I am successfully routed to the give url, But as i mentioned "Component is rendering again on successfully routing, But api is not calling again". Is there any thing in react, which is aware of it, my url is changed, Now call the api again.
Note: Can i use any lifecycle method like: componentWillReceiveProps, But i don't want to call the api again.
But my api is not calling again as i put the api call part in the componentDidMount().
You have same route, so the same <Route> is rendered. All nested components stay the same so instead of remounting they are just updated.
You have few possible ways to handle that:
Fetch data not only on componentDidMount but also on componentDidUpdate
Use key to remount component
It cannot be done with just react-router's <Link /> since it is not actually its responsibility

When (where) to read React router params for data retrieval into props?

I have React route:
<Route path='/contract/:id' component={ContractForm}/>
and I would like to read the value of this id param in my ContractForm component for retrieving data from database (of course using REST API and redux-saga) according to the received id value.
I am following React Router Pass Param to Component and it provides solution:
<Route exact path="/details/:id" render={(props)=>{
<DetailsPage id={props.match.params.id}/>
}} />
But I need to process id value some time before rendering. Where (from what event) should I read id value? I am thinking about several events but none of them is satisfying:
I can not read id in ContractForm constructor() or componentDidMount(), because those events are fired only once, if I am navigating from the current contract entity directly to the next contract entity then my ContractForm is not recreated of remounted, so events don't happen
I can not read id in ContractForm componentWillUpdate(nextProps, nextState), because it is not fired during mounting and creation of the component.
I can make special method processParams for reading this.props.match.params and retrieving data from the database.
render() {
const {contractId, description} = this.state;
this.processParams();
return (
<div>...</div>)}
But this method (which is called upon rendering) will update the global params or local params and this will cause the another call of render, so - I should make special logic in processParams to prevent the second round of rendering. I am quite uneasy to make such logic.
So - where (when) to read parameters passed by router?
Tutorials are quite basic and does not go into details.
I am specifically interested into loading REST API data into props. I am aware (thanks to answers) about getDerivedStateFromProps https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops which is exactly the event for loading REST API data into component's state, but I seek similar mechanism (event etc.) for loading REST API data into global store props. Maybe component can not do such loading? maybe application should listen to some navigation event and upon navigation event load data into store props and then application can simply render those global data with the component view.
Maybe it is bad design to load dynamically REST API data into props during runtime? E.g. all the CRUD tutorials:
http://www.thegreatcodeadventure.com/building-a-simple-crud-app-with-react-redux-part-1/
https://medium.com/#rajaraodv/a-guide-for-building-a-react-redux-crud-app-7fe0b8943d0f
https://github.com/maprihoda/react-redux-crud
are loading data into props during initialization of the main component of the application. My intention was that I can load list of conracts initially and then user can select (navigate to) some specific contract and my application will load this contract (extensive graph) into props upon opening of the contract form and reload different contract into props upon navigation to different contract.
You need to implement the componentWillReceiveProps function in your DetailsPage class component. 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. you may use this as reference
https://reactjs.org/docs/state-and-lifecycle.html
https://hackernoon.com/reactjs-component-lifecycle-methods-a-deep-dive-38275d9d13c0
You may get the params by using props.params.id
You can read it like
this.props.match && this.props.match.params.id
you can use this.props.match where-ever u needed.
Only make sure you are calling super in constructor with props.
constructor(props){
super(props)
}
With redirection,component is re-mounted.So,lifecycle phase also.

Is there a clean way to conditionally load and render different components for the same React Router route?

The use case is that I want to map the root (/) to one of two different components based on whether the user is logged in or not, and I want these two components to reside in different bundles and lazily loaded, so simply putting the login check in the render() method would not do.
I tried to use dynamic route definition with require.ensure() to lazily load the component, and it works for the first time, but after changing the login state the component doesn't get updated (even if I navigate to another route and back to / ).
I tried to force re-rendering the router by setting props on the component that contains the router, both manually and by making it a Redux connected component, and I also tried to add a listener to the Redux store and change the component state in response to login change, but in all of the attempts I got the error "You cannot change ; it will be ignored" and the component doesn't change.
My ugly solution is to have the different component loading code outside of the router, listen to the login state change and in response load the matching component and set it in the wrapping component's state, which is referenced in the render() code. Is there a clean "React-Router-ish" way to do what I want?
React Router 4 pretty much solves this as it made the route configuration part of the component rendering, so having conditional rendering is the same whether it's based on the location or on other props/state.
The closest thing to a clean "React-Router-ish" way to do that is to use the React Router Enterhooks.
An enter hook is a user-defined function that is called when a route is about to be rendered. It receives the next router state as its first argument. The replace function may be used to trigger a transition to a different URL.
So, use the onEnter(nextState, replace, callback?) attribute on your <Route />.
Called when a route is about to be entered. It provides the next router state and a function to redirect to another path. this will be the route instance that triggered the hook.
If callback is listed as a 3rd argument, this hook will run asynchronously, and the transition will block until callback is called.
The general best practice I follow is to place the auth-check flow away from your routes, and place it inside the transition events/hooks.
The usual behavior is - before the route handler actually gets rendered, check the auth, and redirect the user to another route. In your case, if you want to use the same route, but render different components - you should be able to do that using the same technique too. However, that's not a common thing (based on what I've seen), but it should be possible.
For a complete example of this approach, here's the auth-flow code example you can check. It is shared by the creators of React Router, so it looks credible to me.
PS: My answer is valid for React Router versions > 0.13.x.

Call custom method on component when props update

I have a YouTubeiFrame react component that renders an empty div and then drops the YouTube iframe player in to that div using componentDidMount. All works fine. I have a cueVideo method defined on the component which uses the players
API to cue new videos:
cueVideo(videoId) {
this.player.cueVideoById(videoId)
}
In different places in my app you can click a button or some another action to indicate that you want to play a new video. When you perform this action the 'currentVideo' state is updated via redux and as a result the YouTubeiFrame component receives the new video id as an updated prop.
My question is how to call the cueVideo method above in reaction to the updated prop. I've considered using shouldComponentUpdate to compare this.props.currentVideo with the next props
but concious this method is really supposed to return true/false so the render function can be called. All I want to do is call cueVideo when the currentVideo prop changes - I don't really need to re-render.
What's the best way to do this?
All I want to do is call cueVideo when the currentVideo prop changes - I don't really need to re-render.
When new props are received, React will automatically call render() to update your component. Once all the updates have been flushed to the DOM, React will also call componentDidUpdate() on your component to give you an opportunity to respond to the changes. As the documentation says:
Use this as an opportunity to operate on the DOM when the component has been updated.
I would suggest this is the perfect place to call your code as needed.

Resources