React Router 4, Route with stateless component - javascript

If I want to route to a stateless component I use the component which will give me a match params
e.g.
<Route path="/ingredient/:ingredientID" component={IngredientPage} />
the problem with using the "component" attribute is I can't pass in a class function to be used. I'd like to fetch api data based on the id before I load the component and I want the component to be a function component.
if I use render I can't call a state change because that will re-render (infinite loop).
<Route path="/ingredient/:ingredientID" render={(props) => {
this.loadIngredient(props.match.params.ingredientID)
//this will change state which creates a loop
return <IngredientPage ingredient={this.state.ingredient} />
} />
So far the only way I can figure this out is making the component a state component like it's parent but then I am dealing with two states.
Any suggestions?

Related

React router v6 access route params and pass as props

In react router v6, how can I pass route params to a component without the need to use useParams() in the component?
This is what I want to do:
<Route
path='/'
element={ProfileComponent username={'thedefault'}
/>
<Route
exact
path='/u/:username/'
render={(props) =>
<ProfileComponent username={props.match.params.username}/>
}
/>
I don't want to put useParams() in the component because this tightly couples it to the URL. For example, what if I wanted to render another ProfileComponent elsewhere, with a different username to that in the URL. It seems to violate best practice for unit testing unless I can do it like my example.
I don't want to put useParams() in the component because this
tightly couples it to the URL. For example, what if I wanted to render
another ProfileComponent elsewhere, with a different username to that
in the URL. It seems to violate best practice for unit testing unless
I can do it like my example.
Any route using a username route match param would still be accessible via the useParams hook, but I think I understand what you are after. If I understand your question correctly you are asking how to map a route match param to a component prop in a generic way.
For this you can simply use a wrapper component to "sip" the route match param and pass it along to your component on any specific prop.
const ProfileComponentWrapper = () => {
const { username } = useParams();
return <ProfileComponent username={username} />;
};
...
<Route
path='/u/:username/'
element={<ProfileComponentWrapper />}
/>
In the docs, it is clearly specified that it is not possible
Normally in React you'd pass this as a prop: , but you don't control that information because it comes from the
URL.
https://reactrouter.com/docs/en/v6/getting-started/tutorial#reading-url-params
So, you have to use useParams in the component

Re-render a React Component

I have a profile component with URL: '/profile/id'.
Now, suppose I have two users having IDs: id_A and id_B.
I am at URL: '/profile/id_A' and I have a button which sends me to URL: '/profile/id_B' using:
<Link to={{ pathname: `/profile/id_B` }}></Link>
Now, my component won't re-render because there is no state change for which I cannot retrieve my user-data from the server since all my requests are done on componentDidMount.
So, how do I re-render my component for the above scenario?
UPD 1:
This is my Route code:
<Route path="/profile/:userId" component={Profile} />
UPD 2:
I have 3 components nested:
Profile -> FollowList -> Card
I have my link in my card component and this is the code for routing it to 'profile/id_B'.
<Link to={{ pathname: `/profile/${this.props.user.userId}` }}></Link>
Can this be a problem that the below answers are not working?
Ciao, to re-render a component you can use shouldComponentUpdate(nextProps) function. Basically React triggers this function to ask you if component should be updated (re-rendered) and here you can write the logic you need to re-render the component. Take a look at this guide.
It appears you need to use the Route component, placing your "pages" on them with one of the designed methods. You can read more about it here:
https://reactrouter.com/web/api/Route
The recommended way of doing it is using the children of the Route, which is supposed to mount the page when the url matches, and unmount when they don't. So something like:
<Route path="/to">
<My Page/>
</Route>
The thing is that your component is getting rerendered but as you have your code in componentDidMount and it is called only once when the component is mounted so you are facing the difficulty.
So to fix this problem you need to unmount and mount your component again, this can be done using the inline function inside component prop.
According to react-router-docs
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component.
So you need to refactor your code and use
<Route path="/profile/:userId" component={() => (<Profile />)} />
This will mount and unmount on component on each render and you can access the component did mount.
Alternate Solution
You can also use ComponentDidUpdate and check on each update if the id is same or not, if id is not same fetch the data.
componentDidUpdate(prevProps) {
if (prevProps.match.params.userId !== this.props.match.params.userId) {
//call your fetch function here and then set the data in state
setState({
userId: data,
});
}
}

React router: component not updating on url search param change

I have a React application that uses URL search params. Depending on the value of the search parameter v, the component is supposed to show different content.
A possible URL might look like this:
http://localhost:3000/hello?v=12345
Once the v query parameter changes in the URL, I would like my component to update.
http://localhost:3000/hello?v=6789
I'm using using react-router in my App component to display the different components depending on the route. The App component is wrapped into a BrowserRouter component.
render() {
return (
<Switch>
<Route path="/hello" component={Hello}></Route>
<Route path="/" component={Home}></Route>
</Switch>
);
}
If a user clicks on something and the URL search param v changes, I would like the component to display different content. Currently, the URL search param does change, but the component doesn't render again. Any ideas?
As #forJ correctly pointed out, the main idea is to make the component re render once the URL parameters change. I achieved it like so:
render() {
return (
<Switch>
<Route path="/hello" render={() => (<Hello key={this.props.location.key}/>)}></Route>
<Route path="/" component={Home}></Route>
</Switch>
);
}
this.props.location.key changes every time the URL changes (also if the query params change). So if you pass it as props to the component, then the component re renders on URL param changes, even though the base URL (the URL without the URL params) hasn't changed.
I also had to use the withRouter higher order component to make it work.
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Hello));
You have not provided enough information so I cannot be 100% sure where you are going wrong but I can see 1 problem right now in your code. First, you have to let the router know that you will be using parameters. So for example below code
render() {
return (
<Switch>
<Route path="/hello/:yourParam" component={Hello}></Route> // you need the colon here
<Route path="/" component={Home}></Route>
</Switch>
);
}
The above will tell the route that parameter called yourParam will be accessible from the URL
Then it has to be accessed in your component and put into render function somehow (use it to retrieve data from database to render data, just render params directly, etc).
For example below
render(){
<div>
{this.props.match.params.yourParam}
</div>
}
This way, your component will re-render everytime there is param change.
EDIT
Since you want to use query instead of parameter, you would have to retrieve it by going. this.props.match.query.v. You would still have to make sure that variable is rendered depending on query change
If you use useEffect hook make sure to have [props.match.params] instead of empty array []
useEffect(() => {
(async () => {
// Fetch your data
})();
}, [props.match.params]) // <-- By this it will get called when URL changes
of course for class components this.props.match.params

onEnter prop in react-router v4

I'm using react-router v4. I find it confusing that when I click on <Link> component, the route is immediately changed. I want to stay at the current route before I fetch the data, not to switch the route and then fetch the data. I find that in react-router v3 there's a onEnter hook, but now in react-router v4, it is deprecated. So how can I fetch data right after I click on <Link> component and before the route is changed?
Example:
it's: at A -> click at <Link> to B -> fetching data for B -> render B
not: at A -> click at <Link> to B -> render Route B -> fetching data for B -> rerender B
In react-router v4, you can make use of render prop to Route to replace the onEnter functionality existing in react-router v3
Suppose you have a route like in react-router v3
<Route path="/" component={Home} onEnter={getData} />
The Equivalent of this in react-router v4 would be
<Route exact path="/" render= {() => {getData(); return <Home data={this.state.data}/>}} />
However the suggested method is to make use of lifecycle methods in the component.
From the Github discussion:
We have no lifecycle hooks that receive props on initial render.
We no longer have a "route lifecycle". Instead, since <Match>
components are real components (not pseudo-components like s
were) they get to participate in React's lifecycle, which means they
can just use componentWillMount.
So one of the suggested solution is:
v4 is all about routes just being components. That means taking advantage of
lifecycle methods.
componentWillMount() { // or componentDidMount
fetch(this.props.match.params.id)
}
componentWillReceiveProps(nextProps) { // or componentDidUpdate
if (nextProps.match.params.id !== this.props.match.params.id) {
fetch(nextProps.match.params.id)
}
}

React-router v4 unmounting when using render

Potentially a bit confused with how the render method works in react-router and what 'mounting' really means in React. I read the docs and it said that:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component attribute, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).
render: func
This allows for convenient inline rendering and wrapping without the undesired remounting explained above.
So I expected that when the url changes, the existing component will be not be unmounted but instead stay there and hence save its state. But this is not what I observe. Here is my code:
<div style={styles.content}>
<Route path={`${this.props.match.url}/summary`} render={() => <ETFViewer/>} />
<Route path={`${this.props.match.url}/proxy`} render={() => <Proxy/>} />
</div>
So the components ETFViewer and Proxy get rendered fine and there's nothing wrong with them, but I notice that as I move from /proxy to /summary and back that the state is lost! Moreover, I can see in React devtools in Chrome that the component is indeed gone...
So what is going on here? Is there any way to allow the component to save its state and just "hide"?
Thanks for your help!
This question is a bit old but came up in a recent search of mine. I believe the issue is that you are not passing down your props along with the component. Here is an example:
<div style={styles.content}>
<Route path={`${this.props.match.url}/summary`} render={props => <ETFViewer {...props} />} />
<Route path={`${this.props.match.url}/proxy`} render={props => <Proxy {...props} />} />
</div>

Categories

Resources