Closed nikkwong closed 1 year ago
Also.. I have some subset of components that can be mounted at different routes. Let's say they're games. A game like galaga could be mounted at /games/galaga
or /admin/games/galaga
. The game then has routing internally, like /games/galaga/scene-1
or /admin/games/galaga/scene-2
.
At the galaga path (mounted at either /games
or /admin
, I'm not sure how i can relatively redirect to individual scenes in the game, since router.push
only allows absolute navigation and I don't know where the user currently is in the absence of the current route information.
was just looking for this as well, see #10
was exposed in expo-router via useHref
#59
@nandorojo whether included in API or not, it'd be worth documenting reasoning and possible workarounds as this is standard navigation pattern
I think you can use expo router's solution, but outside of that getting the URL isn't very safe on native.
So far, I've resorted to using react context to wrap certain screens and such to note where they are. I know it's imperfect. But I'm hesitant to expose an API for this. There are unsafe ones used by Showtime's solito repo on native which you could try in user land.
Thank you for the responses! useHref
only works in RN though, right? How could I do this on any of the react components in the packages/app
dir? There needs to be.. something. Not being able to show which tab is active (in relation to its presence (or lack thereof) in the URL) is going to be tough to explain to my stakeholders.
So far, I've resorted to using react context to wrap certain screens and such to note where they are. I know it's imperfect. But I'm hesitant to expose an API for this.
It would be awesome to understand how this works.
Thank you :)
How could I do this on any of the react components in the packages/app dir? Pass it down as props, or as suggested above, via React context
I would make components in shared app directory accept higher level property, e.g. isActive
, rather than router implementation details
useHref only works in RN though, right?
yes, there's no consolidate API in Solito, given reasoning mentioned above so you'd use expo and next.js respective router APIs
Yeah.. makes sense. How would you do this though? I was thinking in the next/pages/admin/games/galaga.ts
file you could do something like...
// next/pages/admin/games/galaga.ts
import GalagaComponentWrapper from 'app/features/games/index.tsx'
// add the current route here:
export GalagaComponentWrapper('/admin/games/galaga')
Then in the galaga component (which could be mounted in multiple places):
// app/features/games/galaga/index.tsx
export function GalagaComponentWrapper(path) {
return function GalagaComponent() {
const route = useRouter();
console.log(path); // available here..
}
}
Is this what you're suggesting? Or something else? I'm not sure if this is a good solution, and I'd have to come up with something different for RN. Maybe there is also a way to use the app/features/provider
directory, but i'm not sure.
Thank you!
Welp.. actually not sure if this is a good solution. If there is a parameter in the next route, then you wouldn't be able to statically pass the current route in, ie.
next/pages/admin/[adminId]/games/galaga.ts
import GalagaComponentWrapper from 'app/features/games/index.tsx'
// The current route is not a static string, it's now:
export GalagaComponentWrapper('/admin/[adminId]/games/galaga')
I guess then in the component you'd have to get the current route by getting the parameter from useParam('adminId')
and then passing it into that string.. just seems like a complicated hack, now. Thoughts? Sorry to bother!
one alternative is to pass the prop when you're hooking up that component in each frameworks filesystem router
function Page() {
const { pathname } = useRouter() // or useHref()
const isActive = pathname === '/foo'
return <SomeView isActive={isActive} />
}
I would start with that, and if that gets tiresome you can create your own application abstraction for grabbing pathnames
Ah ok, although it still feels like a hack and very verbose.. I think this solution would work for now, will report back. Thanks for the idea!
Ok, I think the best way to do this is by using the React Context API. For next, in provider/navigation/index-web.tsx
we can add the route context on the root:
export const RouteContext = React.createContext<{
path: string
asPath: string
}>({ path: '', asPath: '' })
export const NavigationProvider = ({
children,
}: {
children: React.ReactElement
}) => {
const router = useRouter()
return (
<RouteContext.Provider
value={{ path: router.pathname, asPath: router.asPath }}
>
{children}
</RouteContext.Provider>
)
}
And then in a child component:
function AnyChildComponent = () => {
const TaskLayout: NextPage = ({ children, Component }) => {
const { path, asPath } = useContext(RouteContext)
}
I will have to revisit when we shift to working on mobile to see if there are bottlenecks that occur at that stage in regards to the idea of relying on route paths.
For context, this has all been discussed at these places too, which might help: #10 #62 #45
I still don't have a fantastic answer, other than the following.
// admin-context.tsx
import { createContext } from 'react'
export const IsAdminContext = createContext(false)
export const IsAdminProvider = ({ children }) => {
return <IsAdminContext.Provider value={true}>{children}</IsAdminContext.Provider>
}
Now we just need a hook to know if it's an admin screen. On Native, we'll use the context, and on Web, the URL.
// use-is-admin.ts
import { IsAdminContext } from './admin-context'
export const useIsAdmin = () => useContext(IsAdminContext)
// use-is-admin.web.ts
import { useRouter } from 'next/router'
export const useIsAdmin = () => useRouter().pathname.includes('/admin')
As a final step, you'd wrap certain screens on native with IsAdminProvider
, so that they know what kind of app they're in.
Alternatively, in <NavigationContainer />
, you could add a redirect that changes /admin/user
to something like /user?isAdmin=true
.
Then, your use-is-admin
on native could more simply be like this:
// use-is-admin.ts
import { createParam } from 'solito'
const { useParam } = createParam<{ isAdmin?: string }>()
export const useIsAdmin = () => {
const [isAdmin] = useParam('isAdmin')
return Boolean(isAdmin)
}
I am doing the following which is super hacky, but works. I am using this for my navigation bar to figure out if the route is active. This doesn't actually solve the core problem, but solves my problem which is just knowing if my top level nav link is active.
const linking = {
config: {
screens: {
Settings: '/settings',
}
}
}
useActiveSlug.ts
import { useMemo } from 'react'
import { O, pipe, RA } from '@steepleinc/fp'
import { useNavigationState } from '@react-navigation/native'
export function useActiveSlug() {
const navigationState = useNavigationState((state) => state)
const history = pipe(
navigationState,
O.fromNullable,
O.map((x) => pipe(x.history, O.fromNullable)),
O.flatten,
O.getOrElse((): ReadonlyArray<unknown> => []),
)
return useMemo(
() =>
pipe(
history as ReadonlyArray<{ key: string }>,
RA.last,
O.map((x) => pipe(x.key.split('-'), RA.head)),
O.flatten,
O.getOrElse(() => ''),
(x) => x.toLowerCase(),
),
[history],
)
}
useActiveSlug.web.ts
import { useMemo } from 'react'
import { capitalizeFirstLetter, O, pipe, RA, Str } from '@steepleinc/fp'
import { useRouter } from 'next/router'
export function useActiveSlug() {
const { pathname } = useRouter()
return useMemo(
() =>
pipe(
pathname,
Str.split('/'),
RA.lookup(1),
O.getOrElse(() => ''),
capitalizeFirstLetter,
),
[pathname],
)
}
I've seen some discussions in the past issues but still have not landed on anything concrete. I have a navigation list and would like to mark one as active based on its presence in the url route. I'm not sure how I can do that without knowing the current route. Any help here? Thanks.