How to write a custom selector in React
Tags: coding, react, games, architecture
Published: 2020-08-16 18:32:46

---
title: How to write a custom selector in React
published: true
description: Recreating useSelector for an app that doesn't use Redux.
tags: coding, react, games, architecture
cover_image: https://mortoray.files.wordpress.com/2020/08/doors-1767563_1920.jpg
canonical_url: https://mortoray.com/2020/08/16/how-to-write-a-custom-selector-in-react/
---

What's an efficient way to react to global state updates in a React app? If using Redux, you'd use a selector. But I don't use Redux for [my puzzle game](https://edaqa.link/EdaqasRoom-Prototype-1), as I have my own state object. It works similar to redux — I have an immutable state which is completely replaced on modification. All changes in the logical game state are done there.

I used React's contexts to subscribe to state changes for the UI. This works, except parts of my UI are needlessly rerendered. The context update is sent upon any change, even if that part of the UI doesn't care about it. In practice this isn't too bad for my game, since I have few components listening, and pass properties down to memoized components. Still, I don't like inefficiency, and I know `useSelector` from other projects.

How could I get the selector logic in my own React code? I have a game state, and I know which parts I'm interested in, so it should be easy. I thought a long time about how it should be done, a lot more time that it took to finally implement.  I'll cover what I did here, hopefully reducing the time you need to search for solutions.

# What does React offer?

Somewhere in [React](https://reactjs.org/) is a subscription mechanism. That's how the components know to update when something changes. There are two options: context and state. They are both needed to build a selector.

Using a context is well documented. Nonetheless, here's a brief outline of how I used this prior to creating a selector. _My actual code is TypeScript and has a layer of wrapping around this._

```jsx
let GameContext = React.createContext([game_state, game_manager])
let game_manager = get_game_magically_from_global()

function MainComponent() {
	// I use React's state system to track the game state within this component.
	const [game_state, set_game_state] = React.useState(game_manager.get_current_state())
	
	// My game manager needs to tell me when the state changes.
	React.useEffect(() => {
		game_manager.watch_state(set_game_state)
	}, [set_game_state])

	// Provide the current state value to the context to pass down through the tree
	return (
		<GameContext.Provider value={[game_state, game_manager]}>
			<EdaqasRoomUI />
		</GameContext>
	)
}


function NiftyGameItem() {
	const [game_state, game_manager] = React.useContext(GameContext)
	
	const drop = React.useCallback(() =>
		game_manager.drop_item()
	}, [game_manager])
	
	return (
		<img onClick={drop} src={game_state.held_item.image} />
	)
}
```

![](https://mortoray.files.wordpress.com/2020/08/screenshot_20200816_200017.png)

I provide both the current game state and the game manager in the context. The state is for reading and the context for providing feedback. This is similar to [Redux's dispatcher](https://redux.js.org/basics/actions/); my game manager also uses messages to communicate with the state.

## The State

Notice [`useState`](https://reactjs.org/docs/hooks-state.html) in that example as well. For React, updating the context is no different than any other use of the state. The extra aspect of the context is providing that value to the descendents of the component. This is what the `Provider` does.

State can be used without a context as well. Here's a simple example as a reminder.

```jsx
function ExpandInventory() {
	const [expanded, set_expanded] = React.useState(false)
	
	const toggle = React.useCallback(() => {
		set_expanded(!expanded)
	}, [expanded, set_expanded])
	
	return (
		<>
			<CompactView onClick={toggle} />
			{expanded && <GloriousFullView />}
		</>
	)
}
```

When the user clicks on the compact view, the browser calls the toggle function, which modifies the state. When the state is modified React will rerender the control.

JSX files create an illusion of close cooperative harmony between this code, the state, and the [HTML DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model).  The truth is a lot uglier. The HTML goes through React's diff engine, then is assembled into the browser's DOM tree. The callback function lives in the global heap, connected to DOM object, as well as being a closure over the stack frame in which it was created. The closure will be called in response to a user's click, far away from the stack in which the render code was run.

Understanding this structure is the key to making our own selectors. That `set_expanded` function can be called from anywhere and React will figure out how to update the component as a result.

# Too many updates

Any component that needs the game state can call `useContext(GameContext)`. The problem is that all state changes, whether they'd alter the component or not, cause the component to rerender. In my previous example, the `NiftyGameItem` only needs to update when `held_item` changes, yet currently it'll update anytime anything in the state changes. That's pointless and wasteful.

If I were using Redux, I'd use a selector to solve this issue.

```js
const held_item = useSelector( game_state => game_state.held_item )
```

Only when `game_state.held_item` changes will the component rerender.

`useSelector` itself isn't magical. It is essentially a layer in between the state and the control. It will listen to every update to the game state, and run the selection function. But it will only update the component if the result of the selection function changes.

I wanted the same facility for my game state.

# My own selector

`useState` is the primary hook into React's subscription system. At first, I looked for an explicit subscription API. What I wanted to do isn't directly covered in the state docs. But as I mentioned before, understanding how the callbacks, DOM, and state connect, assures me that my approach is correct.

What is the goal? This is what I want my `NiftyGameItem` to look like, ignoring the `onClick` part for a moment.

```
function NiftyGameItem() {
	const held_item = useGameState( gs => gs.held_item )
	
	return (
		<img src={game_state.held_item.image} />
	)
}
```

I only want to update when `held_item` changes. Let's jump right the almost final code.

```ts
type game_selector<T> = ( state : GT.game_state ) => T

export function useGameState<T>( gs : game_selector<T> ): T {
	const [_, game_manager] = React.useContext(GameContext)
		
	const [ state, set_state ] = React.useState<T>(():T => gs(game_manager.current_game_state()))

	React.useEffect(() => {
		const track = {
			current: state,
 		}
		
		return game_manager.listen_game_state( (game_state: GT.game_state) => {
			const next: T = gs(game_state)
			if (track.current != next) {
				track.current = next
				set_state(next)
			}
		})
	}, [game_manager, set_state, gs])
	
	return gs(state)
}
```

```ts
	const [_, game_manager] = React.useContext(GameContext)
```

I get the game manger as I did before, but we'll have to come back and fix something here.

```ts
	const [ state, set_state ] = React.useState<T>(():T => gs(game_manager.current_game_state()))
	...
	return state
```

I prep the state for the component. The game manager needs to provide the current state as it'll be needed when the component first renders, not only when the state updates. Here I don't track the entire game state, only the part that is of interest — the part extracted by the selector.

A selector function, `gs` here, takes the global state as input and returns the part to be watched. My `useGameState` code calls  the `gs` selector function with the global state. The selector in my example is `gs => gs.held_item`, which retrieves only the `held_item`. In the game I have an on-screen indicator showing which item the player is currently holding.

I return the state at the end of the function. In the first call, this will be the initial state. In subsequent calls, for each new render of the control, it'll be the current state.

```ts
		return game_manager.listen_game_state( (game_state: GT.game_state) => {
```

The vital piece of code inside `useEffect` is the call to `listen_game_state`. I added this subscription function to the `game_manager`. The game manager already knows when the state updates, since it has to update the context. Now it updates the context as well as calling all the registered listeners. I'll show this code a bit further below.

```ts
		const track = {
			current: state,
 		}
		
		return game_manager.listen_game_state( (game_state: GT.game_state) => {
			const next: T = gs(game_state)
			if (track.current != next) {
				track.current = next
				set_state(next)
			}
		})
```

Each time the state updates, the caller provided selector function is called to select a part of the state. This is compared to what value it had previously, and only if it has changed do we call the `set_state` function.  If we were to call the `set_state` function every time, then it'd be no better than the caller listening for every state change.

Note the `return`. The `listen_game_state` function returns an unsubscribe function, which will be called whenever the effect is reevaluated, or the component unmounts. The game manager shouldn't hold on to components that are no longer around.

```ts
	React.useEffect(() => {
		...
	}, [game_manager, set_state, gs])
```

The `useEffect` runs once when the control is mounted (or first rendered, more correctly). I have a dependency list of `[game_manager, set_state, gs]` for correctness. Should one of those change the effect needs to be reevaluated to grab the new values. In practice, these dependencies never change.

## useState outside of a component?

It may seem unusual to call the `useState` function in something other than a react component. This type of chaining is allowed and expected. There's nothing special about calling `useState` directly in the component, or inside a function called by the component. React will understand which component it is in and associate it correctly.

>I've not looked into precisely how this works. My assumption is that it's a global value that tracks the current component. The hook functions inspect that variable to figure out where they are and to register the appropriate listeners. I don't see another option, since `useGameState` is a plain TS/JS function — the JSX compiler has no chance to modify it. In threaded languages this stack would need to be thread local, but JS is single threaded (workers, etc. get their own global space, making them effectively thread local).

My selector is a combination of existing React functions: `useState`, `useEffect`, and `useContext`.

## Hold on, there's a problem

I have an issue in the first line of the `useGameState` function:

```ts
	const [_, game_manager] = React.useContext(GameContext)
```

I reused the context from before, the one that provides the game state and the game manager. This is bad. Since it hooks into the game state context, this component will still be updated with every change of the state.

To fix this, I added a new context which contains only the game manager.

```
	const game_manager = React.useContext(GameManagerOnly)
```

This game manager never changes for the life of the game, thus no needless updates will be triggered by the call to `useContext`.

>At this point I gain nothing by storing the game manager singleton in a context. I could simply refer to the global object from my `useGameState` code. However, to behave as a proper React app, I've left it in the context. This may also be important for your project, where the object isn't a singleton.

# Save the batteries

Performance wasn't an issue for my game. Curiousity was part of the reason I wrote the selectors. The selectors do of course help; there were thousands of needless updates to components. Cutting back this processing time should help older machines, as well as saving battery power on tablets.

I'll continue to make optimizations where I see them. It may be inconsequential compared to the massive browser SVG rendering overhead, but there's nothing I can do about that. As my games get more complex the calculation will continue to increase. Keeping it performant can only help long term.

Plus, you know, curiousity. A solid reason to do something.

----

_Check out how this all comes together in my game [Edaqa's Room: Prototype](https://edaqa.link/EdaqasRoom-Prototype-3). A collaborative online escape room full of puzzles, adventure, and probably no vampires._

[![](https://mortoray.files.wordpress.com/2020/07/logo.png)](https://edaqa.link/EdaqasRoom-Prototype-3)

----

# Appendix: Game Manager subscription code

This is the `listen_game_state` code called by `useEffect` in `useGameState`. I've removed details about how I connect to my state object, for simplicity. If you'd like a closer examination of that part, let me know.

```ts
export type game_state_listener = (gs: GT.game_state) => void

export class GameManager implements StateChanged {

	gsl_id = 0
	game_state_listeners: Record<number,game_state_listener> = {}
	.
	.
	.
	listen_game_state( listener: game_state_listener ): ()=>void {
		this.gsl_id += 1
		const nid = this.gsl_id
		this.game_state_listeners[nid] = listener
		
		return () => {
			delete this.game_state_listeners[nid]
		}
	}
```

Subscription queues needn't be complex.  On updates to the game state, the function below is called (part of the `StateChanged interface`).

```ts
	game_state_changed(game_state) {
		if( this.set_game_store ) {
			this.set_game_store(game_state)
		}
		
		for (const listener of Object.values(this.game_state_listeners)) {
			listener(game_state)
		}
	}
```

The first line goes back to the `game_manager.watch_state(set_game_state)` call at the start of this article. It's what updates the context storing the game state.

The loop is what tells all the `useGameState` listeners that something has changed.