useSelectorCreator
useSelectorCreator
is a Reflex Hook that lets you memoize and subscribe to a selector factory's state from your component.
const state = useSelectorCreator(factory, ...args);
Reference
useSelectorCreator(factory, ...args)
useSelectorCreator
creates a selector with the given selector factory and args
, and uses the result to subscribe to the state. It returns the selected state.
import { useSelectorCreator } from "@rbxts/roact-reflex";
import { selectTodo } from "./selectors";
function Todo({ id }: Props) {
const todo = useSelectorCreator(selectTodo, id);
// ...
}
The selector returned by the factory will be memoized in useMemo
with the given args
to preserve the cache. If the args
change, the selector will be re-created.
Parameters
factory
- A function that returns a selector for the given arguments....args
- Arguments to pass to the selector factory and memoize with.
Returns
useSelectorCreator
returns the selected value from the root producer's state. If the value changes, the component will re-render.
- Avoid creating new objects and passing them to
...args
, as this will causeuseSelectorCreator
to re-create the selector every re-render. If you need to pass an object, memoize it withuseMemo
.
Usage
Subscribing to state with selector factories
Selector factories are useful for creating selectors that depend on external arguments. For example, a selector that selects a todo by its ID might look like this:
const selectTodo = (id: number) => {
return createSelector(selectTodos, (todos) => {
return todos.find((todo) => todo.id === id);
});
};
Roact Reflex provides useSelector
to connect a function component to selectors, but it's not safe to create a selector inside of a component without memoizing it. This is because the selector will be re-created on every render, which will create a new cache, causing further re-renders.
To solve this, you might try to memoize the selector with the useMemo
hook:
import { selectTodo } from "./selectors";
function Todo({ id }: Props) {
const selector = useMemo(() => {
return selectTodo(id);
}, [id]);
const todo = useSelector(selector);
// ...
}
This works, and it's essentially what useSelectorCreator
does. It creates a selector with the given arguments, memoizes it with the arguments you passed, and subscribes to the selector's state.
import { useSelectorCreator } from "@rbxts/roact-reflex";
import { selectTodo } from "./selectors";
function Todo({ id }: Props) {
const todo = useSelectorCreator(selectTodo, id);
// ...
}
Troubleshooting
useSelectorCreator
is creating a new selector every render
If your selector factory is being called on every render, make sure you're not creating new arrays or objects and passing them to ...args
. This will cause useSelectorCreator
to re-create the selector every render.
Here's an example of what not to do:
import { selectTodos } from "./selectors";
function Todo() {
// 🔴 This array is not memoized
const todos = useSelectorCreator(selectTodos, [1, 2, 3]);
// ...
}
Instead, you should memoize the array with useMemo
:
import { selectTodos } from "./selectors";
function Todo() {
// ✅ This array is memoized
const ids = useMemo(() => [1, 2, 3], []);
const todos = useSelectorCreator(selectTodos, ids);
// ...
}