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

heksher

v0.1.3

Published

Heksher is like the cool, upgraded sibling of the regular React Context API

Downloads

9

Readme

Heksher is like the cool, upgraded sibling of the regular React Context API. It only triggers re-renders when the stuff you're using actually changes.

Bundle Size Latest Version

Installation

npm install heksher # or yarn add heksher or pnpm add heksher

Heksher requires [email protected] or newer in order to work.

Getting Started

Lets suppose we are creating a game app and we want to tell our entire app the current time (which is fetched from the server).

We will start by creating a CurrentTimeHeksher:

import {createHeksher} from 'heksher';

const CurrentTimeHeksher = createHeksher<Date>();

Then we will create our provider which will fetch the current time from the server and provide it to the app. To do so, we will use the .Provider component that createHeksher gives us.

const CurrentTimeProvider = ({children}: PropsWithChildren) => {
  const [currentTime, setCurrentTime] = useState<Date | null>(null);
  
  useEffect(() => {
    // Some time fetching mechanism
  }, []);
  
  if (!currentTime) {
    return <CurrentTimeLoadingScreen/>;
  }
  
  return (
    <CurrentTimeHeksher.Provider value={currentTime}>
      {children}
    </CurrentTimeHeksher.Provider>
  );
}

Now that we have the component that provides us with the current time, we need a way to get it in our application. For that we will use the .use hook the createHeksher function gives us.

const useCurrentTime = CurrentTimeHeksher.use;

Now lets use our new Heksher:

const Game = () => {
  const currentTime = useCurrentTime();
  
  return (
    <div>
      <TimeDisplay time={currentTime}/>
      {/* The rest of the game*/}
    </div>
  )
}

const App = () => (
  <CurrentTimeProvider>
    <Game/>
  </CurrentTimeProvider>
);

Looks similar huh? Well, that's because Heksher's API was inspired by the ReactContextAPI. You might be asking yourself - why shouldn't I just use it then? Lets answer that question together.

The Problem With React Context

Lets ask ChatGPT

The Problem With React Context

Well... if you are wondering, that's still the case :P

Lets take this code as an example:

interface TimerContextValue {
  elapsedTime: number;
  startTimer: () => void;
}

const TimerContext = createContext<TimerContextValue>({} as TimerContextValue);

const TimerProvider = ({children}: PropsWithChildren) => {
  const [elapsedTime, setElapsedTime] = useState(0);
  
  const startTimer = () => {
    const startDate = Date.now();
    
    setInterval(() => {
      setElapsedTime(Date.now() - startDate)
    }, 100);
  }
  
  return (
    <TimerContext.Provider value={{elapsedTime, startTimer}}>
      {children}
    </TimerContext.Provider>
  )
}

const useTimer = () => useContext(TimerContext);

const StartTimerButton = () => {
  const {startTimer} = useTimer();
  
  return <button onClick={startTimer}>Start Timer</button>
}

How often do you think StartTimerButton will re-render?

  1. One time only
  2. Every 100ms
  3. Every time the button is clicked

...

If you guessed 2 you will be right. Even though StartTimerButton only uses the startTimer function that doesn't change, it will still be re-rendered every 100ms.

Lets try to optimize it:

const TimerProvider = ({children}: PropsWithChildren) => {
  const [elapsedTime, setElapsedTime] = useState(0);

  const startTimer = useCallback(() => {
    const startDate = Date.now();

    setInterval(() => {
      setElapsedTime(Date.now() - startDate)
    }, 100);
  }, []);

  return (
    <TimerContext.Provider value={useMemo(() => ({elapsedTime, startTimer}), [elapsedTime])}>
      {children}
    </TimerContext.Provider>
  )
}

We added a useCallback and a useMemo but we still get that re-render every 100ms why is that? Well, React Context is not very sophisticated - when value changes, every useContext is re-rendered. Even with that useMemo, the value changes every time elapsedTime is changed and a re-render will occur.

What can you do? Use Heksher

Solution

Lets take the same timer example from above but use Heksher instead:

import {createHeksher} from 'heksher';

const TimerHeksher = createHeksher<TimerHeksherValue>();

const TimerProvider = ({children}: PropsWithChildren) => {
  const [elapsedTime, setElapsedTime] = useState(0);

  const startTimer = useCallback(() => {
    const startDate = Date.now();

    setInterval(() => {
      setElapsedTime(Date.now() - startDate)
    }, 100);
  }, []);

  return (
    <TimerHeksher.Provider value={{elapsedTime, startTimer}}>
      {children}
    </TimerHeksher.Provider>
  )
}

const StartTimerButton = () => {
  const {startTimer} = TimerHeksher.use();

  return <button onClick={startTimer}>Start Timer</button>
}

Thats it! We now have an optimized heksher, using it anywhere in our app will only trigger re-renders on the fields we used.

Now our StartTimerButton will only render once.

Best Practices

Memoization

Did you notice the example up there? No useMemo on the value object, kinda weird, huh? Well, Heksher handles that for us, no need to wrap the value with useMemo. But don't forget to memoize all the fields inside the value, that's really important in order to minimize unneeded renders.

Lets see an example:

interface RandomNumberHeksherValue {
  number: number;
  generateNewNumber: (min: number, max: number) => void;
}

const RandomNumberHeksher = createHeksher<RandomNumberHeksherValue>();

const RandomNumberProvider = ({children}: PropsWithChildren) => {
  const [number, setNumber] = useState(-1);
  
  const generateNewNumber = (min: number, max: number) => {
    const randomNumber = Math.floor(Math.random() * (max - min)) + min;
    
    setNumber(randomNumber);
  }

  return (
    <RandomNumberHeksher.Provider value={{number, generateNewNumber}}>
      {children}
    </RandomNumberHeksher.Provider>
  );
}

Notice the issue?

The generateNewNumber function isn't getting memoized; a fresh function is made every time the provider renders, causing a re-render when generateNewNumber is used.

To fix this hiccup, we'll wrap the function with useCallback and feed it into the value object.

const generateNewNumber = useCallback((min: number, max: number) => {
  const randomNumber = Math.floor(Math.random() * (max - min)) + min;

  setNumber(randomNumber);
}, []);

This is much better.

Using Objects Instead Of Tuples

We've all used useState before. I don't know about you, but one of the things that I really like about it is the fact that you can simply give the state value / set state function any name you want by simply destructuring an array.

const [name, setName] = useState('');

Sooo easy!

Well, if you'd want to do that with a Heksher you might want to wait a few versions until an official support is added. As of right now, providing a tuple will re-render every usage of the Heksher, doesn't matter what field of it you accessed, that's in order to allow providing arrays.