[Solved] useLocation must be used within a LocationProvider?

I need to listen for routing changes outside the Routes tag

Not sure how to do this… The docs don’t work…

Can you give an example why you need to listen somewhere else?

Or what the UX is or you want to provide?

Maybe there’s another way to solve the problem - if we better understand the problem and not just jump to a solution.

This all is a waste of reading – LocationProvider works fine even when outside the <Routes /> tag

Click to expand

Good Morning @dthyresson

I am using Material-UI and my App.tsx looks like this, in concordance with their examples:

  return (
    <FatalErrorBoundary page={FatalErrorPage}>
      <AuthProvider client={supabaseClient} type="supabase">
        <RedwoodApolloProvider useAuth={useAuth}>
            <div className={classes.flexGrow}>
              <Helmet title={helmet.title} meta={helmet.meta} />
              <AppBar position="static">
                <Toolbar>
                  <NavMenu />
                  <Box className={classes.flexGrow}>
                    <Typography variant="h5" className={classes.appBarTitle} onClick={clickHome}>
                      Vaccess, by Vaxxifi
                    </Typography>
                  </Box>
                  <AvatarMenu />
                  <LogInOutButton logOutOptions={{}} />
                </Toolbar>
              </AppBar>
            </div>
            <Routes />
            <BackdropOne />
        </RedwoodApolloProvider>
      </AuthProvider>
    </FatalErrorBoundary>
  )

The Routes slot is outside the AppBar which is always at the top of the screen

The LoginOutButton only shows when isAuthenticated returns true – that works well

import { Button } from '@material-ui/core'
import { navigate } from '@redwoodjs/router'
import { useAuth } from '@redwoodjs/auth'
import { useStoreState } from 'pullstate'
import { RoutableInfo } from 'src/states/routable'
export const LogInOutButton = ({ logOutOptions }) => {
  const duringRegistration = useStoreState(RoutableInfo, (s) => s.duringRegistration)
  const { logOut, isAuthenticated } = useAuth()
  const doLogout = async () => {
    await logOut(logOutOptions)
    navigate('/')
  }
  const showLoginOutButton = () =>  isAuthenticated
  ? (
      <Button color="inherit" onClick={doLogout} >
          Log out
      </Button>
  )
  : null
  return !duringRegistration && showLoginOutButton()
}

The LoginOutButton is to be hidden on certain routes, to emulate (for our staff) the experience that users (who can’t login) will see.

I would have used: useLocation – but it won’t run because it’s not within a LocationProvider (it complains)

For now, I worked around this by saving the routePath externally in a state engine and updating same as each page is loading. Then I can follow that state to make my realtime changes based on Route.

import { navigate, routes, Redirect } from '@redwoodjs/router'
import PathAnnouncer from 'src/components/PathAnnouncer'
import AttendeeRegistration from 'src/components/AttendeeRegistration'
import AttendeeEnsureAttendeeCell from 'src/cells/AttendeeEnsureAttendeeCell'
import useSharedClasses from 'src/hooks/shared-styles'
const AttendeeRegistrationPage = ({ eventId }) => {
  const classes = useSharedClasses()
  const [input, setInput] = React.useState<any>({})
  const [attendee, setAttendee] = React.useState<any>(null)
  switch (true) {
    case !!attendee?.id && !!input.phone: {
      console.debug(`AttendeeRegistrationPage ~ case !!attendee?.id && !!phone`)
      return <Redirect to={routes.attendee({ attendeeId: attendee.id })} />
    }
    case !attendee?.id && !!input.phone: {
      console.debug(`AttendeeRegistrationPage ~ case !attendee?.id && !!phone`)
      return <AttendeeEnsureAttendeeCell eventId={eventId} input={input} exfiltrate={setAttendee} />
    }
    default: {
      console.debug(`AttendeeRegistrationPage ~ default`)
      return (
        <>
          <PathAnnouncer />
          <AttendeeRegistration eventId={eventId} exfiltrate={setInput} />
        </>
      )
    }
  }
}
export default AttendeeRegistrationPage

To DRY it out I made a component to do the work

import { useLocation } from '@redwoodjs/router'
import { setRoutePath } from 'src/states/routable'
const PathAnnouncer = () => {
  const { pathname } = useLocation()
  setRoutePath(pathname)
  return null;
}
export default PathAnnouncer

(I also hide/show the Nav and Avatar menus off that same bit of external state)

This doesn’t feel like the Redwood way, so I know I’m missing something…

If there is no Redwood way in this case, I’d love something like this (to maintain the encapsulation of the router) :

    const appLocationListener = (ctx: LocationContext) => console.debug;

    <Routes changeListener={appLocationListener} />

^^^ Is that the goal?

yes, that was the most easily described goal

Also, in addition, the Nav (hamburger) and Account (profile) menus are hidden as well

if all this markup code is in a Layout used on a Set, can’t that Layout use useLocation() and then determine the path and selectively present the controls as needed?

1 Like

Well now I don’t know what I must have tried first, I just wrapped the AppBar with a LocationProvider - and the NavMenu accepted the useLocation hook cleanly – I’m must be going crazy… (Luckily, it’s a short trip !)

Ok, this was long-winded waste of time !! (apologies @dthyresson to take up valuable minutes)

Thanks!
Al;