Skip to content

Commit

Permalink
add comparator support to selectState (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bloomca authored Oct 14, 2024
1 parent 6a8529f commit 5de8ea1
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 3 deletions.
5 changes: 4 additions & 1 deletion docs/api/select-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ parent: API
> This function is also available as `select`
When working with multiple states, often by using [combineState](./combine-state.html), you end up with not the ideal state value representation. E.g. if you want to pass it down as a property to multiple component, you might want to avoid doing the same selector work on all of them, and want to do them at once. It becomes even more important if you want to combine that state with something else.
When working with multiple states, often by using [combineState](./combine-state.html), you end up with not the ideal state value representation. E.g. if you want to pass it down as a property to multiple component, you might want to avoid doing the same selector work on all of them, and want to do them at once. It becomes even more important if you want to combine that state with something else. If you want to control the updates, you can pass a comparator function as the third argument.

To change the state value, you can use `selectState`/`select` functions. Here is an example:

Expand All @@ -25,6 +25,9 @@ function Component() {
const fullNameState = selectState(
combineState(nameState, lastNameState),
([firstName, lastName]) => `${firstName} ${lastName}`
// you can pass a comparator as the third argument
// by default, it uses referential equality:
// function comparator(previous, next) { return previous === next}
);

return (
Expand Down
39 changes: 39 additions & 0 deletions integration-tests/utils/select-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,43 @@ describe("createState", () => {
await user.click(btn1);
expect(await screen.findByText("current value is 24")).toBeVisible();
});

test("allows to use comparator in selectState", () => {
const firstSpy = jest.fn();
const secondSpy = jest.fn();
const state = createState({
firstValue: 1,
secondValue: 2,
});

cleanup = attachComponent({
htmlElement: document.body,
component: createElement(StateComponent),
});

function StateComponent() {
const selectedState = selectState(
state,
(state) => state,
(a, b) =>
a.firstValue === b.firstValue && a.secondValue === b.secondValue
);
const selectedStateNoComparator = selectState(state, (state) => state);
selectedState.trackValue(firstSpy);
selectedStateNoComparator.trackValue(secondSpy);

return null;
}

expect(firstSpy).toHaveBeenCalledTimes(1);
expect(secondSpy).toHaveBeenCalledTimes(1);

state.setValue({ firstValue: 2, secondValue: 5 });
expect(firstSpy).toHaveBeenCalledTimes(2);
expect(secondSpy).toHaveBeenCalledTimes(2);

state.setValue({ firstValue: 2, secondValue: 5 });
expect(firstSpy).toHaveBeenCalledTimes(2);
expect(secondSpy).toHaveBeenCalledTimes(3);
});
});
5 changes: 3 additions & 2 deletions src/utils/select-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ type State<StateType> = ReturnType<typeof createState<StateType>>;

function selectState<F, T>(
state: State<F>,
selector: (state: F) => T
selector: (state: F) => T,
comparator?: (previousSelectedState: T, nextSelectedState: T) => boolean
): State<T> {
const initialValue = selector(state.getValue());

Expand All @@ -15,7 +16,7 @@ function selectState<F, T>(
// we use a function because `selectedState` can be a function itself
newState.setValue(() => selectedState);
},
{ skipFirstCall: true }
{ skipFirstCall: true, comparator }
);

return newState;
Expand Down

0 comments on commit 5de8ea1

Please sign in to comment.