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 🙏

© 2026 – Pkg Stats / Ryan Hefner

magic-selectors

v0.3.0

Published

Like `useEffect` but for selectors.

Readme

magic-selectors

Like useEffect but for selectors.

Under construction…

Why?

Problem

In an early stage of development of an application, we might not want to deal with data loading. We might decide to pre-load some data into the store before starting the application.

function Application(props) {
  const [dataLoaded, setDataLoaded] = useState(false)
  const dispatch = useDispatch()

  useEffect(() => {
    dispatch(loadInitialData())
      .then(() => setDataLoaded(true))
      .catch(handleCatastrophicError)
  }, [dispatch])

  if (!dataLoaded) {
    return <LoadingScreen />
  }

  return <Layout>{/* ... */}</Layout>
}

function loadInitialData() {
  return async dispatch => {
    await Promise.all([
      dispatch(loadAllUsers()),
      dispatch(loadAllProjects()),
      dispatch(loadAllTags()),
      dispatch(loadSubscriptionInfo()),
    ])
  }
}

and the component that renders the user name simply select the data from the store.

function UserCard(props) {
  const user = useSelector(selectUserState(props.userId))
  //                       ^ selects a user or returns a default "unknown user" object
  return <Card title={user.name}>{/* ... */}</Card>
}

Over time, the app grows and there is more data to load. Once 10kb, some long-time users have to wait for 200kb of data to load. And we realized that not all data that we load beforehand are actually needed on first render. For example, in the home page, we might not need the tags.

In modern React apps, we select data from store with useSelector and fetch data with useEffect when we need to load it:

function ProjectPage(props) {
  const { projectId } = props

  const dispatch = useDispatch()

  useEffect(() => {
    dispatch(fetchProjectDataIfNeeded(projectId))
  }, [projectId, dispatch])

  const projectData = useSelector(selectProjectData(projectId))

  return /* ... */
}

This ensures that if the data doesn’t already exist for something we try to display, it will be fetched and eventually displayed.

But what if hundreds of components selected the data from the store, assuming that the data is already there (or at least, being loaded).

  • We could go ahead and add useEffect to all those 100+ components.

    • But some components are still using classes or higher-order components, so they can’t use useEffect. Some of them will get componentDidMount instead.
    • So, this will be a big sweeping change... But didn’t we use selectors to abstract our component away from the implementation detail of which part of the store the data came from?
    • Failure to add the effect in a component may mean that the component may be stuck at displaying the loading screen.
  • We could stop using selectors directly and start using React hooks instead.

    • This looks interesting because hooks can also trigger effects, such as fetching data and subscribing to data source.
    • It seems that this is a general direction that React community is going for.
    • We can also benefit from suspense for data fetching along the way.
    • But again, 100+ components must be refactored to use hooks. This is a breaking change.
    • Some selectors are composed of other selectors. For example, selectParticipants might depend on selectTask + selectUser. That means to properly implement lazy loading across the app, in addition to 100+ components, we would need to convert all selectors into React hooks as well.
  • Add some magic to useSelector (and react-redux’s connect) to allow selectors to fetch data on component’s behalf.

    • Think about it… selecting data from store and fetching data from API is deeply linked. It doesn’t make much sense to select data from the store without someone putting data into it, right? Shouldn’t the act of selecting something from the store signify that the data where we’re selecting needs to be fetched?
    • We modify the notion of selector (which originally means selecting some data from the store) to mean selecting some data from wherever it needs to come from. The selector thus is then responsible for making sure the data is in (or eventually will be in) the store.
    • By doing this, the data selection API stays the same (no breaking changes).

Sketch of the solution

This component will look the same:

function UserCard(props) {
  const user = useSelector(selectUserState(props.userId))
  return <Card title={user.name}>{/* ... */}</Card>
}

However selectUserState will be able to register some effect:

const selectUserState = id => state => {
  useSelectorEffect(ensureUserIsFetched(id))
  return state.users[id]
}

const ensureUserIsFetched = makeParameterizedSelectorEffect(
  // Name, for debugging purposed.
  'ensureUserIsFetched',
  id => context => {
    const { store } = context
    // from here it is like useEffect
    // note: non-ideal code
    if (!store.getState().users[id]) store.dispatch(fetchUser(id))
    return () => {}
  }
)

Instead of calling stock useSelector, we wrap the useSelector API which can keep track of active effects.

Usage

Setting up

This setup will be different in different Redux apps. This section is based atop Redux Toolkit’s Advanced Tutorial.

We first need to create a typed context and export its members, so that it can be used in other modules.

// app-core/index.ts
import { createSelectorEffectContext } from 'magic-selectors'
import { AppDispatch } from 'app/store'

// ...

export const {
  SelectorEffectContextProvider,
  wrapSelectorWithEffect,
  useMagicSelector: useSelector,
  makeNamedSelectorEffect,
  makeParameterizedSelectorEffect,
} = createSelectorEffectContext<AppDispatch>()

Then, replace react-redux’s useSelector with magic-selector’s. This new hook will allow selectors to register an effect.

-import { useSelector } from 'react-redux'
+import { useSelector } from '../app-core'

Next, provide the selector effect context, so that our useSelector can detect it:

 ReactDOM.render(
   <Provider store={store}>
+    <SelectorEffectContextProvider value={store.dispatch}>
       <App />
+    </SelectorEffectContextProvider>
   </Provider>,
   document.getElementById('root')
 )

Refactoring code to use effectful selector.

Instead of tying the selection logic to the component, tie it to the selector.

+const fetchProjectDataEffect = makeParameterizedSelectorEffect(
+  'fetchProjectDataEffect',
+  (projectId: string) => dispatch => {
+    dispatch(fetchProjectDataIfNeeded(projectId))
+  }
+)

 function ProjectPage(props) {
   const { projectId } = props

-  const dispatch = useDispatch()

-  useEffect(() => {
-    dispatch(fetchProjectDataIfNeeded(projectId))
-  }, [projectId, dispatch])

   const projectData = useSelector(selectProjectData(projectId))

   return /* ... */
 }