useParam
tsx
import {createParam } from 'solito'Âconst {useParam } =createParam ()
tsx
import {createParam } from 'solito'Âconst {useParam } =createParam ()
useParam
is a hook that lets you read screen parameters on both Next.js and React Native. On Native, it reads React Navigation params, and on Web, it reads query params from next/router
.
useParam
reads both query parameters and dynamic route parameters. A Next.js dynamic route might look like /artists/[slug].tsx
, where slug
is a dynamic route. useParam('slug')
works there too. On the native side, your linking config would have a URL with /artists/:slug
.
It also lets you update the parameter, using query parameters on Web, and React state on iOS/Android.
Video​
Learn how to use useParam
from Fernando Rojo, creator of Solito, in his Next.js Conf 2021 talk.
Quick look​
tsx
import {createParam } from 'solito'import {Text } from 'react-native'Â// first, generate the hookconst {useParam } =createParam ()Âexport constUserName = () => {const [username ,setUsername ] =useParam ('username')Âreturn <Text >{username }</Text >}
tsx
import {createParam } from 'solito'import {Text } from 'react-native'Â// first, generate the hookconst {useParam } =createParam ()Âexport constUserName = () => {const [username ,setUsername ] =useParam ('username')Âreturn <Text >{username }</Text >}
You'll use useParam()
instead of useRoute().params
from React Navigation, and instead of useRouter().query
from Next.js.
TypeScript​
tsx
typeQuery = {username : string }Âconst {useParam } =createParam <Query >()Âexport constUserName = () => {const [username ,setUsername ] =useParam ('username')Âreturn <Text >{username }</Text >}
tsx
typeQuery = {username : string }Âconst {useParam } =createParam <Query >()Âexport constUserName = () => {const [username ,setUsername ] =useParam ('username')Âreturn <Text >{username }</Text >}
Create custom hooks​
Rather than writing useParam
directly into your components, you should wrap them into your own hooks.
For example, create your own useUsername
hook:
tsx
import {createParam } from 'solito'ÂtypeQuery = {username : string }Âconst {useParam } =createParam <Query >()Âexport constuseUsername = () => {const [username ,setUsername ] =useParam ('username')Âreturn {username ,setUsername ,}}
tsx
import {createParam } from 'solito'ÂtypeQuery = {username : string }Âconst {useParam } =createParam <Query >()Âexport constuseUsername = () => {const [username ,setUsername ] =useParam ('username')Âreturn {username ,setUsername ,}}
And if you want to get all the parameters for a given screen, you could do this:
tsx
import {createParam } from 'solito'ÂtypeUserScreenParams = {username : string;referredBy ?: string }Âconst {useParam } =createParam <UserScreenParams >()Âexport constuseUserScreenParams = () => {const [username ] =useParam ('username')const [referredBy ] =useParam ('referredBy')Âreturn {username ,referredBy ,}}
tsx
import {createParam } from 'solito'ÂtypeUserScreenParams = {username : string;referredBy ?: string }Âconst {useParam } =createParam <UserScreenParams >()Âexport constuseUserScreenParams = () => {const [username ] =useParam ('username')const [referredBy ] =useParam ('referredBy')Âreturn {username ,referredBy ,}}
Then, use the hook in your screen:
tsx
export const User = () => {const { username, referredBy } = useUserScreenParams()// You can fetch the user hereconst user = useUser({ username })return (<View><Text>{username}</Text></View>)}
tsx
export const User = () => {const { username, referredBy } = useUserScreenParams()// You can fetch the user hereconst user = useUser({ username })return (<View><Text>{username}</Text></View>)}
Compare to React Navigation Usage​
Let's compare what this hook does compared to React Navigation, to help you easily migrate.
Before, with React Navigation​
js
import {useRoute } from '@react-navigation/native'ÂconstuseUsername = () => {constroute =useRoute ()constusername =route .params ?.username returnusername }
js
import {useRoute } from '@react-navigation/native'ÂconstuseUsername = () => {constroute =useRoute ()constusername =route .params ?.username returnusername }
After, with Solito​
tsx
import {createParam } from 'solito'Âconst {useParam } =createParam ()Âexport constuseUsername = () => {const [username ] =useParam ('username')Âreturnusername }
tsx
import {createParam } from 'solito'Âconst {useParam } =createParam ()Âexport constuseUsername = () => {const [username ] =useParam ('username')Âreturnusername }
Compare to Next.js Usage​
Before, with Next.js​
ts
import {useRouter } from 'next/router'ÂconstuseUsername = () => {constrouter =useRouter ()constusername =router ?.query .username returnusername }
ts
import {useRouter } from 'next/router'ÂconstuseUsername = () => {constrouter =useRouter ()constusername =router ?.query .username returnusername }
After, with Solito​
tsx
import {createParam } from 'solito'Âconst {useParam } =createParam ()Âexport constuseUsername = () => {const [username ] =useParam ('username')Âreturnusername }
tsx
import {createParam } from 'solito'Âconst {useParam } =createParam ()Âexport constuseUsername = () => {const [username ] =useParam ('username')Âreturnusername }
Motivation​
A typical use-case on Web for maintaining React State is your URL's query parameters. It lets users refresh pages & share links without losing their spot in your app.
URL-as-state is especially useful on Next.js, since next/router
will re-render your page with shallow navigation.
useParam
lets you leverage the power of URL-as-state, while providing a fallback to React state for usage in React Native apps.
It's essentially a replacement for useState
.
First, create the schema for your query parameters:
ts
typeQuery = {bookingId : stringtemplate : 'story' | 'square'}
ts
typeQuery = {bookingId : stringtemplate : 'story' | 'square'}
The values of
Query
must be primitives, such as strings/booleans/numbers, since you can't use nested fields in a URL with next router.
Next, we're going to generate our useParam
function:
ts
import {createParam } from 'solito'ÂtypeQuery = {bookingId : stringtemplate : 'story' | 'square'}Âconst {useParam } =createParam <Query >()
ts
import {createParam } from 'solito'ÂtypeQuery = {bookingId : stringtemplate : 'story' | 'square'}Âconst {useParam } =createParam <Query >()
This usage of a factory is similar to react-navigation
's createStackNavigator
. It allows us to have great TypeScript safety.
Usage​
Now that we've created our useParam
function, call it in your component:
ts
typeQuery = {bookingId : stringtemplate : 'story' | 'square'}Âconst {useParam } =createParam <Query >()Âexport functionApp () {const [bookingId ,setBookingId ] =useParam ('bookingId')}
ts
typeQuery = {bookingId : stringtemplate : 'story' | 'square'}Âconst {useParam } =createParam <Query >()Âexport functionApp () {const [bookingId ,setBookingId ] =useParam ('bookingId')}
Whenever you call setBookingId
, it will update the query parameter in the URL. To remove the query parameter, call setBookingId(null)
.
On native, this will function as normal React State.
Initial value​
With React state, we pass an initial value like this:
ts
const [selected ,setSelected ] =useState (true)
ts
const [selected ,setSelected ] =useState (true)
With useParam
we achieve the same thing with the initial
property:
ts
const [template ,setTemplate ] =useParam ('template', {initial : 'story',})
ts
const [template ,setTemplate ] =useParam ('template', {initial : 'story',})
However, on web, this might not aways be the initial value. This is because the initial value itself could be set from the URL on the first navigation.
initial
gets used on Web when these two cases are satisfied:
- the query param (in this case,
template
) isundefined
- you haven't called the set state function yet (in this case,
setTemplate
)
There is might appear to be an edge case here. What happens if you call setTemplate(null)
? This will remove the query parameter from the URL, so we're left with an empty state. But it also won't fall back to the initial
field, since this wouldn't match the React state behavior.
Can we find a way to provide a fallback value on Web in this case, to make sure that our URL isn't the only source of truth?
The solution lies with the parse
field.
Parsing values​
One issue with having state in URLs is, users have an API to inject whatever state they want into your app.
This could break in many ways.
Take our Query
type we wrote earlier:
ts
typeQuery = {bookingId : stringtemplate : 'story' | 'square'}
ts
typeQuery = {bookingId : stringtemplate : 'story' | 'square'}
Our template
is a required field that accepts square
or story
.
A naive approach would use it like this:
ts
const [template ,setTemplate ] =useParam ('template', {initial : 'story',})
ts
const [template ,setTemplate ] =useParam ('template', {initial : 'story',})
There are two problems here: what if the URL doesn't have template
? Or worse, what if it does have template
, but it doesn't match one of the types you specified?
Enter parse
:
ts
typeQuery = {bookingId : stringtemplate : 'story' | 'square'}Âconst {useParam } =createParam <Query >()Âconst [template ,setTemplate ] =useParam ('template', {initial : 'story',parse : (templateFromUrl ) => {if (templateFromUrl === 'story' ||templateFromUrl === 'square') {returntemplateFromUrl }return 'story'},})
ts
typeQuery = {bookingId : stringtemplate : 'story' | 'square'}Âconst {useParam } =createParam <Query >()Âconst [template ,setTemplate ] =useParam ('template', {initial : 'story',parse : (templateFromUrl ) => {if (templateFromUrl === 'story' ||templateFromUrl === 'square') {returntemplateFromUrl }return 'story'},})
parse
is the final piece of the puzzle. It lets you ensure that any state you're using from your URL is "safe".
It's also strictly typesafe, which is an added bonus.
The argument it receives will always be a string
parse
gets run when this case is satisfied:
- the query param (in this case,
template
) is notundefined
warning
The parse
function will only run on Web, not on Native. To configure a parse
function on native, you must use the parse
option from your React Navigation linking config.
Strict Types​
This hook has great strict types.
The state value it returns will always be State | undefined
, unless you pass both an initial
value and parse
. That way, we know that on both Web and native, we're always using values which match our state.
Stringify​
It's possible you'll want to customize the way that the query param is stored in the URL.
If so, you can use the stringify
property:
ts
type Query = {bookingId: stringtemplate: 'story' | 'square'}const { useParam } = createParam<Query>()const [bookingId, setBookingId] = useParam('bookingId', {stringify: (bookingId) => {// if we call setBookingId('123')// URL will be ?bookingId=artist-123return `artist-${bookingId}`},parse: (bookingIdFromUrl) => {// remember that URL params can be arraysif (Array.isArray(bookingIdFromUrl)) {return bookingIdFromUrl[0]?.replace('artist-', '')}return bookingIdFromUrl?.replace('artist-', '')},initial: '',})
ts
type Query = {bookingId: stringtemplate: 'story' | 'square'}const { useParam } = createParam<Query>()const [bookingId, setBookingId] = useParam('bookingId', {stringify: (bookingId) => {// if we call setBookingId('123')// URL will be ?bookingId=artist-123return `artist-${bookingId}`},parse: (bookingIdFromUrl) => {// remember that URL params can be arraysif (Array.isArray(bookingIdFromUrl)) {return bookingIdFromUrl[0]?.replace('artist-', '')}return bookingIdFromUrl?.replace('artist-', '')},initial: '',})