Coder Social home page Coder Social logo

molefrog / wouter Goto Github PK

View Code? Open in Web Editor NEW
6.1K 35.0 142.0 706 KB

πŸ₯’ A minimalist-friendly ~2.1KB routing for React and Preact

Home Page: https://npm.im/wouter

License: The Unlicense

JavaScript 17.38% TypeScript 82.62%
react-hooks router microlibrary zero-dependency react preact typescript react-router hacktoberfest

wouter's Issues

New tabs

Hello,

Is there any way to use the component to open a new tab at that location?

Thanks

Add coverage

My recommendation is to add coverage badge, example codecov.

404 on direct request to route

Routes only work with <Link> but when you go directly to a route it just throws 404. What's about adding support for direct requests like in React Router?

Uncaught RangeError: Maximum call stack size exceeded

I'm getting Uncaught RangeError: Maximum call stack size exceeded while using useLocation right from 'wouter' package

import { useLocation } from 'wouter';

this works fine

import useLocation from 'wouter/use-location';

Minor type tweaks to cover edge cases

Hey @StrayFromThePath @StrayFromThePath I'm super excited to have types finally in the project! I managed to spent some time reading the sources and although I'm not a TS user I've got some feedback I thought I need to share with you.

Proper Params type

The current Params type is a union of hash or null. This works fine for the Match type which can represent either [true, { ... }] or [false, null], however this also assumes that the render prop can contain the null as a first argument here. I think we can narrow the type even further, because the render prop always guarantees that the argument is a valid object.

So how can this be solved? I suggest we make the Params type to always be an object and mark that the match result could contain null:

type Params = { [paramName: string]: string }

// it this a real TypeScript code? :) I'm not sure, but you get the idea
type Match = [boolean, Params | null];

// or is this even possible:
type Match = [true, Params] | [false, null];

Remove match props from Route

This prop is an internal thing used by the switch, so it's not part of the public API. I suggest we either remove it or change the type, since it's not a boolean anymore, but Match. But I think it's easier to just remove this.

Proper type of children in Router

As @StrayFromThePath pointed out in #54, some components need proper types to support mixed React nodes. The current type of the children prop in Router is ReactElement | ReactElement[];, but this makes it impossible to use something like:

<Router>
  Hello, this is an <Application />
  We also have <Footer /> and some {1337} numbers here.
</Router>

Same applies to Link. Generally speaking, anything that React.Provider accepts should work here as well.

Let me know what you guys think and keep up with the good work!

Package is not compatible with Create React App and it's testing setup

I see the published package contains import statements. This produce the following error in default create react app setup after running npm test

Test suite failed to run

    /home/igor/react-hooks-beer-example/node_modules/wouter/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import locationHook from "./use-location.js";
                                                                                                    ^^^^^^^^^^^^

    SyntaxError: Unexpected identifier

Can we publish a transpiled version containing require statements so no babel transpilation would be required inside node_modules?

`Redirect` component is not documented

I just saw the Redirect component exported in the source code, but I couldn't find it documented anywhere, this is just a heads up but if I find time over the weekend I'll try to document it myself πŸ˜‰

Allow passing state to link/setLocation

Would it be possible to support the following using the browser's history.state API, optionally allowing history[method](stateObj, 0, to), instead of always using history.[method](0, 0, to)?

e..g.

<Link href="/" state={{ exampleState: true }}>Link</Link>

and

const [location, setLocation] = useLocation();

setLocation("/", { exampleState: true });

Not sure where the accessor makes the most sense, maybe something like:

import { useRouterState } from 'wouter';

const routerState = useRouterState();

Thanks!

Auto cast numeric parameters as integers

Here's an idea:

Cast parameters as integers when it can, leave it otherwise.

Use case: I'm comparing match.id with object.id to apply a selected class and it would be great to not have to use == and not have to cast it myself.

Why: I don't see casting parameter types inside the scope of my application, but rather belonging to the routing mechanism.

How would I do: test each parameter value for a string of numbers and apply parseInt accordingly.

I can hack it together and send a PR if you like it. Also, let me know if think another approach would be best.

Cheers! 🍺

Preact support

Since the library was created to be as small as possible it would make sense to add support for Preact. It should only support the version 10 of Preact, which isn't fully released yet, but supports hooks and fragments.

There are few interested problems that we will need to solve in order to release this. Here is how I see the final API:

// regular imports
import { Route, Switch, Link } from "wouter/preact";

// additional modules
import staticHistory from "wouter/preact/extra/static-history.js"

We can do that by simply creating a new folder preact in the repo with one file called react-deps.js with all react/preact exports such as useState, createElement and so on. The folder can be prepopulated with the sources from the root folder on prepublish npm script (might need to use this package https://www.npmjs.com/package/copyfiles for cross-platfom support). We shouldn't commit anything except for preact/react-deps.js however, the files should be gitginored!

The implementation plan:

  • Remove incompatible methods isValidElement and Children.map. Merge #27 and check that it doesn't break anything.
  • Create a project structure for Preact /preact/react-deps.js and /react-deps.js and write a prepublish script.
  • Test the implementation with Preact. Basic rendering, make sure Route, Link and Switch work. A 2-3 test cases should be enough. One thing that isn't clear yet is how to require right react-deps.js in tests without having sources in preact folder. Maybe Jest mocks?
  • Describe the Preact feature in the README.

Incorrect Link href with basepath

Hello, I'm trying to implement basepath into my application. I followed the README's instruction. Everything works well, except <Link /> component.

For example,

<Link href="/path">link</Link>

It's fine that clicking on it will navigate to :basepath/path, but the href prop is still /path.

Now I had to use <Link /> in this way,

<Link href="/path">
  <span><a href={`${basepath}/path`}>link</a></span>
</Link>

Any idea on it?

Wouter isn't compatible with TypeScript and Preact at the same time

Wouter can't be used with TypeScript and Preact at the same time.

If you install @types/wouter, that also installs @types/react, which conflicts with and breaks type definitions for preact.

It (probably) works if you use preact-compat, but at that point you're not really "compatible with preact" in my opinion.

A possible fix would be to push an alternative @types/wouter-preact that worked with Preact's type definitions.

CC @StrayFromThePath - Thought this would be relevant for you.

Support for search

In the latest version of wouter, when the location is somewebsite.com/users?name=john

const [location] = useLocation();

location only retrieves /users emitting ?name=john. And even though I push new query params with other methods, re-rendering is not triggered. But sometimes a view needs to depend on search params.

I think we have two options in a broad sense

  • add useSearch thing

    const [search, setSearch] = useSearch();
    search // "?name=john"

    or we can let wouter give searchParams as a URLSearchParams object, the parsed one.
    or how about useURL which gives current location as URL object?

  • let location be location.pathname + location.search.

    breaking change!

    In this case, we got a new problem to redefine routing rules. Should useRoute provide matching for search parameters? I don't think so. Routing rules can go the same just ignoring search.
    And the parsing for location.pathname + location.search is annoying. AFAIK, there is no builtin function to parse it. And even if there is, the code to parse would be repeated in many components.

Support for commonjs output - wouter-preact blocks ts-jest on import statements

Hello! :)
I've just added wouter-preact to my preact + typescript boilerplate. I'm using ts-jest for tests with ts-jest preset. Unfortunately I cannot test app.tsx component which contains
import { Link, Route } from 'wouter-preact';

After running yarn test I've got:

FAIL src/app/app.spec.tsx
● Test suite failed to run

/home/mk/Documents/repos/preact-ts-parcel/node_modules/wouter-preact/index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import locationHook from "./use-location.js";
                                                                                                ^^^^^^^^^^^^

SyntaxError: Unexpected identifier

  1 | import { h } from 'preact';
> 2 | import { Link, Route } from 'wouter-preact';
    | ^
  3 | import { TypedComponent } from '../shared/typings/prop-types';
  4 | import { actions, StoreState } from '../store';
  5 | import { useAction, useSelector } from '@preact-hooks/unistore';

  at ScriptTransformer._transformAndBuildScript (node_modules/@jest/transform/build/ScriptTransformer.js:537:17)
  at ScriptTransformer.transform (node_modules/@jest/transform/build/ScriptTransformer.js:579:25)

I'm not saying that this is a problem with wouter-preact itself, because it may be e.g. ts-jest problem or maybe even I did something wrong in terms of configuration (see below) but could you add also commonjs output?

jest.config.js

module.exports = {
    preset: 'ts-jest',
    roots: ['<rootDir>/src'],
    verbose: true,
    transform: {
        '^.+\\.(t|j)sx?$': 'ts-jest',
    },
    testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};

tsconfig.json:


{
    "compilerOptions": {
        "outDir": "./dist",
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "commonjs",
        "strict": true,
        "moduleResolution": "node",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "target": "es6",
        "jsx": "react",
        "jsxFactory": "h",
        "allowJs": true
    },
    "include": ["./src/**/*"],
    "exclude": ["node_modules", "**/*.spec.ts", "**/*.spec.tsx"]
}

I've been trying with different tsconfig.json configurations (also without "include" & || "exclude" fields) but without success.

Related issue (?): #70

The regex routes cause errors

If we will try to use path-to-regexp package it will break the router just because it uses another semantics. I have written a custom makeMatcher function to make it possible to use it. It is not perfect but at least doesn't break the system.

The getRegexp(pattern); function doesn't return tuple but just regex in the path-to-regexp package

Also, this workaround allows creating i18n routes like /:lang(en|es|pt)/posts/:id.

function makeMatcher(makeRegexpFn = pathToRegexp) {
  const cache: { [key: string]: RegExp } = {};

  // obtains a cached regexp version of the pattern
  const getRegexp = (pattern: string) =>
    (cache[pattern]) || (cache[pattern] = makeRegexpFn(pattern));

  return (pattern: Path, path: Path) => {
    const regexp = getRegexp(pattern);
    const found = pattern.match(/:(\w+)/ig);
    const keys = found ? found.map(el => el.substr(1)) : [];
    const out = regexp.exec(path);

    if (!out) return [false, null];

    // formats an object with matched params
    const params = keys.reduce((params, key, i) => {
      params[key] = out[i + 1];
      return params;
    }, {});

    return [true, params];
  };
}
...

<Router matcher={makeMatcher(pathToRegexp)}>

Url Params decode

Hello,

Been having some issues with params values in the sense that with long text params the space symbols still stays in the props value extracted and i have to use decodeURIComponent.

Is there anyway props value could be formatted by default?

Discussion about default routes

i wrote a demo:

<Switch>
    <Route path="/:anything*">Default Route: nothing found!</Route>
    <Route path="/users/one">Users One</Route>
    <Route path="/">First Route</Route>
     //...
    {/* <Route path="/:anything*">Default Route: nothing found!</Route> */}
</Switch>

If we accidentally put the default route in front, it will cause problems.

I think there are two ways:

  1. The problem is explained in the documentation, which draws the attention of developers⚠️
  2. If you use <Route default component={AnyComponent}/> and the position is irrelevant, is it a good idea?

@omgovich @molefrog Looking forward to your reply.

Support onClick funtion on Link component

Hi @molefrog, nice plugin, i love the Hooks approace!

I have discovered that the Link component didn't accept a onClick prop to do something other then the normal behavior of navigate.

I have implemented the code to support this functionality locally, can i make a PR to merge with your package?

Thanks in advance!

ctrl + click to open link in new tab not working

For desktop browsers there are typical navigation patterns to open the link in a new tab instead of the current one. The most common one is ctrl + click or clicking with the middle mouse button. Currently this doesn't work because the event will always be prevented:

wouter/index.js

Lines 91 to 98 in c9c7900

const handleClick = useCallback(
event => {
event.preventDefault();
navigate(href);
onClick && onClick(event);
},
[href, onClick, navigate]
);

In preact-router we have this line which explicitly checks for common key combinations and lets the browser handle the event instead:

// ignore events the browser takes care of already:
if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.button!==0) return;

https://github.com/preactjs/preact-router/blob/3eb5b31fe75c34672eeb13f2d310dee6d51b1f97/src/index.js#L112

Use with React Native?

Has anyone used this library with React Native?

Any interesting experiences to share?

Support for basepath

Hello again πŸ™ŒπŸ»

I'm made demo on gh-pages and I can't specify base path for router (in my case it wouter-async-routes)

need this feature πŸ’πŸ»β€β™‚οΈ

Router push with Switch

I made a <Switch> component like this:

import { useRouter } from 'wouter'

const Switch = ({ children }) => {
    const router = useRouter()
    const path = router.history.path()
    const childPaths = children.map(c => c.props.path)
    let matchedIndex
    childPaths.forEach((childPath, idx) => {
        const match = router.matcher(childPath, path)[0]
        if (isNaN(matchedIndex) && match) {
            matchedIndex = idx
        }
    })
    return children[matchedIndex]
}

export default Switch

This just checks which of the children's path matches first and renders that Route. This sorta works, but when I navigate using router.history.push() afterwards, I get blank screens and nothing renders until I refresh. Any ideas?

How to add an 404 route?

Hello. Nice routing library you got here. But I can't find a way to do a "default route" eg to create a 404 error route.

Can you shed some info on this?

Thanks.

Get down to 1KB. Reimplement a subset of path-to-regexp

Right now the gzipped library size is 2KB, 70% of which is path-to-regexp dependency. While path-to-regexp provides a well-known way for developer to describe routes, it seems like we could shrink it's functionality to cover the 99% of use cases.

It would be cool to reimplement a limited subset of the library, so that:

  1. We drop that dependency and make it a zero-dependency library 😎
  2. We reduce the size to be less than 1KB (current bundle size without a matcher is 686B).

For users who still want to use the path-to-regexp functionality and get an advanced syntax we could provide an optional entry point:

import { Router } from "wouter";
import makeMatcher from "wouter/matcher"; 
import pathToRegexp from "path-to-regexp";

<Router matchFn={makeMatcher(pathToRegexp)}>
  ...
</Router>

So ideally it would be nice to define a subset of features we would like to support. Here are my ideas:

  • Always case-insensitive
  • Ignores a slash at the end
  • Matches an entire path
  • Supports named segments /:foo/:bar
  • Supports modifiers: :foo?, :foo* and :foo+
  • No support for unnamed params. All params should have a name.
  • No segment constraints e.g. /:foo(\d+)/

Intercept the location change

Hi,

I could not see on the docs how to intercept the location change from a component.

Let's say I have a dirty state at the current page and want to use a custom modal component to alert the user if they want to move forward and lose data.

Thanks

Getting active path

Hello, wouter seems great so far!

I would like to highlight the current page's link on my navbar, is there a better way than the following?

const [, params] = useRoute("/:page");
const currentPage = params ? `/${params.page}` : "/"; //params will be null on /

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.