npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

naglfar

v3.0.0

Published

Redux router done right.

Downloads

67

Readme

Naglfar

A router that keeps all state in redux. It treats the url like a form input: it fires actions on change which update the location in your state. This is accessible from any redux-connected component: no need for a separate RouteProvider.

It doesn't need to own/wrap your application. It provides a Fragment pattern to partition your UI based on current route, including route status codes.

It works both in the browser and on the server, and offers:

  • Server-side rendering
  • Data fetching prior to transition (via dispatching actions)
  • Status codes
  • Not-found routes (and any status-code based route)
  • Redirects
  • Prefetching data (pre-firing actions without modifying state) for Links to speed up navigation

Requires the history module.

How to use

Install via npm i -S naglfar or yarn add naglfar.

Create your routes. Example:

// routes.js
import {routeFragment, routeRedirect} from 'naglfar'
import {
  getSubredditFeed,
  getStory
} from './actionCreators'

// Actions per route can be regular actionCreators, async thunks,
// action objects, or action-type strings, or an array of any of those
export const SubredditRoute = routeFragment('/r/:subreddit', getSubredditFeed)
export const StoryRoute = routeFragment('/r/:subreddit/:story', [getSubredditFeed, getStory])
export const NotFoundRoute = routeFragment(404)
export const ErrorRoute = routeFragment(500)

routeRedirect('/', '/r/front_page')

Actions are passed the route params, and should return promises (this isn't necessary, but all promises return by your actions will be resolved before the state will transition to the specified route).

Create your app:

import {Link, Fragment} from 'naglfar'
import {
  SubredditRoute,
  StoryRoute
} from './routes'

export default () => (
  <div>
    <nav>
      <Link to='/r/all'>All</Link>
      <Link to='/r/games' prefetchData>Games</Link>
      <Link to='/r/cars'>Cars</Link>
    </nav>
     // You can create route-based views directly as well
     // But the router doesn't know about these until after the first render,
     // so their routes can't be relied on for data-fetching in SSR
    <Fragment forRoute={'/r/front_page'}>
      <FrontPage />
    </Fragment>
    <SubredditRoute>
      <Feed />
    </SubredditRoute>
    <StoryRoute>
      <Story />
    </StoryRoute>
  </div>
)

Render it

import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {Provider} from 'react-redux'
import createHistory from 'history/createBrowserHistory'
import router, {reducer: routeReducer} from 'naglfar'
import App from './App'
import appReducer from './store'

const composeReducers = (first, ...fns) =>
  (s, a) => fns.reduce((s, fn) => fn(s, a), first(s, a))

const configureStore = (history, initialState) =>
  createStore(
    composeReducers(appReducer, routeReducer),
    initialState,
    applyMiddleware(thunk, router(history))
  )

const store = configureStore(createHistory(), {})

export default () => {
  render(
    <Provider store={store}>
      <App />
    </Provider>
  )
}

Server-side render middleware (example using express)

import React from 'react'
import {renderToString, renderToStaticMarkup} from 'react-dom/server'
import {Provider} from 'react-redux'
import createHistory from 'history/createMemoryHistory'
import {resolveLocation} from 'naglfar'
import Root from './Root'
import App from './App'

const renderHtml = ({store}) => {
  const body = renderToString(
    <Provider store={store}>
      <App />
    </Provider>
  )

  const rootMarkup = renderToStaticMarkup(
    <Root
      content={body}
      initialState={store.getState()}
    />
  )

  return `<!doctype html>\n${rootMarkup}`
}

const resolvePath = ({path, store}) => {
  resolveLocation(path, store.dispatch)
    .then(({status, url}) => {
      if (url) return {status, redirect: url}
      return {status, html: renderHtml({store})}
    })
}

const renderMiddleware = (req, res) => {
  const history = createHistory({
    initialEntries: [req.url]
  })
  resolvePath({path: req.url, store: configureStore(history)})
    .then(({redirect, status, html}) => {
      if (redirect) res.redirect(status, redirect)
      res.status(status).send(html)
    })
}

export default renderMiddleware

// use like: server.get('*', renderMiddleware)

Useful stuff

If you need to access location data, such as query and route params, they are accessible on your redux store under the top level location key. Example:

import {connect} from 'react-redux'
import FilterOptions from '../FilterOptions'

const mapStateToProps = ({location}) => ({
  sortOrder: location.query.sortOrder,
  page: location.params.page
})

export default connect(mapStateToProps)(FilterOptions)

If you want to only handle valid routes, you can extract the whitelist of all registered routes:

import {whitelist} from 'naglfar'

const rejectInvalidRoutesMiddleware = (req, res, next) =>
  whitelist.includes(req.path) ? next() : res.status(404).send()