Screen Parameters
tsximport { createParam } from 'solito'const { useParam, useParams } = createParam()
tsximport { createParam } from 'solito'const { useParam, useParams } = createParam()
You can read and update multiple parameters at once using useParams():
tsximport { createParam } from 'solito'type Params = {artistSlug?: string}const { useParam, useParams } = createParam<Params>()export default function Page() {const { params, setParams } = useParams()const artistSlug = params.artistSlug // optional stringconst onPressUpdateSlug = () => {setParams({// params will merge with existing onesartistSlug: 'drake',})}}
tsximport { createParam } from 'solito'type Params = {artistSlug?: string}const { useParam, useParams } = createParam<Params>()export default function Page() {const { params, setParams } = useParams()const artistSlug = params.artistSlug // optional stringconst onPressUpdateSlug = () => {setParams({// params will merge with existing onesartistSlug: 'drake',})}}
On Web, this will update the parameters in the URL, including dynamic URL segments. On native, it will use React Navigation params.
For more advanced use cases, such as parameters that are numbers or arrays, you may be better off using the useParam hook.
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​
tsximport {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 >}
tsximport {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​
tsxtypeQuery = {username : string }Âconst {useParam } =createParam <Query >()Âexport constUserName = () => {const [username ,setUsername ] =useParam ('username')Âreturn <Text >{username }</Text >}
tsxtypeQuery = {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:
tsximport {createParam } from 'solito'ÂtypeQuery = {username : string }Âconst {useParam } =createParam <Query >()Âexport constuseUsername = () => {const [username ,setUsername ] =useParam ('username')Âreturn {username ,setUsername ,}}
tsximport {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:
tsximport {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 ,}}
tsximport {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:
tsxexport const User = () => {const { username, referredBy } = useUserScreenParams()// You can fetch the user hereconst user = useUser({ username })return (<View><Text>{username}</Text></View>)}
tsxexport 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​
jsimport {useRoute } from '@react-navigation/native'ÂconstuseUsername = () => {constroute =useRoute ()constusername =route .params ?.username returnusername }
jsimport {useRoute } from '@react-navigation/native'ÂconstuseUsername = () => {constroute =useRoute ()constusername =route .params ?.username returnusername }
After, with Solito​
tsximport {createParam } from 'solito'Âconst {useParam } =createParam ()Âexport constuseUsername = () => {const [username ] =useParam ('username')Âreturnusername }
tsximport {createParam } from 'solito'Âconst {useParam } =createParam ()Âexport constuseUsername = () => {const [username ] =useParam ('username')Âreturnusername }
Compare to Next.js Usage​
Before, with Next.js​
tsimport {useRouter } from 'next/router'ÂconstuseUsername = () => {constrouter =useRouter ()constusername =router ?.query .username returnusername }
tsimport {useRouter } from 'next/router'ÂconstuseUsername = () => {constrouter =useRouter ()constusername =router ?.query .username returnusername }
After, with Solito​
tsximport {createParam } from 'solito'Âconst {useParam } =createParam ()Âexport constuseUsername = () => {const [username ] =useParam ('username')Âreturnusername }
tsximport {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:
tstypeQuery = {bookingId : stringtemplate : 'story' | 'square'}
tstypeQuery = {bookingId : stringtemplate : 'story' | 'square'}
The values of
Querymust 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:
tsimport {createParam } from 'solito'ÂtypeQuery = {bookingId : stringtemplate : 'story' | 'square'}Âconst {useParam } =createParam <Query >()
tsimport {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:
tstypeQuery = {bookingId : stringtemplate : 'story' | 'square'}Âconst {useParam } =createParam <Query >()Âexport functionApp () {const [bookingId ,setBookingId ] =useParam ('bookingId')}
tstypeQuery = {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:
tsconst [selected ,setSelected ] =useState (true)
tsconst [selected ,setSelected ] =useState (true)
With useParam we achieve the same thing with the initial property:
tsconst [template ,setTemplate ] =useParam ('template', {initial : 'story',})
tsconst [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:
tstypeQuery = {bookingId : stringtemplate : 'story' | 'square'}
tstypeQuery = {bookingId : stringtemplate : 'story' | 'square'}
Our template is a required field that accepts square or story.
A naive approach would use it like this:
tsconst [template ,setTemplate ] =useParam ('template', {initial : 'story',})
tsconst [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:
tstypeQuery = {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'},})
tstypeQuery = {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:
tstype 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: '',})
tstype 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: '',})