React router: component not updating on url search param change - javascript

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

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

React router DOM : Switch not working if child element isnt a route

I'm facing an issue im not able to comprehend. I'm working on a React codebase and we started having too much Routes, so i decided to refactor it a little bit. There is some logic to determine if we should redirect to a route that will check if you're authenticated, and if not will redirect you to the sign in page. We also have basic routes without that mechanism that just display a component ( for every page that doesnt need authentication like sign in, forgot password etc. )
I've decided to abstract that logic in a different component, that will ultimatley render a Route. As a first step in that direction, i decided to wrap the rendering of every route in a single component, instead of having the whole logic just laying down there.
Here's the code for the routes:
<Switch>
{Object.values(userRoutes).map((route: SanitizedRoute) => (
<RouteController route={route} key={route.PATH} />
))}
</Switch>
RouterController.tsx
return (
<Route
path={props.route.PATH}
exact={props.route.EXACT}
render={() => {
return <Layout>{props.route.COMPONENT}</Layout>;
}}
/>
);
All the information for the Route component is passed down as a prop. This is where i start getting problems. If i try to access /path_b what is rendered is the first element of my userRoute array, eventhough the Route doesn't match; /path_b or /path_c or /path_whatever will always render the compononent defined for /path_a.
Accessing any path actually returns the /path_a component as if it was the only one present in my Switch component.
If i were to replace the RouteController component by its content as such :
<Switch>
{Object.values(userRoutes).map((route: SanitizedRoute) => (
<Route
path={route.PATH}
exact={route.EXACT}
key={route.PATH}
render={() => {
return <Layout>{route.COMPONENT}</Layout>;
}}
/>
))}
</Switch>
Then everything would work fine as expected. Wrapping my RouteController with a Switch component is also working fine - although im not sure about the side effect of having a Switch per route ?
{Object.values(userRoutes).map((route: SanitizedRoute) => (
<Switch key={route.PATH}>
<RouteController route={route} />
</Switch>
))}
My questions are:
Why isn't it possible to wrap all of those routes in a single ?
Is there some props from the component i should manually pass down to the component via my ?
I know that the is used to render the first route that match the path. Does having multiple kind of defeat the purpose of using a in the first place ?
Here's a Sandbox to display this behavior : https://codesandbox.io/s/react-router-dom-switch-test-forked-sqc98
I wrapper 1 route in a component. When using the wrapper nothing work. If i copy paste the wrapper content inside the Switch, it does.
Why isn't it possible to wrap all of those routes in a single
<Switch>?
This is because of how the Switch component works, it renders the first child <Route> or <Redirect> that matches the location.
Switch
The RouteController component is neither, so it's not involved in matching, and in fact only first component will be rendered. This is why you see only the first route rendered regardless of path.
Is there some props from the <Switch> component I should manually
pass down to the <Route> component via my <RouterController>?
It's possible to move/define a path prop (and any other "route" prop) on the RouteController the Switch may use for path matching, but this is ill-advised and not a documented use case of the Switch and Route components.
I know that the <Switch> is used to render the first route that
match the path. Does having multiple <Switch> kind of defeat the
purpose of using a <Switch> in the first place?
Yes, if you are rendering multiple Switch components, each with only a single Route component then they aren't really switching routes at this point and you're relying on the inclusive route matching of any wrapping router, or exclusively matching in the case of any other wrapping switches.
Solution
Instead of trying to map routes into your RouterController component and dealing with the issues of what is directly composing what, have your RouterController consume all the routes as a prop, and render them into a single Switch component.
RouterController.tsx
const RouterController = ({ routes }) => {
// any component business logic
return (
<Switch>
{routes.map((route: SanitizedRoute) => (
<Route
path={route.PATH}
exact={route.EXACT}
key={route.PATH}
render={() => <Layout>{route.COMPONENT}</Layout>}
/>
))}
</Switch>
);
};
...
<RouterController routes={Object.values(userRoutes)} />

React-router-dom how to update parent state inside route component

I have a class parent class App containing a navbar and a bunch of routes:
function App() {
const [showNavbar, setShowNavbar] = useState(true);
return (
<>
<MyNavBar show={showNavbar}/>
<Switch>
<Route exact path='/child' component={() => <ChildPart setter={showNavbar}/>}/>
<Route> ... //other routes
</Switch>
</>
)
}
function ChildPart(props) {
const [data, setData] = useState("");
// hide navbar
useEffect(() => {
console.log("child init"); // this got printed twice
props.setter(false);
}, [props.setter]);
// get data at first mount;
useEffect(()=>{
setData(api.loadUserInfo());
}, []);
return (<div>
Data here: {data}
</div>)
}
What I want to achieve is to have navbar change based on which page a user is currently at.
For example, hide navbar when use is visiting /child
But by implementing it this way, the ChildPart seems got mount and unmount for twice since the parent is rendered and rotues are also a part of the App.
This causes unnecessary API calls inside the child component.
Is there any way to address this?
Have you considered adding the Navbar as a component in the Routes, instead of using the state to decide if the navbar should be visible or not?
<Switch>
<Route exact path='/child' component={ChildPart}/>
<Route path="">
<MyNavBar />
<Home />
</Route>
</Switch>
If hiding one (MyNavBar) component by navigating to one (or more) urls is a goal, something like this could work. Unfortunatelly negative lookup (regex) is not supported by react router so we can't use something like this (?!child).
Another thing which you can do is making MyNavBar aware of current url by wrapping it withRouter HOC or using useLocation hook. Than, you can do your logic in MyNavBar depending on url.

How to call setState with componentDidMount without causing extra calls to render?

I have a problem with componentDidMount: everytime that i use setState in componentDidMount it calls render several times in child components, and I don't know why... look:
componentDidMount() {
const firstName = localStorage.getItem('nameLoggedUser');
const lastName = localStorage.getItem('lastNameLoggedUser');
const fullName = `${firstName} ${lastName}`.toLowerCase();
const loggedUserIs = localStorage.getItem("user-role");
const emailLoggedUser = localStorage.getItem('emailLoggedUser');
if (loggedUserIs === 'full') {
axios.get(`/api/menuDomain`)
.then(res => this.setState({
domain: res.data
}))
.catch(err => console.log(err))
}
}
It gives this:
But, if I use ComponentDidUpdate, it gives:
That is correct, but the AJAX call is not happening...
I want to make the AJAX call and not have it render several times... But I don't know how... Could someone help me? Please???
And I am using react-router too:
return (
<Router>
<Switch>
<Route path="/login" component={Login} />
<Route exact path="/" render={() =>
<Overview
{...myProps}
/>
}
/>
<Route path="/list" render={() =>
<Graphic
{...myProps}
/>
} />
</Switch>
</Router>
);
}
}
First, never wrap your routes within a stateful component. Instead, the routes should be a stateless function that just returns the same JSX route elements. For the example above, you should use a HOC that wraps your protected routes and is only concerned with authentication: https://stackoverflow.com/a/53197429/7376526
Second, you shouldn't be storing the logged in user in plain text within local storage. Instead, it and possibly the password should be stored within a JWT token (or within some sort of hashed plus salted token) or perhaps within a secured cookie and then decoded and compared against server-side. How you're currently setting it to localStorage is incredibly insecure and anyone can simply set a user's first and last name and their "user-role" and gain access to their account.
Third, each container should handle and fetch relevant data and pass it to a relevant component for display. For performance, you should compartmentalize your retrieved data to relevant data for that particular component. Since you have multiple routes, fetching everything at once is wasteful. For example, fetch Overview data inside of a FetchOverview container's componentDidMount, then pass it down to a DisplayOverview child component for display. Read this for more information on containers and components: https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

In React, How do I redirect a user back to the index with altered state

In my React.JS application, I want to create a link which does nothing more than alter the client's state, but show the main index page. For example, the main page is:
http://example.com/#/
This shows the main index page. Now, if a user is directed to this page:
http://example.com/#/check?arg=abcd
I would want to still show the user the main index page, but have the this.props variable be affected by the arg=abcd
I define my routes with:
var routes = (
<Route handler={APP}>
<DefaultRoute handler={Index} />
<Route name="check" path="check" handler={check}></Route>
</Route>
);
So I do have a route which takes in a check path, but again my goal is to just use the argument to check and still keep showing the regular index page.
What is the most efficient way of doing this with React.JS?
I just realized that I could circumvent the need of a second page and router. All I need to do is use an argument handler on the index page. I am away from work and on mobile so I will answer my own question tomorrow when I get in.
Update --
So this is how I got it working:
I added a new line to the route set up:
var routes = (
<Route handler={APP}>
<DefaultRoute handler={Index} />
<Route path="/arg1/:arg1" handler={Index} />
</Route>
);
The line with arg1. Then I made sure the handler to that path was {Index}. Now, within my component, I have the following line:
componentWillMount() {
this._handleArgs(this.props.params.arg1);
},
Which will handle (or not-handle if arg1 is null) any args before mounting the component.
So, no need to add a new component, just another route to the same component and some helper functions.

Categories

Resources