Skip to content

Commit

Permalink
useSelector warns if it causes a hook error
Browse files Browse the repository at this point in the history
  • Loading branch information
jmeistrich committed Oct 12, 2023
1 parent c342fd6 commit fc90871
Showing 1 changed file with 54 additions and 21 deletions.
75 changes: 54 additions & 21 deletions src/react/useSelector.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { computeSelector, isPrimitive, isPromise, ListenerParams, Selector, trackSelector } from '@legendapp/state';
import {
computeSelector,
isObservable,
isPrimitive,
isPromise,
ListenerParams,
Observable,
Selector,
trackSelector,
WithState,
} from '@legendapp/state';
import React, { useRef } from 'react';
import type { UseSelectorOptions } from './reactInterfaces';
import { useSyncExternalStore } from 'use-sync-external-store/shim';
Expand Down Expand Up @@ -84,28 +94,51 @@ export function useSelector<T>(selector: Selector<T>, options?: UseSelectorOptio
return computeSelector(selector);
}

const ref = useRef<SelectorFunctions<T>>();
if (!ref.current) {
ref.current = createSelectorFunctions<T>(options);
}
const { subscribe, getVersion, run } = ref.current;

// Run the selector
// Note: The selector needs to run on every render because it may have different results
// than the previous run if it uses local state
const value = run(selector) as any;

useSyncExternalStore(subscribe, getVersion, getVersion);

// Suspense support
if (options?.suspense) {
if (isPromise(value)) {
if (React.use) {
React.use(value);
} else {
throw value;
let value;

try {
const ref = useRef<SelectorFunctions<T>>();
if (!ref.current) {
ref.current = createSelectorFunctions<T>(options);
}
const { subscribe, getVersion, run } = ref.current;

// Run the selector
// Note: The selector needs to run on every render because it may have different results
// than the previous run if it uses local state
value = run(selector) as any;

useSyncExternalStore(subscribe, getVersion, getVersion);

// Suspense support
if (options?.suspense) {
// Note: Although it's not possible for an observable to be a promise, the selector may be a
// function that returns a Promise, so we handle that case too.
if (
isPromise(value) ||
(!value &&
isObservable(selector) &&
!(selector as unknown as Observable<WithState>).state.isLoaded.get())
) {
if (React.use) {
React.use(value);
} else {
throw value;
}
}
}
} catch (err: unknown) {
if (
(process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') &&
(err as Error)?.message?.includes('Rendered more')
) {
console.warn(
`[legend-state]: You may want to wrap this component in \`observer\` to fix the error of ${
(err as Error).message
}`,
);
throw err;
}
}

return value;
Expand Down

0 comments on commit fc90871

Please sign in to comment.