question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

React Context API

See original GitHub issue

Hi devs,

I’m trying to implement an authentication required route which would ask an AuthContext if the user is logged in and I think I found some limitations with how the Route component works:

import React from "react";
import Route from "found/lib/Route";
import HttpError from "found/lib/HttpError";
import LoadingOverlay from "./LoadingOverlay";
import LinearProgress from "@material-ui/core/LinearProgress";
import { AuthContext } from "./auth-context";

export default class AuthRequiredRoute extends Route {
  render({ Component, props, error, match, resolving }) {
    // Couldn't useContext because hooks are only allowed in function components
    return (
      <AuthContext.Consumer>
        {({ loggedIn }) => {
          if (!loggedIn)
            throw new HttpError(401, "No JWT found in localStorage");

          // This error hasn't been caught by renderError yet, that's why we need to throw it
          if (resolving && error) {
            throw new HttpError(error.status, error);
          }

          if (Component && props) {
            return <Component {...props} />;
          }

          if (match.route.loadingOverlay) {
            return <LoadingOverlay />;
          }

          return <LinearProgress style={{ marginTop: "68px" }} id="cargando" />;
        }}
      </AuthContext.Consumer>
    );
  }
}

The problem with the above approach is

react-dom.development.js:520 Warning: A context consumer was rendered with multiple children, or a child that isn't a function. A context consumer expects a single child that is a function. If you did pass a function, make sure there is no trailing or leading whitespace around it.
warningWithoutStack @ react-dom.development.js:520
updateContextConsumer @ react-dom.development.js:15661
beginWork @ react-dom.development.js:15881
performUnitOfWork @ react-dom.development.js:19745
workLoop @ react-dom.development.js:19786
renderRoot @ react-dom.development.js:19866
performWorkOnRoot @ react-dom.development.js:20811
performWork @ react-dom.development.js:20721
performSyncWork @ react-dom.development.js:20695
requestWork @ react-dom.development.js:20550
scheduleWork @ react-dom.development.js:20357
enqueueSetState @ react-dom.development.js:11475
push.../../node_modules/react/cjs/react.development.js.Component.setState @ react.development.js:336
_callee$ @ createBaseRouter.js:219
tryCatch @ runtime.js:63
invoke @ runtime.js:290
prototype.(anonymous function) @ runtime.js:116
step @ asyncToGenerator.js:21
(anonymous) @ asyncToGenerator.js:32
Promise.then (async)
step @ asyncToGenerator.js:31
(anonymous) @ asyncToGenerator.js:32
Promise.then (async)
step @ asyncToGenerator.js:31
(anonymous) @ asyncToGenerator.js:39
F @ _export.js:43
(anonymous) @ asyncToGenerator.js:18
resolveMatch @ createBaseRouter.js:302
componentDidMount @ createBaseRouter.js:118
commitLifeCycles @ react-dom.development.js:17636
commitAllLifeCycles @ react-dom.development.js:19141
callCallback @ react-dom.development.js:147
invokeGuardedCallbackDev @ react-dom.development.js:196
invokeGuardedCallback @ react-dom.development.js:250
commitRoot @ react-dom.development.js:19360
completeRoot @ react-dom.development.js:20893
performWorkOnRoot @ react-dom.development.js:20816
performWork @ react-dom.development.js:20721
performSyncWork @ react-dom.development.js:20695
requestWork @ react-dom.development.js:20550
scheduleWork @ react-dom.development.js:20357
scheduleRootUpdate @ react-dom.development.js:21061
updateContainerAtExpirationTime @ react-dom.development.js:21087
updateContainer @ react-dom.development.js:21155
push.../../node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render @ react-dom.development.js:21468
(anonymous) @ react-dom.development.js:21620
unbatchedUpdates @ react-dom.development.js:20938
legacyRenderSubtreeIntoContainer @ react-dom.development.js:21616
render @ react-dom.development.js:21691
./src/index.js @ index.js:14
__webpack_require__ @ bootstrap:83
checkDeferredModules @ bootstrap:45
webpackJsonpCallback @ bootstrap:32
(anonymous) @ main.chunk.js:1
react-dom.development.js:520 Warning: A context consumer was rendered with multiple children, or a child that isn't a function. A context consumer expects a single child that is a function. If you did pass a function, make sure there is no trailing or leading whitespace around it.
warningWithoutStack @ react-dom.development.js:520
updateContextConsumer @ react-dom.development.js:15661
beginWork @ react-dom.development.js:15881
performUnitOfWork @ react-dom.development.js:19745
workLoop @ react-dom.development.js:19786
callCallback @ react-dom.development.js:147
invokeGuardedCallbackDev @ react-dom.development.js:196
invokeGuardedCallback @ react-dom.development.js:250
replayUnitOfWork @ react-dom.development.js:18976
renderRoot @ react-dom.development.js:19899
performWorkOnRoot @ react-dom.development.js:20811
performWork @ react-dom.development.js:20721
performSyncWork @ react-dom.development.js:20695
requestWork @ react-dom.development.js:20550
scheduleWork @ react-dom.development.js:20357
enqueueSetState @ react-dom.development.js:11475
push.../../node_modules/react/cjs/react.development.js.Component.setState @ react.development.js:336
_callee$ @ createBaseRouter.js:219
tryCatch @ runtime.js:63
invoke @ runtime.js:290
prototype.(anonymous function) @ runtime.js:116
step @ asyncToGenerator.js:21
(anonymous) @ asyncToGenerator.js:32
Promise.then (async)
step @ asyncToGenerator.js:31
(anonymous) @ asyncToGenerator.js:32
Promise.then (async)
step @ asyncToGenerator.js:31
(anonymous) @ asyncToGenerator.js:39
F @ _export.js:43
(anonymous) @ asyncToGenerator.js:18
resolveMatch @ createBaseRouter.js:302
componentDidMount @ createBaseRouter.js:118
commitLifeCycles @ react-dom.development.js:17636
commitAllLifeCycles @ react-dom.development.js:19141
callCallback @ react-dom.development.js:147
invokeGuardedCallbackDev @ react-dom.development.js:196
invokeGuardedCallback @ react-dom.development.js:250
commitRoot @ react-dom.development.js:19360
completeRoot @ react-dom.development.js:20893
performWorkOnRoot @ react-dom.development.js:20816
performWork @ react-dom.development.js:20721
performSyncWork @ react-dom.development.js:20695
requestWork @ react-dom.development.js:20550
scheduleWork @ react-dom.development.js:20357
scheduleRootUpdate @ react-dom.development.js:21061
updateContainerAtExpirationTime @ react-dom.development.js:21087
updateContainer @ react-dom.development.js:21155
push.../../node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render @ react-dom.development.js:21468
(anonymous) @ react-dom.development.js:21620
unbatchedUpdates @ react-dom.development.js:20938
legacyRenderSubtreeIntoContainer @ react-dom.development.js:21616
render @ react-dom.development.js:21691
./src/index.js @ index.js:14
__webpack_require__ @ bootstrap:83
checkDeferredModules @ bootstrap:45
webpackJsonpCallback @ bootstrap:32
(anonymous) @ main.chunk.js:1
react-dom.development.js:15671 Uncaught TypeError: render is not a function
    at updateContextConsumer (react-dom.development.js:15671)
    at beginWork (react-dom.development.js:15881)
    at performUnitOfWork (react-dom.development.js:19745)
    at workLoop (react-dom.development.js:19786)
    at HTMLUnknownElement.callCallback (react-dom.development.js:147)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:196)
    at invokeGuardedCallback (react-dom.development.js:250)
    at replayUnitOfWork (react-dom.development.js:18976)
    at renderRoot (react-dom.development.js:19899)
    at performWorkOnRoot (react-dom.development.js:20811)

if I’m not wrong, ReactDOM can’t find a render method because the Route is not a React.Component; it’s just a plain class. It’s worth mentioning that I tried all sorts of simpler consumer functions with different formats, in case of leading or trailing space errors but all of them ended up with the same warning and errors.

The next thing I tried is to move the auth logic to a function component wrapper of Component in order to useContext:

import React, { useContext } from "react";
import Route from "found/lib/Route";
import HttpError from "found/lib/HttpError";
import LoadingOverlay from "./LoadingOverlay";
import LinearProgress from "@material-ui/core/LinearProgress";
import { AuthContext } from "./auth-context";

const AuthWrapper = ({ Component, props, error, match, resolving }) => {
  const { loggedIn } = useContext(AuthContext);

  if (!loggedIn) throw new HttpError(401, "No JWT found in localStorage");

  // This error hasn't been caught by renderError yet, that's why we need to throw it
  if (resolving && error) {
    throw new HttpError(error.status, error);
  }

  if (Component && props) {
    return <Component {...props} />;
  }

  if (match.route.loadingOverlay) {
    return <LoadingOverlay />;
  }

  return <LinearProgress style={{ marginTop: "68px" }} id="cargando" />;
};

export default class AuthRequiredRoute extends Route {
  render(routeProps) {
    return <AuthWrapper {...routeProps} />;
  }
}

The problem with this approach is that the HttpError is not caught by the router’s renderError, perhaps because as mentioned in React Error Boundaries docs:

As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree.

Finally, I tried extending my AuthRequiredRoute from React.Component to add an error boundary but that wouldn’t even match the current path.

  1. Is there a way I can share some context with Routes dynamically without having to use a Redux router?

  2. Wouldn’t it be better to make the Route a function component in order to use hooks and other React abilities?

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:17 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
taioncommented, Mar 1, 2019

Yeah, so that exact pattern isn’t quite supported with Found Relay – render needs to return synchronously and can’t return a promise.

So per @hisapy’s example above, we do something like have a wrapping component that handles auth, which then renders the router, and does something like matchContext={{ loggedIn: !!accessToken }} or the equivalent.

Then the route render methods can check context.loggedIn and throw the RedirectException if needed.

1reaction
taioncommented, Feb 22, 2019

You don’t need to deal with that – that’s what the router handles right now.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Context - React
Context provides a way to pass data through the component tree without having to ... API. React.createContext; Context.Provider; Class.contextType; Context.
Read more >
An Introduction To React's Context API - Smashing Magazine
In this article, you will learn how to use React's Context API which allows you to manage global application states in your React...
Read more >
React Context API: What is it and How it works?
The React Context API is a way for a React app to effectively produce global variables that can be passed around. This is...
Read more >
How to Work with the React Context API - Toptal
React's context allows you to share information to any component, by storing it in a central place and allowing access to any component...
Read more >
React Context API: A deep dive with examples - LogRocket Blog
The new React Context API, introduced with React v.16.3, allows us to pass data through our component trees, giving our components the ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found