Giter VIP home page Giter VIP logo

navi's People

Contributors

alicanc avatar ammgws avatar azu avatar barontommy avatar benjamingr avatar brekk avatar cevr avatar coryhouse avatar desmap avatar jamesknelson avatar janaagaard75 avatar kmkr avatar labidiaymen avatar lookfirst avatar mattiassundling avatar nathanebel avatar nickfoden avatar nickjanssen avatar rix1 avatar rtivital avatar shamsup avatar strass avatar tatchi avatar tony avatar valpinkman avatar vxna avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

navi's Issues

Questions: Modules and :id/edit

Hi James,

I'm trying to migrate a "large" react application which has modules with it's own routes from react-router v4 to junctions.

I have two questions:
Modules
I currently have an Auth module which has all components like /sign-in and /register. These are "root" routes, but I don't really want them in my root junction. As I find in the documentation, this isn't really possible with junctions because it only has relative paths, and I should OR have it like /auth/sign-in with a auth-junction OR have the routes in the main junction. Is that correct?

:id/edit
I have three routes:
/assets
/assets/:id
/assets/:id/edit
How would I create these in junctions?
Currently I got this far:

const mainJunction = createJunction({
  Auth: {
    next: AuthScreen.junction,
  },
  Assets: {
    next: AssetsScreen.junction,
  }
});

// Junction in AssetsScreen
const junction = createJunction({
  Asset: {
    path: '/:id',
    paramTypes: {
      id: { required: true },
    },
  // idea for /edit
  AssetEdit: {
    path: '/:id/edit',
    paramTypes: {
      id: { required: true },
    },
});

When I do it like this, I get an error that the routes match.
"Two branches have paths "undefined" and "undefined" that match the same URLs!"
(the undefined is caused by trying to grab .path on this line: https://github.com/jamesknelson/junctions/blob/master/source/createJunction.js#L106)
Is that correct or is that a bug? (I can send a PR if it's a bug)

I also tried /:id(/edit) or /:id/(edit) but none work. Do you think it's possible to have routes like this?

I'm also curious if you have any plans for Junctions. If I'm going to use it I could help you maintain it if you want (and write some more docs).

I also made a Redirect component and a withHistory decorator for react-junctions based on the context. This made it easier to migrate some components from react-router. If you want I could make a PR for them.

Thanks for the library, and looking forward to your reply :)

Take the Router Challenge

The Router Challenge aims to be to Routers what TodoMVC is to MV* frameworks. It offers the same Music Catalog application built in React using different Routers. For it to be successful I need the help of Router writers like you. Will you take the Router Challenge and implement the Music Catalog application using Junctions, please?

Nested single junctions cannot be located

Example:

For a nested Junction tree with 3 levels:

{
  a: { next: createJunction({
    b: { next: createJunction({
      c: { next: createJunction({ ... }) }
    }) }
  }) }
}

Locations can be converted to Routes, but running locate requires that { main: route } be passed in as an argument, instead of just route.

Build errors due to case sensitivity

When running npm start in the site/ directory, babel emits the following errors, and the site fails to load.

ERROR in ../~/babel-loader/lib!../~/babel-loader/lib!../docs/SITE.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./glossary.md in /junctions/docs
 @ ../~/babel-loader/lib!../~/babel-loader/lib!../docs/SITE.js 6:107-131

ERROR in ../~/babel-loader/lib!../~/babel-loader/lib!../docs/introduction/SITE.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./motivation.md in /junctions/docs/introduction
 @ ../~/babel-loader/lib!../~/babel-loader/lib!../docs/introduction/SITE.js 5:46-72

ERROR in ../~/babel-loader/lib!../~/babel-loader/lib!../docs/basics/SITE.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./locations.md in /junctions/docs/basics
 @ ../~/babel-loader/lib!../~/babel-loader/lib!../docs/basics/SITE.js 5:10-35

ERROR in ../~/babel-loader/lib!../~/babel-loader/lib!../docs/basics/SITE.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./routes.md in /junctions/docs/basics
 @ ../~/babel-loader/lib!../~/babel-loader/lib!../docs/basics/SITE.js 5:37-59

ERROR in ../~/babel-loader/lib!../~/babel-loader/lib!../docs/basics/SITE.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./junctions.md in /junctions/docs/basics
 @ ../~/babel-loader/lib!../~/babel-loader/lib!../docs/basics/SITE.js 5:61-86

ERROR in ../~/babel-loader/lib!../~/babel-loader/lib!../docs/basics/SITE.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./links.md in /junctions/docs/basics
 @ ../~/babel-loader/lib!../~/babel-loader/lib!../docs/basics/SITE.js 5:172-193

This is due to the case difference between the SITE.js file lists and the actual .md files.

SITE.js Actual filename
./glossary.md ./Glossary.md
./motivation.md ./Motivation.md
./locations.md ./Locations.md
./routes.md ./Routes.md
./junctions.md ./Junctions.md
./links.md ./Links.md

`navi-scripts build` fails with react hooks

When running navi-scripts build using hooks it fails with the following error:

Overview

[ohshit] An error occured while building your app
Minified React error #298; visit https://reactjs.org/docs/error-decoder.html?invariant=298 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
Invariant Violation: Minified React error #298; visit https://reactjs.org/docs/error-decoder.html?invariant=298 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

The unminified error message states:

Hooks can only be called inside the body of a function component.

I know hooks are still in alpha but it might be worth investigating this issue before they release.

Steps to reproduce

  1. Clone the navi repo
  2. cd into navi/examples/create-react-blog
  3. Update react and react-scripts 16.7.0-alpha.2
  4. Replace the App component in App.js to use a hook (see below)
  5. Run yarn build for the example

Hook usage in App.js

export function App(props) {
  const [state] = useState(true)
  return (
    <Nav.Provider navigation={props.navigation}>
      <Nav.Loading>
        {isLoading => (
          <>
            <AppBusyIndicator show={isLoading} />
            <nav>
              <Nav.Link href="/">Home</Nav.Link>
              {state ? 'state is true' : 'state is false'}
            </nav>
            <main>
              <Nav.NotFoundBoundary render={renderNotFound}>
                <Nav.Route />
              </Nav.NotFoundBoundary>
            </main>
          </>
        )}
      </Nav.Loading>
    </Nav.Provider>
  )
}

Pass properties to <a> link

It would be nice to be able to pass all unrecognized properties form 'NavLink' to generated <a> tag, like:

<NavLink href="/" data-track="navbar" role="button">

and having data-track and role passed to generated anchor:

<a href="/" data-track="navbar" role="button">

Vue Example

Navi doesn't have any ties to React within the navi package itself. Also, its design is based on the assumption that your View uses components -- but not necessarily React components.

So it should totally be possible to use Navi with Vue.

Need to figure out how to do this and add an example. Would really appreciate help from anyone with some Vue experience.

Changelog

Hi,

Great work on navi but it would be really helpful to have a changelog to track what is fixed, changed, etc.

Static rendering fails with Error: Not implemented: HTMLCanvasElement.prototype.getContext

Hey there, navi rocks! ^^
My app is dependent on react-native-vector-icons, which seems to fail navi-scripts's build process.

The full error:

Using create-react-app renderer...
Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
    at module.exports (/Users/nickjanssen/Documents/docs/lk-web-app/node_modules/navi-scripts/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
    at HTMLCanvasElementImpl.getContext (/Users/nickjanssen/Documents/docs/lk-web-app/node_modules/navi-scripts/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)
    at HTMLCanvasElement.getContext (/Users/nickjanssen/Documents/docs/lk-web-app/node_modules/navi-scripts/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:41:47)

As the error indicated, I tried installing canvas in the root directory as well as in node_modules/navi-scripts but it doesn't have an effect. Any idea what I could be doing wrong?

Here's a repo with the reproducable error: https://github.com/nickjanssen/navi-test

Mobile Site

The documentation site looks pretty bad on mobile. It would be nice to make the following changes:

  • Move the side menu into a drop-down menu above the site content on narrow screens
  • Make the example code wrap when viewing on narrow screens

Also, the font sizes could probably use some adjustment.

Same link creates duplicates in location history

Hey,

When clicking same link, it creates duplicates in history so when you click back, you get to the same page.

Tested on basic example.
Click home -> about -> about -> about -> back. You would expect to go back to home, but you will stay on the same page. Default browser <a> tag doesn't create same page history duplicates.

This is a well know bug in history package (since v4) which was reported multiple times in react-router (RR#5500, check number of linked issues) and in history (H#507).

React-router guys tell this should be fixed in history while history guy one day tells it should be fixed in react-router, next day says it should fixed in history and wants a pr, next day closes pr with no comments (H#570).

Maybe at least one router will have links that work same way as browser ones.

Converter not returning the correct route

I'm trying to integrate Junctions into a fresh code base where I'm already using React Router. I'm replicating the raw example and stripped out all the react-router components to try and get this to work. One tidbit to know is that all of my urls in the entire app need to be prefixed with /wp to get this to work alongside a legacy codebase.

My issue is that when I go to /wp/about or /wp/organizations I just see the word "Home". I dug into it and saw that the route variable in AppScreen returns the same object at every one of those locations instead of the corresponding next junction that it should return.

Here's my code:

import createBrowserHistory from 'history/createBrowserHistory'
import { createJunction, createConverter, locationsEqual } from 'junctions'

AppScreen.junction = createJunction({
  wp: {
    next: createJunction({
      about: {},
      help: {},
      organizations: {},
    }),
  },
});
function AppScreen({ route, locate, history }) {
  const junction = AppScreen.junction;
  let content;
  switch (route ? route.key : route) {
    case 'wp':
      content = <h2>Home</h2>;
      break;

    case 'about':
      content = <h2>About</h2>;
      break;

    case 'organizations':
      content = <h2>Organizations</h2>;
      break;

    case null:
      content = <h2>Home</h2>;
      break;

    // An undefined route indicates that the converter didn't know how
    // to handle the received location
    case undefined:
      content = <h2>Not Found</h2>;
      break;
  }
  return (
    <DefaultLayout
      content={content}
      history={history}
      locate={locate}
      route={route}
    />
  )
}

class JunctionsRouter extends Component {

  componentWillMount() {
    // As an application's Junction doesn't usually change at runtime,
    // we only ever need a single application-wide converter
    this.converter = createConverter(AppScreen.junction)

    // Handle the application's initial location
    this.handleLocationChange(this.props.history.location)
  }

  componentDidMount() {
    this.unlisten = this.props.history.listen(this.handleLocationChange.bind(this))
  }

  componentWillUnmount() {
    if (this.unlisten) {
      this.unlisten()
      this.unlisten = null
    }
  }

  handleLocationChange(location) {
    // Convert the Location object emitted by our history into a Route
    // through our application Junction
    const route = this.converter.route(location)
    // The Route produced by the converter may contain informatino which
    // the received Location object doesn't, due to default parameters
    // and default branches. If this is the case, create a new Location
    // object containing the new information, and redirect to it
    const canonicalLocation = route && this.converter.locate(route)
    if (route && !locationsEqual(location, canonicalLocation)) {
      this.props.history.replace(canonicalLocation)
    }

    // Add the route to component state to trigger a re-render
    this.setState({ route })
  }

  render() {
    return (
      // Screen Components always take `route` and `locate` props, so they
      // can decide what to render, and create Locations for any Links
      // and redirects.
      //
      // In this example, we also pass a history. Usually, this would be
      // passed via context, using a <Router> or <HistoryContext> component.
      <AppScreen
        route={this.state.route}
        locate={this.converter.locate}
        history={this.props.history}
      />
    )
  }

}

const history = createBrowserHistory();
export default render((
   <JunctionsRouter
      history={history}
      junction={AppScreen.junction}
      render={AppScreen}
    />
), document.getElementById('root'));

Unable to use Junctions with Typescript 2.9

I really like the idea and simplicity of this library, however I am unable to use it in a TypeScript 2.9 project with strict mode turned on.

Despite setting the 'rootDir' and 'excludes' settings in my TS Config to exclude node_modules, the typescript compiler still tries to compile the src files shipped in the npm module, which is failing with a ton of errors (a small subset pasted below).

(I've also tried explicitly setting 'excludes' in my webpack config)

ERROR in /Users/russell/code/testapp/node_modules/junctions/src/Route.ts
[tsl] ERROR in /Users/russell/code/testapp/node_modules/junctions/src/Route.ts(16,18)
      TS2430: Interface 'JunctionRoute<J>' incorrectly extends interface 'Route'.
  Types of property '0' are incompatible.
    Type 'Junction<J>' is not assignable to type 'RouteSegment'.
      Type 'Junction<J>' is not assignable to type 'Junction<JunctionTemplate<any, any, any>>'.
        Types of property 'activeRoute' are incompatible.
          Type 'JunctionDescendentSegments<J>' is not assignable to type 'JunctionDescendentSegments<JunctionTemplate<any, any, any>>'.
            Type 'JunctionTemplate<any, any, any>' is not assignable to type 'J'.

ERROR in /Users/russell/code/testapp/node_modules/junctions/src/Route.ts
[tsl] ERROR in /Users/russell/code/testapp/node_modules/junctions/src/Route.ts(17,5)
      TS2412: Property '0' of type 'Junction<J>' is not assignable to numeric index type 'RouteSegment'.

ERROR in /Users/russell/code/testapp/node_modules/junctions/src/Route.ts
[tsl] ERROR in /Users/russell/code/testapp/node_modules/junctions/src/Route.ts(152,5)
      TS2412: Property '0' of type '{ [K in keyof J["children"]]: J["children"][K] extends AsyncObjectContainer<infer T> ? { Junction...' is not assignable to numeric index type 'RouteSegment'.

ERROR in /Users/russell/code/testapp/node_modules/junctions/src/PageTemplate.ts
[tsl] ERROR in /Users/russell/code/testapp/node_modules/junctions/src/PageTemplate.ts(3,1)
      TS6192: All imports in import declaration are unused.

ERROR in /Users/russell/code/testapp/node_modules/junctions/src/PageTemplate.ts
[tsl] ERROR in /Users/russell/code/testapp/node_modules/junctions/src/PageTemplate.ts(4,10)
      TS6133: 'Page' is declared but its value is never read.

My feeling is it would be better to ship the npm package without the source files (just the dist folder with the .d.ts typings), then maybe ship a seperate -src package for peeps that want to compile the source themselves?

Part of sire fails in Firefox

The console throws an error and I am unable to route. The address bar changes but the content remains the same.

I start at

https://junctions.js.org/docs/introduction/do-i-need-a-router

and click a link and the address bar changes to

https://junctions.js.org/docs/introduction/locations-routes-and-maps

but the content never budges.

I get this stacktrace on the bundled js

TypeError: this.refs.html.querySelectorAll(...).forEach is not a function
...

This link works perfectly: https://junctions.js.org/examples/Raw

I am using Firefox 47 on an EOLed Ubuntu. I hope I am helpful enough. I wish your project the best!

Dynamically pushing to a new route

Is it possible to dynamically switch to a new route? In my case, I'm doing an API request when user clicks a button. If the request was successful, they are routed to a new page.

React Class Component example?

Great library! I actually prefer having all the pages is JSON-ish format, plus you get the benefits of react-loadable built in which is perfect.

I was trying this out and wanted to see if there's an example of a route which uses a react class component instead of a function.

So instead of

// pages/reference.js
import * as React from 'react'

export default function Reference() {
  return (
    <div>
      <h2>Reference</h2>
    </div>
  )
}

What would be the createPage syntax for this:

import React from 'react'

export default class Reference extends React.Component {
  render() {
    return <h2>Smart Page!</h2>;
  }
}

Thank you!

Static page generation for website

Website is currently a single-page app, but there really should be HTML pages generated for each of its pages.

In order to do this, the processing on SITE.js files currently handled by the sitepack loader will need to be handled by node itself.

The general idea is:

  • Load root SITE.js and build root junction, like in current website
  • Walk this junction, creating a list of locations to render
  • For each of these locations, create an in-memory history and use it to render the application to a string
  • Will also need to take the route's title and add it to the generated page's <title> tag.

Slashes at end of URLs

Is there a way to avoid the "/" characters getting added to my URLs?

In this sandbox, click on the /page1 or /page2 links and you will see the URL change to /page1/ or /page2/. Happens in my own app, too.

https://codesandbox.io/s/n7v5o7j310

All the code:

import React from "react";
import ReactDOM from "react-dom";
import { mount, route } from "navi";
import { Router, Link } from "react-navi";

import "./styles.css";

const routes = mount({
  "/": route({
    view: () => (
      <p>
        Home. Go to <Link href="/page1">page 1</Link> or{" "}
        <Link href="/page2">page 2</Link>.
      </p>
    )
  }),
  "/page1": route({
    view: () => (
      <p>
        Page 1. Go <Link href="/">home</Link> or to{" "}
        <Link href="/page2">page 2</Link>.
      </p>
    )
  }),
  "/page2": route({
    view: () => (
      <p>
        Page 2. Go <Link href="/">home</Link> or to{" "}
        <Link href="/page1">page 1</Link>.
      </p>
    )
  })
});

function App() {
  return (
    <div className="App">
      <Router routes={routes} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Disable prefetch globally

Is it possible to disable prefetching everywhere without putting prefetch={false} on all my links?

How to get navigation in getContent?

Hi! I was impressed by your presentation in Nodefest in this year, and now am trying out react-navi right now to make my new app.

I just would like to know how to go to another path in getContent. The purpose of this is that I would like to change the rendering according to user login status like below. Can I get navigation from props or something? Thanks for your help.

export const RootComponent = Navi.createPage({
  title: 'index',
  getContent: async env => {
    const currentUser = await getCurrentUser();

    if (!currentUser) {
      // Wanna go to `/login` here
    }

    return component(currentUser)
  }
})

const component = (user: User) => {
  return (
    // ...
  )
}

Use with electron (HashRouter)

Hi, is there a way to use the navi router with electron?
React-router has the HashRouter, which can be used, navi doesn't seem to have that.

Preload imported assets

We're using navi in our React-app together with server side rendering. We're working on page speeds and one of our requirements to improve speed is to preload assets such as CSS files and JS files.

To do that, we need to know the set of dynamic modules where rendered in a request. For example, a request to '/' renders two dynamically imported JavaScript bundles and one dynamically imported CSS bundle. Do you have a suggestion for how we can get that information from navi?

We used react-loadable prior to navi, but changed to navi as it solved most of our requirements better with less code so we'd love to keep navi. However, this particular requirement is unclear how to solve using navi. Here's a link to the corresponding react-loadable documentation.

What to do if react app is not in root url?

Hello!
I have app with url site.com/some/path/before/AppItself
How to work with url like that?
I tried to specify path in createJunctions as window.basePath + route but I receive message like

Uncaught Error: Branch "branchName" uses a pattern with "anything", but another pattern or alias already uses this identifier.

Thanks

Fallback for map() matcher

Currently, if the map() matcher isn't able to match any of its provided paths, it yields a NotFound error segment.

Ideally, it'd be possible to chain multiple map() matchers, so that not all URLs under a single path must be defined in a single map(). This should be easy enough to do by using the map()'s child matcher as a fallback (if it exists).

This would be a good first PR, as it's a small change with reasonably simple tests. If you'd like to give it a try, please leave a comment.

Cannot render redirection to async route from NavNotFoundErrorBoundary

I have web app which instead of rendering "not found" page redirects to home page.

Example code: https://frontarm.com/demoboard/?id=1f95147d-e713-4d80-a22a-af880c6810f2

I'm trying to do it this way:

function renderNotFound() {
  return (
    <NavHistory>{(history) => history.push('/about')}</NavHistory>
  )
} 

// [...]
<NavNotFoundBoundary render={renderNotFound}>
  <NavContent />
</NavNotFoundBoundary>

and it works correctly when my page's getContent() returns content immediately, but when I try to redirect to async page

'/about': Navi.createPage({
      title: 'The createSwitch() function',
      getContent: () => import('./about.mdx'),
    }),

it fails with following errors in console:

Uncaught Error: URL not found: /abc/
    at createNotFoundSegment (VM168 navi.js:322)
    at class_1.NodeMatcher.getResult (VM168 navi.js:363)
    at class_1.SwitchMatcher.execute (VM168 navi.js:872)
    at class_1.NodeMatcher.getResult (VM168 navi.js:356)
    at RouteObservable.refresh (VM168 navi.js:1336)
    at RouteObservable.handleChange (VM168 navi.js:1321)
    at RouteObservable.subscribe (VM168 navi.js:1359)
    at CurrentRouteObservable.handleURLChange (VM168 navi.js:1256)
    at CurrentRouteObservable.refresh (VM168 navi.js:1180)
    at new CurrentRouteObservable (VM168 navi.js:1164)
The above error occurred in the <InnerNavContent> component:
    in InnerNavContent (created by Context.Consumer)
    in NavContent (created by Context.Consumer)
    in InnerNotFoundBoundary (created by Context.Consumer)
    in ErrorBoundary (created by Context.Consumer)
    in main (created by Context.Consumer)
    in div (created by Context.Consumer)
    in NavLoading (created by App)
    in Suspense (created by NavProvider)
    in NavProvider (created by App)
    in App

React will try to recreate this component tree from scratch using the error boundary you provided, InnerNotFoundBoundary.

Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.

Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
    at invariant (VM167 react-dom.development.js:49)
    at scheduleWork (VM167 react-dom.development.js:18530)
    at Object.enqueueSetState (VM167 react-dom.development.js:12437)
    at NavProvider.Component.setState (VM166 react.development.js:460)
    at Object.NavProvider._this.handleNavigationSnapshot [as next] (VM173 react-navi.js:335)
    at MapObserver.next (VM168 navi.js:1963)
    at CurrentRouteObservable.handleURLChange (VM168 navi.js:1226)
    at VM168 navi.js:1163
    at listener (VM171 history.js:878)
    at VM171 history.js:897

The above error occurred in the <Context.Consumer> component:
    in NavHistory (created by InnerNotFoundBoundary)
    in InnerNotFoundBoundary (created by Context.Consumer)
    in ErrorBoundary (created by Context.Consumer)
    in main (created by Context.Consumer)
    in div (created by Context.Consumer)
    in NavLoading (created by App)
    in Suspense (created by NavProvider)
    in NavProvider (created by App)
    in App

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

Uncaught (in promise) Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
    at invariant (VM167 react-dom.development.js:49)
    at scheduleWork (VM167 react-dom.development.js:18530)
    at Object.enqueueSetState (VM167 react-dom.development.js:12437)
    at NavProvider.Component.setState (VM166 react.development.js:460)
    at Object.NavProvider._this.handleNavigationSnapshot [as next] (VM173 react-navi.js:335)
    at MapObserver.next (VM168 navi.js:1963)
    at CurrentRouteObservable.handleURLChange (VM168 navi.js:1226)
    at VM168 navi.js:1163
    at listener (VM171 history.js:878)
    at VM171 history.js:897

Refactor site maps

The resolveSiteMap() function is used by navi-scripts to find a map of all routes during static builds, but in my opinion, it's currently the weakest part of Navi's design. It's heavy, complicated code that isn't frequently used, and it's getting in the way of #60 .

Before 1.0, I want to refactor this so that instead of outputting an object mapping URLs to Route objects, it just outputs a list of URLs (which can then be resolved separately).

The new map function should work by setting the request's method property to Symbol(map) instead of the standard GET method. Then, matchers themselves will be tasked with creating a list of all of their possible children, probably putting them on a new "map" Chunk.

For custom matchers that don't understand the "map" method, they should just treat it as a GET and return a standard route. This shouldn't be a problem, as they'll still pass the request through to their children, which can add more map segments. To facilitate this, it may make sense to remove "GET" as the default, and replace it with undefined?

Since map is a special method, map configuration can be added to the request body, including predicate/url parameters extraction.

react tutorial doesn't work at step 1

# Install the create-react-app command-line tool
npm install -g create-react-app

# Create a new project under the `junctions-tutorial` directory
create-react-app junctions-tutorial
cd junctions-tutorial

# Install junctions and react-junctions
npm install --save junctions react-junctions

# Start a development server at http://localhost:3000
npm run start

you get 'command not found'. Obviously, if you don't add junctions or react-junctions, create-react-app works fine. Noticed that react-junctions hasn't been update in a long while so I'm guessing it just lacks support for the latest react/create-react-app. Should fire off peer dependency warnings IMO to let people know its for react 15 (or whatever it last worked on)

Dev Tools

Navi needs some dev tools. As a result of Navi's architecture, they should be able to support time travel and give you a window into what's going on under the hood.

I'd really appreciate any help putting them together. I'd be happy to contribute to and link to a quality project that's maintained by someone else.

How Navi Works (the 2 minute version)

When you pass Navi a new URL or Context, it will map them to an array of simple objects called Chunks, which are then reduced into your Route object with a Redux-like configurable reducer.

The array of chunks corresponding to a single URL/context grows over time. Each Matcher (i.e. mount(), route(), withView(), etc.) maps a URL/context pair to an arbitrary number of chunks to the route during each iteration.

There's one special type of chunk: "busy". When Navi encounters a busy chunk (which will contain a promise), it'll wait until that promise resolves or is rejected, and then it'll recompute the list of chunks from scratch (thus allowing it to grow over time). Once there are no busy chunks, the route is considered "steady".

Dev Tools

Due to Navi's architecture, it should be possible to visualize how each Route is computed in response to a Request or Context change, possibly as a list of actions.

Request(/members)
- Chunks [view, busy]
- Chunks [view, redirect(/login), status(302)]

Request(/login)
- Chunks [view, status(200]

Context({ isAuthenticated: true })
- Chunks [view, status(200]

Request(/members)
- Chunks [view, busy]
- Chunks [view, data, busy]
- Chunks [view, data, status(200)]

Being able to see at a glance the information that is mapped to the current URL, and the process used to fetch it, should be useful for debugging routing issues. It would also allow for developers to pick a previous state and use that instead, i.e. time travel.

Accessibility example

To support screenreaders, each branch should store a title under data. Then on navigation, the browser title should be updated, and an announcer div updated.

See https://twitter.com/noopkat/status/818240685715419136

This will need some changes:

  • The documentation website will need to have the announcer div (it wouldn't hurt to use it for the documentation pages too)
  • The react-junctions component should accept an onRouteChanged callback (or something similar), which we can use to update the browser title and announcer div

It would be nice to have all this built into react-junctions, but I can't see how to make it work:

  • Multiple routes can be active at once, and deciding which one's title should be used is app-specific
  • The announcer div needs to be available at page load, but junctions isn't loaded until later and probably doesn't know which div it is

Proposal: support array of meta tags

For now, meta tags is an object and due to hardcoded pattern, end result is always bound to name="" and content="" attributes. There is a variety of other attributes, so it seems a must to support this.

It's already possible with custom navi.config.js, but the main idea is to have more flexible defaults.

Users might like to write their meta this way:

  '/': createPage({
      title: 'Navi',
      meta: [
        {
          name: 'description',
          content: 'navi-description'
        },
        {
          property: 'og:description',
          content: 'navi-og-description'
        }
      ]
  })

Some kind of draft implementation:

  function generateMetaTags(attrs) {
    if (!Array.isArray(attrs)) return ''

    return attrs
      .map(
        attr =>
          `<meta ${Object.entries(attr)
            .map(([key, val]) => `${he.encode(key)}="${he.encode(val)}"`)
            .join(' ')}>`
      )
      .join('')
  }

  const title = `<title>${route.title || 'Untitled'}</title>`
  const meta = generateMetaTags(route.meta)

Usage with apollo

Navi looks like a really neat little routing alternative. Our data always come from Apollo though which allows to fetch all data required in a whole react tree in server rendering with getDataFromTree is something like that possible with Navi?

TypeError: resolvable is not a function

I found this library via a tutorial on Zeit, I'm pretty pleased with the clarity and ease-of implementation so far, so thank you very much!

I recently was hit with this simple and I imagine easy-to-fall-into mistake:

export default createSwitch({
  paths: {
    "/": createPage({
      title: `home`,
      getContent: () => import("./Home")
    }),
    "/test": {
      title: "this is a test",
      getContent: () => import("./TestPage")
    }
})

โ˜๏ธ So I'm missing a createPage wrapping on the /test property.

Because of that, when I try to go to /test, I see an error:

TypeError: resolvable is not a function
  Resolver.resolve
  node_modules/navi/dist/es/Resolver.js:54

I'd prefer to see some kind of error like:

Given path "/test" is not a valid path. Provided path should be one of Switch, Page, Redirect or Context. See https://frontarm.com/navi/reference/declarations/#declaring-pages for more info.

I don't have enough context to know whether this would be a reasonable change, but I spend a few minutes tracking this down and it seems like it'd be a common enough mistake for others to run into.

Thanks for your time and thanks for this project -- excited to use it more in the future!


For myself I wrapped the paths property with a map(createPage):

import {map} from 'ramda'
export default createSwitch({
  paths: map(createPage, { '/': blah, '/test': blah})
})

So that I can avoid that potential footgun, but likely more complicated pages would require more nuance.

When using react hooks in pages and using getContent and error throws

I have noticed an issue when using the latest react version 16.8.0-alpha.0 that when i use getContent and do a dynamic import to a page that contains a hook it will blow up with this error:
Hooks can only be called inside the body of a function component.

Has anyone else seen this? And is there a current fix. I have found that I can get around it by using content and getting the page as a React component but I wonder if thats a great idea as I assume it then pulls in every page on load

Subdomain routing example

Subdomain routing should be possible with Junctions by manually handling events from history, and joining the current subdomain onto the beginning of the pathname.

Unfortunately this will prevent baseLocation support, but I doubt this will be an issue if we're using subdomains. The baseLocation filtering function could be exported and documented if this turns out to be an issue.

History API vs history library

This bug is related to the documentation as well as to the dependencies of this library.
It's not stated explicitly what your library's feature coverage is in browsers when comparing using browsers' native window.history vs the history library's API. This makes it unclear what to expect if one uses your library without the history library as a dependency unless the developer tests literally every browser or at least a great many of them.
It would help a fantastic amount if it was stated more explicitly in the docs which precise browsers and features are being shimmed by the history API when/if it's used in a project along with this router. Every little bit of extra code counts, and if it's tenable not to include an extra library, it would help to know that.
Additionally, if it's untenable not to include a given library, maybe because very few browsers based on usage percentage would actually work without that library's support, then it seems that library should be a required dependency.
These are just my opinions. I hope you will consider taking a look at these issues.
Thanks a million.

window.scroll with options object used in scrollToHash is not supported in IE, Edge, Safari and older versions of Chrome

In scrollToHash function window.scroll method is called with options object. Unfortunately, this syntax is not supported by IE, Edge, Safari and older versions of Chrome (MDN docs), which only support the old syntax, i.e. window.scroll(x-coord, y-coord).

Top 3 JS errors on our site reported by TrackJS are caused by this issue :/ Unfortunately some browsers that don't support it don't throw any error and simply scroll to 0, 0, but e.g. older versions of Chrome (e.g. 55, even though it should work according to MDN) throw the following error:

Failed to execute 'scroll' on 'Window': No function was found that matched the signature provided.

Navi scripts failing to build.

Hi, I have a index.html file in the public folder, which consists some jquery plugins and what not, navi seems to get stuck while building the application and assumes the jquery plugins are in my build folder, when i'm building the project, although I have defined the script tags.
For example, I'm using script tags like this.
<script src="//code.jquery.com/jquery-3.3.1.min.js"></script>
But when I build, I get an error like

The build folder is ready to be deployed.
You may serve it with a static server:

  yarn global add serve
  serve -s build

Find out more about deployment here:

  http://bit.ly/CRA-deploy

navi-scripts: Using config at C:\Users\Mikk\blog-template\navi.config.js
Error: Could not load script: "//code.jquery.com/jquery-3.3.1.min.js"
    at onErrorWrapped (C:\Users\Mikk\blog-template\node_modules\jsdom\lib\jsdom\browser\resources\per-document-resource-loader.js:38:19)
    at Object.check (C:\Users\Mikk\blog-template\node_modules\jsdom\lib\jsdom\browser\resources\resource-queue.js:72:23)
    at request.then.catch.err (C:\Users\Mikk\blog-template\node_modules\jsdom\lib\jsdom\browser\resources\resource-queue.js:124:14) { [Error: ENOENT: no such file or directory, open 'C:\Users\Mikk\blog-template\build\code.jquery.com\jquery-3.3.1.min.js']
  errno: -4058,
  code: 'ENOENT',
  syscall: 'open',
  path:
   'C:\\Users\\Mikk\\blog-template\\build\\code.jquery.com\\jquery-3.3.1.min.js' }
[ohshit] An error occured while building your app
ENOENT: no such file or directory, open 'C:\Users\Mikk\blog-template\build\code.jquery.com\jquery-3.3.1.min.js'
Error: ENOENT: no such file or directory, open 'C:\Users\Mikk\blog-template\build\code.jquery.com\jquery-3.3.1.min.js'
error Command failed with exit code 1.

My navi scripts config is below.

import path from 'path'
import getTagsFromSiteMap from './src/utils/getTagsFromSiteMap'

export const renderPageToString = require.resolve('./src/renderPageToString')

export const resolveSiteMapOptions = {
  /**
   * navi-scripts will call this function when creating a list of URLs which
   * need to be statically built. It allows you to substitute in a list of
   * values when URLs contain wildcards, e.g. /tags/:tag -> ["/tags/react"]
   */
  async expandPattern(pattern, router) {
    if (/\/:tag$/.test(pattern)) {
      let siteMap = await router.resolveSiteMap('/')
      return getTagsFromSiteMap(siteMap).map(tag => pattern.replace(':tag', tag))
    }
  },
}

/**
 * Get the file to write each URL to during the build
 */
export function getPagePathname({ url }) {
  if (url.pathname === '/rss/') {
    return 'rss.xml'
  }
  if (url.pathname === '/') {
    return 'index.html'
  }
  return path.join(url.pathname.slice(1), 'index.html')
}

Any ideas what can be causing this?

Don't decode paths in locations (keep the browser-native value)

Hey, I'm really excited about Navi and the potential to replace React Router with it in one of our apps. This is a feature request, and I'm not sure how difficult it would be given Navi's architecture and its use of the history module, but here we go:

It would be awesome if Navi could bypass history's automatic decoding of location.pathname, discussed in this issue here: remix-run/history#505.

That automatic decoding deviates from browser behavior and makes the flow of pathnames very difficult to reason about - it results in divergent behavior when navigating directly to a page via a URL vs when navigating through history with back/forward vs when clicking on a <Link>. We have worked around this by creating a custom <PageRoute> component that manually decodes path params from window.location, but it's all very hack-y.

It would be cool to just drop the entire mess and switch to Navi, however if I'm following the code correctly, I think that bug would still apply here since it's internal to history. If there's a way for Navi to either provide a workaround that doesn't depend on history for the path params, or for Navi to drop the dependency on history entirely (like Reach Router does), that would be really awesome!

Errors importing as an ES module from unpkg

I have been trying to import navi directly from unpkg and have come across a few errors:

  1. If you request https://unpkg.com/[email protected]?module then this error appears:

screen shot 2019-03-04 at 15 21 31

Apparently this is related to webpack webpack/webpack#8838

  1. If you specify the exact file https://unpkg.com/[email protected]/dist/es/index.js then the above error disappears but then this error appears:

screen shot 2019-03-04 at 15 28 22

Apparently this is to do with navi having history as an optional dependency (for interoperability with react-router). Solving this issue is likely to negate issue 1.

How to keep get parameters?

Good day!
I have an url like:
site.com/thing?version=5
where thing is Route
I need get parameter to handle request correctly on server. But it's vanished after router initialisation. How to keep it?

How do I redirect to a route from an async function?

Hi, I feel like I'm missing something fundamental here - how do I go to a route after an async function is called?

I can only see as a way to go to different routes, but how do I trigger a change from some arbitrary function?

Thanks!

'*' wildcard

I can't seem to find this in the docs, but is it possible to route with a '*' wildcard that acts as a middleware?

Example:

const routes = mount({
    '/login': route({
        view: <div>Login</div>,
    }),
    '/': redirect('/login'),
    '*': map((req, { user }) => (user ? protectedRoutes : redirect(req.originalUrl))),
});

react-navi causing error in StrictMode

SideEffect is causing an error in StrictMode.

SideEffect.prototype.componentWillMount = function componentWillMount() {
  mountedInstances.push(this);
  emitChange();
};

found this in react-navi/dist/umd/react-navi.js. Using StrictMode it causes this error:

Warning: Unsafe lifecycle methods were found within a strict-mode tree:
    in StrictMode (at App.tsx:7)
    in App (at src/index.tsx:6)

componentWillMount: Please update the following components to use componentDidMount instead: SideEffect(NullComponent)

Learn more about this warning here:
https://fb.me/react-strict-mode-warnings

Auth guidance

Is there a recommend way of handling authorized URLs with Junctions?

Uncaught Error: URL not found

First off, thanks for creating this! Super exciting project ๐Ÿ™Œ Currently migrating a large web app from react-router to navi.

It seems to be an Uncaught Error: URL not found when navigating to a page that doesn't exist. From the docs I believe the error is supposed to be a NotFoundError so that the <NavNotFoundBoundary /> but it just looks like a generic error based on the console output. The strange thing is that the Error boundary catches the error ๐Ÿค”

navi: 0.10.5
react-navi: 0.10.6
react: 16.6.0

Because the error message is not present on my Express server (using createMemoryNavigation) it leads me to believe the problem might be related to createBrowserNavigation, but I'm uncertain.

The error is also present on the examples on the Navi docs webpage ๐Ÿ‘‡

screenshot 2019-01-10 at 23 25 14


Full stack trace
Errors.js:24

Uncaught Error: URL not found: /asdf/
    at createNotFoundSegment (Segments.js:38)
    at class_1../node_modules/navi/dist/es/Node.js.NodeMatcher.getResult (Node.js:29)
    at class_1../node_modules/navi/dist/es/Switch.js.SwitchMatcher.execute (Switch.js:121)
    at class_1../node_modules/navi/dist/es/Node.js.NodeMatcher.getResult (Node.js:22)
    at RouteObservable.refresh (RouteObservable.js:28)
    at RouteObservable.handleChange (RouteObservable.js:13)
    at RouteObservable../node_modules/navi/dist/es/RouteObservable.js.RouteObservable.subscribe (RouteObservable.js:51)
    at CurrentRouteObservable../node_modules/navi/dist/es/CurrentRouteObservable.js.CurrentRouteObservable.handleURLChange (CurrentRouteObservable.js:186)
    at CurrentRouteObservable../node_modules/navi/dist/es/CurrentRouteObservable.js.CurrentRouteObservable.refresh (CurrentRouteObservable.js:110)
    at new CurrentRouteObservable (CurrentRouteObservable.js:94)
NaviError	@	Errors.js:24
NotFoundError	@	Errors.js:40
createNotFoundSegment	@	Segments.js:38
./node_modules/navi/dist/es/Node.js.NodeMatcher.getResult	@	Node.js:29
./node_modules/navi/dist/es/Switch.js.SwitchMatcher.execute	@	Switch.js:121
./node_modules/navi/dist/es/Node.js.NodeMatcher.getResult	@	Node.js:22
RouteObservable.refresh	@	RouteObservable.js:28
RouteObservable.handleChange	@	RouteObservable.js:13
./node_modules/navi/dist/es/RouteObservable.js.RouteObservable.subscribe	@	RouteObservable.js:51
./node_modules/navi/dist/es/CurrentRouteObservable.js.CurrentRouteObservable.handleURLChange	@	CurrentRouteObservable.js:186
./node_modules/navi/dist/es/CurrentRouteObservable.js.CurrentRouteObservable.refresh	@	CurrentRouteObservable.js:110
CurrentRouteObservable	@	CurrentRouteObservable.js:94
createCurrentRouteObservable	@	CurrentRouteObservable.js:53
BrowserNavigation	@	BrowserNavigation.js:92
createBrowserNavigation	@	BrowserNavigation.js:44
main$	@	index.js:27
tryCatch	@	runtime.js:65
invoke	@	runtime.js:303
prototype.(anonymous function)	@	runtime.js:117
tryCatch	@	runtime.js:65
invoke	@	runtime.js:155
(anonymous)	@	runtime.js:202
callInvokeWithMethodAndArg	@	runtime.js:201
enqueue	@	runtime.js:224
prototype.(anonymous function)	@	runtime.js:117
./node_modules/regenerator-runtime/runtime.js.runtime.async	@	runtime.js:248
main	@	index.js:26
./src/index.js	@	index.js:72
__webpack_require__	@	bootstrap:68
0	@	commons.js:91567
__webpack_require__	@	bootstrap:68
(anonymous)	@	bootstrap:238
(anonymous)	@	bootstrap:238
Promise.then (async)		
invoke	@	runtime.js:164
(anonymous)	@	runtime.js:202
callInvokeWithMethodAndArg	@	runtime.js:201
enqueue	@	runtime.js:224
prototype.(anonymous function)	@	runtime.js:117
./node_modules/regenerator-runtime/runtime.js.runtime.async	@	runtime.js:248
main	@	index.js:26
./src/index.js	@	index.js:72
__webpack_require__	@	bootstrap:68
0	@	commons.js:91567
__webpack_require__	@	bootstrap:68
(anonymous)	@	bootstrap:238
(anonymous)	@	bootstrap:238


react-dom.development.js:15123

The above error occurred in the <InnerNavContent> component:
    in InnerNavContent (created by Context.Consumer)
    in NavContent
    in InnerNotFoundBoundary (created by Context.Consumer)
    in ErrorBoundary (created by Root)
    in NavProvider (created by Root)
    in IntlProvider (created by LanguageProvider)
    in LanguageProvider (created by Root)
    in Provider (created by Root)
    in Root

React will try to recreate this component tree from scratch using the error boundary you provided, InnerNotFoundBoundary.
logCapturedError	@	react-dom.development.js:15123
logError	@	react-dom.development.js:15157
callback	@	react-dom.development.js:15931
callCallback	@	react-dom.development.js:11194
commitUpdateEffects	@	react-dom.development.js:11233
commitUpdateQueue	@	react-dom.development.js:11224
commitLifeCycles	@	react-dom.development.js:15271
commitAllLifeCycles	@	react-dom.development.js:16523
callCallback	@	react-dom.development.js:149
invokeGuardedCallbackDev	@	react-dom.development.js:199
invokeGuardedCallback	@	react-dom.development.js:256
commitRoot	@	react-dom.development.js:16677
completeRoot	@	react-dom.development.js:18069
performWorkOnRoot	@	react-dom.development.js:17997
performWork	@	react-dom.development.js:17901
performSyncWork	@	react-dom.development.js:17873
requestWork	@	react-dom.development.js:17761
scheduleWork	@	react-dom.development.js:17566
scheduleRootUpdate	@	react-dom.development.js:18240
updateContainerAtExpirationTime	@	react-dom.development.js:18267
updateContainer	@	react-dom.development.js:18324
./node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render	@	react-dom.development.js:18586
(anonymous)	@	react-dom.development.js:18726
unbatchedUpdates	@	react-dom.development.js:18124
legacyRenderSubtreeIntoContainer	@	react-dom.development.js:18722
render	@	react-dom.development.js:18783
main$	@	index.js:64
tryCatch	@	runtime.js:65
invoke	@	runtime.js:303
prototype.(anonymous function)	@	runtime.js:117
tryCatch	@	runtime.js:65
invoke	@	runtime.js:155
(anonymous)	@	runtime.js:165
Promise.then (async)		
invoke	@	runtime.js:164
(anonymous)	@	runtime.js:202
callInvokeWithMethodAndArg	@	runtime.js:201
enqueue	@	runtime.js:224
prototype.(anonymous function)	@	runtime.js:117
./node_modules/regenerator-runtime/runtime.js.runtime.async	@	runtime.js:248
main	@	index.js:26
./src/index.js	@	index.js:72
__webpack_require__	@	bootstrap:68
0	@	commons.js:91567
__webpack_require__	@	bootstrap:68
(anonymous)	@	bootstrap:238
(anonymous)	@	bootstrap:238

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.