Skip to main content

useSelector

useSelector is a Reflex Hook that lets you read and subscribe to a producer's state from your component.

const state = useSelector((state) => state.value);

Reference

useSelector(selector, isEqual?)

useSelector subscribes to the root producer with the given selector, and returns the selected state.

import { useSelector } from "@rbxts/roact-reflex";
import { selectTodos } from "./selectors";

function Todos() {
const todos = useSelector(selectTodos);
// ...
}

See more examples below.

Parameters

  • selector - A function that, given the root producer's state, returns a new value.
  • isEqual - An optional function that compares the previous and next values. If the function returns true, the component will not re-render. By default, === is used.

Returns

useSelector returns the selected value from the root producer's state. If the value changes, the component will re-render.

caveat

Selectors that return new objects can cause excessive re-renders. If your selector performs array transformations or returns new objects, it should be memoized with createSelector.


isEqual function

isEqual is a function that compares the current and previous values. If the function returns true, the component will not re-render. By default, === is used.

import { useSelector } from "@rbxts/roact-reflex";
import { selectTodos } from "./selectors";

function Todos() {
const todos = useSelector(selectTodos, shallowEqual);
// ...
}

Parameters

  • current - The current value of the selector.
  • previous - The previous value of the selector.

Returns

isEqual returns true if the current and previous values are equal.


Usage

Subscribing to a producer's state

Sometimes, components may want to access values from the producer's state. We'll go over how to create a basic todo list component that reads the producer's state. Don't forget to include <ReflexProvider> in your app!

Call useSelector from a function component to read a value from the producer's state:

import { useSelector } from "@rbxts/roact-reflex";
import { selectTodos } from "./selectors";

function Todos() {
const todos = useSelector(selectTodos);
// ...
}

Todos will re-render whenever selectTodos returns a new value. Functionally, useSelector subscribes to the producer's state, and re-renders when the selected value changes.

You can then render a list of todos from the selected value:

function Todos() {
const todos = useSelector(selectTodos);

return (
<scrollingframe>
{todos.map((todo) => (
<Todo id={todo.id} />
))}
</scrollingframe>
);
}
pitfall

Selectors that return new objects can cause excessive re-renders. State updates are compared by reference (===), so if your selector creates a new object, Reflex will assume an update happened and re-render. Remember to memoize these selectors with createSelector.


Custom equality comparison

By default, useSelector uses === to compare the previous and next values. You can customize this behavior with the isEqual parameter.

For example, some components might want to receive the latest state only if it's defined and exclude undefined values. You can write an equality function that compares the current and previous values, and returns true if the new value is undefined:

import { useSelector } from "@rbxts/roact-reflex";
import { selectValue } from "./selectors";

function isEqualOrUndefined(current: unknown, previous: unknown) {
return current === previous || current === undefined;
}

function Button() {
const value = useSelector(selectValue, isEqualOrUndefined);
// ...
}

Remember that if the equality function returns true, the component will not re-render for that state update.

The logic can be a bit difficult to follow, so let's break down the two cases:

  • current === previous - If the current and previous values are equal, return true. This is the default behavior.
  • current === undefined - If the current value is undefined, return true. This tells Reflex that the values are "equal," and thus the component will not re-render for undefined values.

Using selector factories

Selectors can receive parameters other than state using selector factories, but using them with useSelector can be unsafe.

Use factories with the useSelectorCreator hook.


Using selectors with curried arguments

Selectors can receive parameters other than state using curried arguments. You can use these selectors with useSelector by wrapping them in a function:

import { useSelector } from "@rbxts/roact-reflex";
import { selectTodo } from "./selectors";

function Todo({ id }: Props) {
const todo = useSelector((state) => selectTodo(state, id));
// ...
}

But you might wonder: why isn't this selector memoized? This is safe because useSelector will only re-render when the selected value changes, and not necessarily when the selector function changes. It's safe to leave the selector function like this.

On the other hand, selector factories should be memoized, because creating a new selector with createSelector on render also creates a new, empty argument cache, causing the selector to re-run when it shouldn't.


Troubleshooting

I'm getting an error: "useSelector must be called from within a ReflexProvider"

This error means that you're trying to use useSelector in a function component that isn't wrapped in a <ReflexProvider>, which throws this error because it uses useProducer internally.

function TodosApp() {
const todos = useSelector(selectTodos);
// ...
}

// 🔴 Don't forget to wrap your root elements in a <ReflexProvider>
Roact.mount(<TodosApp />, container);

Roact Reflex uses Roact contexts to pass the producer to your components and allow them to use Reflex Hooks. If you don't wrap your root elements in a <ReflexProvider>, your components won't be able to access the producer.

If your app or components use Reflex, you should wrap your root elements in a <ReflexProvider>:

function TodosApp() {
const todos = useSelector(selectTodos);
// ...
}

// ✅ You can use the root producer in your components
Roact.mount(
<ReflexProvider producer={producer}>
<TodosApp />
</ReflexProvider>,
container,
);

My component is re-rendering too often

If your component is re-rendering too often, you might be using a selector that returns a new object every time it's called. Remember that Reflex uses reference equality (===) to compare the previous and next values, so if your selector returns a new object, Reflex will assume an update happened and re-render.

Here's an example of a bad selector that returns a new object every time it's called:

const selectTodos = (state: RootState) => {
state.todos;
};

// 🔴 This selector creates a new array every time it's called
const selectTodosDone = (state: RootState) => {
return state.todos.filter((todo) => todo.done);
};

Because selectors are called every time the producer updates, and selectTodosDone works by creating a new array, Reflex assumes that the value changed. This will cause the component to re-render with the new value, even if the underlying todos state didn't change.

To fix this, you can use createSelector to memoize your selectors and prevent unnecessary re-renders:

import { createSelector } from "@rbxts/roact-reflex";

const selectTodos = (state: RootState) => {
state.todos;
};

// ✅ This selector is memoized, and won't re-render unless 'todos' changes
const selectTodosDone = createSelector(selectTodos, (todos) => {
return todos.filter((todo) => todo.done);
});