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

context-with-use-selector

v2.0.0

Published

A custom React context hook that enables efficient state selection and updates with minimal re-renders using a selector function.

Downloads

537

Readme

context-with-use-selector

This library provides a custom context hook that enhances React's context API by enabling efficient state selection through a selector function. With the createContextWithUseSelector function, you can create a context with a custom useSelectorContext hook. This hook allows components to subscribe to only specific parts of the context value, ensuring minimal re-renders and optimized performance. This solution is ideal for managing global state in React applications, especially when you need to access and update specific context values without triggering unnecessary re-renders.

Installation

npm install context-with-use-selector

Usage

import { Dispatch, memo, SetStateAction, useEffect, useRef, useState } from "react";
import createContextWithUseSelector from "context-with-use-selector";

// Define the context type
type IContext = {
  count1: number;
  setCount1: Dispatch<SetStateAction<number>>;
  count2: number;
  setCount2: Dispatch<SetStateAction<number>>;
  count3: number;
  setCount3: Dispatch<SetStateAction<number>>;
  ref: React.RefObject<HTMLDivElement>;
};

// Create the context with initial values
const { Provider, useSelectorContext } = createContextWithUseSelector<IContext>({} as IContext);

export default function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const [count3, setCount3] = useState(0);
  const ref = useRef<HTMLDivElement>(null);

  return (
    <Provider value={{ count1, setCount1, count2, setCount2, count3, setCount3, ref }}>
      <Screen />
    </Provider>
  );
}

const Screen = () => {
  const value = useSelectorContext((s) => s.count1);

  return (
    <>
      <p>Value of count1: {value}</p>
      <Count1 />
      <Count2 />
      <Count3 />
    </>
  );
};

const Count1 = memo(() => {
  const { set, val, ref } = useSelectorContext((s) => ({
    val: s.count1,
    set: s.setCount2,
    ref: s.ref,
  }));

  console.log("Re-render count1");

  useEffect(() => {
    console.log(ref.current);
  }, [ref]);

  return (
    <div ref={ref}>
      value: {val} <br />
      <button onClick={() => set((p) => p + 1)}>Change</button>
    </div>
  );
});

const Count2 = memo(() => {
  const { s: { set, val } } = useSelectorContext((s) => ({
    s: { val: s.count2, set: s.setCount3 },
  }));

  console.log("Re-render count2");

  return (
    <div>
      value: {val} <br />
      <button onClick={() => set((p) => p + 1)}>Change</button>
    </div>
  );
});

const Count3 = memo(() => {
  const { set, val } = useSelectorContext((s) => ({
    val: s.count3,
    set: s.setCount1,
  }));

  console.log("Re-render count3");

  return (
    <div>
      value: {val} <br />
      <button onClick={() => set((p) => p + 1)}>Change</button>
    </div>
  );
});

Explanation

  • Provider: Wraps the part of your app that needs access to the context. It accepts a value prop, which is the state you want to share across components.
  • useSelectorContext: This hook allows you to subscribe to only the part of the context value that your component needs, reducing unnecessary re-renders. The hook accepts a selector function to specify which part of the context should be accessed.
  • Memoization: The memo wrapper ensures that the component only re-renders when the specific state it depends on changes, improving performance.

Usage of useSelectorContext with the Second Parameter

The useSelectorContext hook accepts a second parameter, comparisonType, which determines how the selected value should be compared to its previous value to decide if the component should re-render. This parameter can be one of three options:

  • "shallow": The comparison checks if the two values are the same by reference (using Object.is).
  • "deep1": This performs a shallow comparison of all properties in the selected object or array.
  • "deepN": This performs a deep comparison of all properties and nested values in the selected object or array.

Using this second parameter can help optimize your app further, allowing you to control the granularity of the comparison. By default, comparisonType is set to "deepN", ensuring that the component re-renders only when the deeply nested values change. However, in cases where performance is a concern, you may opt for "shallow" or "deep1" to make comparisons more efficient.

Why Use This Library?

  • Efficient Re-Renders: Components only re-render when the selected part of the context value changes. This reduces unnecessary re-renders and optimizes performance.
  • Granular State Management: You can select specific pieces of state (e.g., count1, setCount2) without needing to access the entire context, leading to more efficient updates.
  • Optimal Performance: By isolating the state each component depends on, this library reduces unnecessary re-renders, improving overall application performance.

Why I created this library:

There is a well-known library called use-context-selector that provides similar functionality. In the example provided, I used that library, but I noticed it causes unnecessary re-computation of component values during reconciliation. This can result in costly performance issues. To address this problem, I created this library to ensure more efficient state selection and minimize unnecessary reconciliation and re-renders.