thegreatjedi
thegreatjedi

Reputation: 3028

Runtime error when trying to implement client-only routes in GatsbyJS

I'm trying to follow the basic examples on the Gatsby website. These are my codes so far:

// pages/app.js
import { Router } from '@reach/router';
import { Link } from 'gatsby';
import * as React from 'react';

import Browse from '../components/Browse';
import Upload from '../components/Upload';

const AppPage = () => (
  <>
    <nav>
      <Link to "/app/browse">Browse Link</Link>
      <Link to "/app/upload">Upload Link</Link>
    </nav>
    <main>
      <Router basepath="/app">
        <Browse path="/browse">Browse</Browse>
        <Upload path="/upload">Upload</Upload>
      </Router>
    </main>
  </>
);

export default AppPage;

// components/Browse.tsx
import * as React from 'react';

import { RouteComponentProps } from '@reach/router';

type BrowseProps = {
  children?: string,
} & RouteComponentProps;

const Browse = ({ children }: BrowseProps) => <div>{children}</div>;

Browse.defaultProps = {
  children: '',
};

export default Browse;

// components/Upload.tsx
// Identical to Browse.tsx, except it's named Upload
type UploadProps {...}

const Upload = ...;

export default Upload;

To help configure client-only paths, I'm using gatsby-plugin-create-client-paths to help automate the process as suggested on the site. It has been added to gatsby-config.js like so:

// gatsby-config.js
plugins: [
  "gatsby-plugin-react-helmet",
  {
    resolve: "gatsby-plugin-create-client-paths",
    options: {
      prefixes: ["/app/*"],
    },
  },
  {
    resolve: "gatsby-plugin-manifest",
    options: {
      icon: "src/images/icon.png",
    },
  },
]

As far as I can tell, that should be all I need to get it working. So I started the development server:

npm run develop

I successfully opened http://localhost:8000/app/ and the two Links are successfully rendered, but when I click on either of them, a runtime error occurred:

Unhandled Runtime Error

One unhandled runtime error found in your files. See the list below to fix it:

Error in function eval in ./node_modules/@gatsbyjs/reach-router/es/index.js:698

Cannot read property 'path' of undefined

./node_modules/@gatsbyjs/reach-router/es/index.js:698
 696 |       return React.Children.map(element.props.children, createRoute(basepath));
  697 |     }
> 698 |     !(element.props.path || element.props.default || element.type === Redirect) ? process.env.NODE_ENV !== "production" ? invariant(false, "<Router>: Children of <Router> must have a `path` or `default` prop, or be a `<Redirect>`. None found on element type `" + element.type + "`") : invariant(false) : void 0;
  699 |
  700 |     !!(element.type === Redirect && (!element.props.from || !element.props.to)) ? process.env.NODE_ENV !== "production" ? invariant(false, "<Redirect from=\"" + element.props.from + "\" to=\"" + element.props.to + "\"/> requires both \"from\" and \"to\" props when inside a <Router>.") : invariant(false) : void 0;
  701 |

What does this mean? Am I missing something? Is it because I'm using the development and not production environment, causing it to fail?

Update: Following the answer by Ferran, I modified my code as follows, refactoring pages/index.js to pages/app/[...].js

// pages/app/[...].js
import { Router } from '@reach/router';
import { Link } from 'gatsby';
import * as React from 'react';

import Browse from '../../components/Browse';
import Upload from '../../components/Upload';

const AppPage = () => (
  <>
    <nav>
      <Link to "/app/browse">Browse Link</Link>
      <Link to "/app/upload">Upload Link</Link>
    </nav>
    <main>
      <Router basepath="/app">
        <Browse path="/browse">Browse</Browse>
        <Upload path="/upload">Upload</Upload>
      </Router>
    </main>
  </>
);

export default AppPage;

All other files remain unchanged. However, the error persists at http://localhost:8000/app/ after clicking a link.

Update: Tried adding a Home route:

// pages/app/[...].js
import { Router } from '@reach/router';
import { Link } from 'gatsby';
import * as React from 'react';

import Browse from '../../components/Browse';
import Home from '../../components/Home';
import Upload from '../../components/Upload';

const AppPage = () => (
  <>
    <nav>
      <Link to "/app/">Home Link</Link>
      <Link to "/app/browse">Browse Link</Link>
      <Link to "/app/upload">Upload Link</Link>
    </nav>
    <main>
      <Router basepath="/app">
        <Browse path="/browse">Browse</Browse>
        <Upload path="/upload">Upload</Upload>
        <Browse path="/">Home</Browse>
      </Router>
    </main>
  </>
);

export default AppPage;

Upon adding this in, the same error immediately triggered at http://localhost:8000/app/. After removing the Home route but not the Link, the page can load again. Clicking Home Link at this point doesn't trigger any error.

Upvotes: 1

Views: 1927

Answers (1)

Ferran Buireu
Ferran Buireu

Reputation: 29320

What seems to be happening is that your paths are not found by Gatsby. I think you only need to move your app.js in src/pages/app/[...].js (renaming the file to [...].js).

You can follow an official Gatsby example in their GitHub. The code from Gatsby's docs suggests:

// src/pages/app/[...].js

import React from "react"
import { Router } from "@reach/router"
import Layout from "../components/Layout"
import Profile from "../components/Profile"
import Details from "../components/Details"
import Login from "../components/Login"
import Default from "../components/Default"
import PrivateRoute from "../components/PrivateRoute"

const App = () => {
  return (
    <Layout>
      <Router basepath="/app">
        <PrivateRoute path="/profile" component={Profile} />
        <PrivateRoute path="/details" component={Details} />
        <Login path="/login" />
        <Default path="/" />
      </Router>
    </Layout>
  )
}
export default App

The [...].js notation is using the File System Route API, which is marking any dynamic segment of the URL after /app (because of the folder structure, app/[...].js).

Upvotes: 1

Related Questions