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

redux-first-router-restore-scroll

v1.2.2

Published

think of your app in states not routes (and, yes, while keeping the address bar in sync)

Downloads

51,475

Readme

redux-first-router-restore-scroll

This package provides complete scroll restoration for redux-first-router through the call of a single function. It also insures hash changes work as you would expect (e.g. like when you click #links to different section of a Github readme it automatically scrolls, and allows you to use the browser back/next buttons to move between sections you've visited).

Example:

import restoreScroll from 'redux-first-router-restore-scroll'
connectRoutes(history, routesMap, { restoreScroll: restoreScroll() })

Advanced Usage

To disable automatic scroll restoration, pass manual: true:

import restoreScroll from 'redux-first-router-restore-scroll'

connectRoutes(history, routesMap, {
  restoreScroll: restoreScroll({ manual: true })
})

See Manual Scroll Position Updates below for how to handle scroll restoration manually.

If you'd like to implement custom scroll positioning, provide a shouldUpdateScroll handler as seen below:

import restoreScroll from 'redux-first-router-restore-scroll'

connectRoutes(history, routesMap, {
  restoreScroll: restoreScroll({
    shouldUpdateScroll: (prev, locationState) => {
      // disable scroll restoration on history state changes
      // note: this is useful if you want to maintain scroll position from previous route
      if (prev.type === 'HOME' && locationState.type === 'CATEGORY') {
        return false
      }

      // scroll into view HTML element with this ID or name attribute value
      else if (locationState.load && locationState.type === 'USER') {
        return 'profile-box'
      }


      // return an array of xy coordinates to scroll there
      else if (locationState.payload.coords) {
        return [coords.x, coords.y]
      }

      // Accurately emulate the default behavior of scrolling to the top on new history
      // entries, and to previous positions on pop state + hash changes.
      // This is the default behavior, and this callback is not needed if this is all you want.
      return true
    }
  })
})

Manual Scroll Position Updates

It's one of the core premises of redux-first-router that you avoid using 3rd party container components that update unnecessarily behind the scenes (such as the route component from React Router), and that Redux's connect + React's shouldComponentUpdate stay your primary mechanism/container for controlling updates. It's all too common for a lot more updates to be going on than you're aware. The browser isn't perfect and jank is a fact of life for large animation-heavy applications. By keeping your updating containers to userland Redux containers (as much as possible), you keep your app's rendering performance in your control.

Everything redux-first-router is doing is to make Redux remain as your go-to for optimizing rendering performance.

It's for this reason we avoid a top level <ScrollContext /> provider component which listens and updates in response to every single location state change. It may just be the virtual DOM which re-renders, but cycles add up.

Therefore, in some cases you may want to update the scroll position manually. So rather than provide a <ScrollContext /> container component, we expose an API so you can update scroll position in places you likely already are listening to such updates:

import React from 'react'
import { updateScroll } from 'redux-first-router' // note: this is the main package

class MyComponent extends React.Component {
  componentDidUpdate() {
    const dispatch = this.props.dispatch
    requestData()
      .then(payload => dispatch({ type: 'NEW_DATA', payload })
      .then(() = updateScroll())
  }

  render() {...}
}

The purpose of calling updateScroll after the new data is here and rendered is so that the page can be scrolled down to a portion of the page that might not have existed yet (e.g. because a spinner was showing instead).

Note however that if you are using redux-first-router's thunk or chunks options for your routes, updateScroll will automatically be called for you after the corresponding promises resolve. So you may never need this.

Custom Storage Backend

To implement a custom backend storage for scroll state, pass a custom stateStorage object. The object should implement the methods as described by scroll-behavior as well as a function called setPrevKey that keeps track of the previous key. See the default sessionStorage backed example.

import restoreScroll from 'redux-first-router-restore-scroll'
import someStorageMechanism from './someStorageMechanism'

function determineKeyFromLocation(location, key) {
  // figure out a key for your storage from location and nullable key, not a robust example
  return `${location.key || location.hash || 'loadPage'}${key}`
}

let prevKey;
const stateStorage = {
  setPrevKey(key) {
    prevKey = key;
  },
  read(location, key) {
    // somewhere you have stored state
    return someStorageMechanism.get(determineKeyFromLocation(location, key))
  },
  save(location, key, value) {
    // somewhere you will store state
    someStorageMechanism.set(determineKeyFromLocation(location, key), value)
  }
}

connectRoutes(history, routesMap, {
  restoreScroll: restoreScroll({
    stateStorage
  })
})

Caveats

In React 16 ("Fiber"), there is more asynchrony involved, and therefore you may need to pass the manual option and create a component at the top of your component tree like the following:

import React from 'react'
import { connect } from 'react-redux'
import { updateScroll } from 'redux-first-router' 

class ScrollContext extends React.Component {
  componentDidUpdate(prevProps) {
    if (prevProps.path !== this.props.path) {
      updateScroll()
    }
  }

  render() {
    return this.props.children
  }
}
export default connect(({ location }) => ({ path: location.pathname }))(ScrollContext)

Now just wrap your top level <App /> component inside <ScrollContext />. Its componentDidUpdate method will be called last and the remainder of your page (i.e. child components) will have already rendered. As a result, the window will be able to properly scroll down to a portion of the page that now exists.

Again, since redux-first-router is based on Redux, our goal is to avoid a huge set of library components, but rather to facilitate your frictionless implementation of tried and true Redux connected container patterns. We will however try to find a way to automate this for you in the main redux-first-router package on history transitions if Fiber provides some sort of handler like: React.runAfterUpdates(updateScroll), similar to React Native's InteractionManager.runAfterInteractions.

Notes

Modern browsers like Chrome attempt to provide the default behavior, but we have found it to be flakey in fact. It's pretty good in Chrome, but doesn't always happen. If all you want is the default behavior and nothing more, simply call restoreScroll() and assign it to the restoreScroll option of redux-first-router's option map. That results in the same as returning true above.

Scroll Restoration for Elements other than window

We got you covered. Please checkout redux-first-router-scroll-container.

Scroll Restoration for React Native

We got you covered! Please checkout redux-first-router-scroll-container-native.

Thanks

Our Scroll Restoration package comes thanks to: https://github.com/taion/scroll-behavior, which powered react-router-scroll in older versions of React Router. See either for more information on how this works.