2020-07-13

Private routes with Kotlin JS and React Router

For example to hide routes behind a login

In order to implement login functionality in a Kotlin React app, I used React Context API to save an optional instance of Login. This context can be used to create a wrap-around component that checks whether a given route can be accessed or not. If not allowed, the request is redirected to the public /login route.

The context class has to provide a way to get the current login data and functionality to logout or login. The state resides in the main component and is accessed through the onChange callback. The component1 function can be used to destructure the useContext result when only read access is needed.

class LoginContextData(login: Login?, private val onChange: (Login?) -> Unit) {
    var login: Login? = login
        private set
    fun login(potentialLogin: Login) {
        login = potentialLogin
        onChange(login)
    }
    fun logout() {
        login = null
        onChange(login)
    }
    operator fun component1() = login
}
// can be global
val LoginContext = createContext(LoginContextData(null) { })

The context is used like


val loginState = useState(null as Login?)
val (login, setLogin) = loginState

LoginContext.Provider(LoginContextData(login) { newLogin ->
    setLogin(newLogin)
}) {
// render your tree here
}

Just like React Router provides the route function, we can write a function that does an if-else on the context and either calls the known route function or gives a redirect:


fun  RBuilder.privateRoute(
    path: String,
    exact: Boolean = false,
    strict: Boolean = false,
    children: RBuilder.(RouteResultProps<*>) -> ReactElement?
): ReactElement {
    val (login) = useContext(LoginContext)

    return route(path, exact, strict) { routerProps: RouteResultProps<*> ->
        if(login != null) {
            children(routerProps)
        } else {
            redirect(from = path, to = "/login")
        }
    }
}

The callsite can just use privateRoute instead or route and done. The login route remains public.

The context can be used to decide whether a navigation item should be rendered or not as well.