I was recently looking into storing some values in the url to enable users to pass urls around and load the app into a certain state. Simple enough, though complicated by the fact that the app also uses redux to manage the apps state as Redux will not be covered here we will mimic this behaviour with state instead.
Want to see the code used? Visit Code Sandbox
Setup
So to start off we need to make sure that we have react-router-dom and react-router installed and our app is using react-router-dom (if you need to help with this Paul Sherman has a great article on the basic setup. We also need to make sure that the component that we want to capture use the search parameters has exposure to them via the props so we need to add RouterComponentProps to the prop type and then when exporting the component we need to wrap with withRouter( ).
import {RouteComponentProps, withRouter} from "react-router";
class SimpleMainComponent extends React.Component<
RouteComponentProps<any>
{
...
}
export default withRouter(SimpleMainComponent);
Getting the search query
After the setup is done we will be able to access the route properties, the one we are interested in is the location.search properly.
this.props.location.search
this comes through as a string, so when we navigate to "http://.../user?name=john" it will mean that location.search is "?name=john". This is not very helpful to us when we want to grab a specific value from this. This is where the URLSearchParams type comes into play
let search = new URLSearchParams(this.props.location.search);
this encapsulates the logic required to pull out values of the search query based on a key.
let search = new URLSearchParams(this.props.location.search);
let name = search.get("name");
when used with the url that we had above, "http://.../user?name=john", we get name equalling "john". Perfect, we're ready to start piecing together a simple component to consume values from our search query.
import * as React from "react";
import { RouteComponentProps, withRouter } from "react-router";
class SimpleMainComponent extends React.Component<RouteComponentProps<any>> {
render() {
let name = this.getName();
return (
<div>
name is: {name}
</div>
);
}
getName(): string {
let search = this.getUrlParams();
return search.get("name") || "";
}
getUrlParams(): URLSearchParams {
if (!this.props.location.search) return new URLSearchParams();
return new URLSearchParams(this.props.location.search);
}
}
Statefulness problems
The next step starts the problem. If we are using redux along with this we have the problem that we might read the name, store it in redux, navigate to a different area of the app then return to this component later. In this case the url is now wrong, though we might get to "/users" our search parameter has gone. We might have the users name set correctly in redux but the search parameter is misaligned with what is being displayed and therefore if we refresh the page we will get a different result shown. In this situation we could possibly want to append the search query string on to the end of the route when we come back to this component. To do this we will need to store that string somewhere.
If we revisit the component that we've just made we can mimic this behaviour by putting it into state. The specific behaviour that i want to draw attention to is the re-rendering being triggered when the state or props change. A problem we quickly run into is continuously re-rendering the component if we simply put the search paramerter into state like so
state = {
search: this.props.location.search,
};
componentDidMount() {
this.setState({ search: this.getUrlParams().toString() });
}
componentDidUpdate() {
this.setState({ search: this.getUrlParams().toString() });
}
This is a clear violation of setting state, but one that can be easily missed, especially if you break out the logic of getting the search parameters, finding the values you want and updating the state. What is the solution?
componentDidMount() {
let search = this.getUrlParams().toString();
this.setState({ search: search });
}
componentDidUpdate() {
let search = this.getUrlParams().toString();
if (this.didQueryChange(search)) this.setState({ search: search });
}
...
didQueryChange(search: string): boolean {
return this.state.search !== search;
}
with this check now in place we can saftely update our props or state without causing an infinate re-rendering loop
import * as React from "react";
import { RouteComponentProps, withRouter } from "react-router";
type State = { search: string; count: number };
class MainComponent extends React.Component<RouteComponentProps<any>, State> {
state = {
search: this.props.location.search,
count: 0
};
componentDidMount() {
let search = this.getUrlParams().toString();
this.setState({ search: search });
}
componentDidUpdate() {
let search = this.getUrlParams().toString();
if (this.didQueryChange(search)) this.setState({ search: search });
}
render() {
let name = this.getName();
return (
<div>
name is: {name}{" "}
<button onClick={this.increaseCount}>count: {this.state.count}</button>
</div>
);
}
increaseCount = () => this.setState({ count: this.state.count + 1 });
getName(): string {
let search = this.getUrlParams();
return search.get("name") || "";
}
didQueryChange(search: string): boolean {
return this.state.search !== search;
}
getUrlParams(): URLSearchParams {
if (!this.props.location.search) return new URLSearchParams();
return new URLSearchParams(this.props.location.search);
}
}
export default withRouter(MainComponent);
One of our big problems when comparing is that this.props.location.search is not equal to new URLSearchParams(this.props.location.search).toString(). This might seem obvious but this has caused heachaches before as methods such as getUrlParams() are so easy to use, handling the safe retrial of the search query, that it's tempting to do something like
this.state.search !== this.props.location.search;
...
this.setState({ search: this.getUrlParams().toString()})
but these evaluate to do different things
- this.props.location.search = "?name=john"
- this.getUrlParams().toString = "name=john"
you can set the search string state to either one, but be careful that when you use the same retrial method to for both the comparing and setting of the stateful string
Last tip
explore the URLSearchParams type as it does more than just read the values, you can use this type to also set values, for instance
let values = this.getUrlParams();
values.append("count",this.state.count.toString())
this.props.history.push({search:values.toString()})
In Summary
It's fairly quick and painless to get started with accessing the URL search parameters in react with typescript, we just have to remember to use the URLSearchParams type when getting and setting values. We can easily run into problems when we start trying to add this into the state of our application and therefore need to guard against infinite rendering problems.
The code for this tutorial can be found in github or play with it on code sandbox
Top comments (0)