Skip to content

Commit

Permalink
feat: adding use$ which is like useSelector but just a shorter name, …
Browse files Browse the repository at this point in the history
…and $ which is like Memo but a shorter name and with a prop to make it a Computed
  • Loading branch information
jmeistrich committed Nov 4, 2024
1 parent fecbe65 commit 2e1f471
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 19 deletions.
1 change: 1 addition & 0 deletions react.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './src/react/Computed';
export * from './src/react/$';
export * from './src/react/For';
export { usePauseProvider } from './src/react/usePauseProvider';
export * from './src/react/Memo';
Expand Down
45 changes: 29 additions & 16 deletions src/config/enableReactTracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,55 @@ import { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED as ReactInternals }
interface ReactTrackingOptions {
auto?: boolean; // Make all get() calls act as useSelector() hooks
warnUnobserved?: boolean; // Warn if get() is used outside of an observer
warnGet?: boolean; // Warn if get() is used in a component
}

export function enableReactTracking({ auto, warnUnobserved }: ReactTrackingOptions) {
export function enableReactTracking({ auto, warnUnobserved, warnGet }: ReactTrackingOptions) {
const { get } = internal;

if (auto || (process.env.NODE_ENV === 'development' && warnUnobserved)) {
if (auto || (process.env.NODE_ENV === 'development' && (warnUnobserved || warnGet))) {
const ReactRenderContext = createContext(0);

const isInRender = () => {
// If we're already tracking then we definitely don't need useSelector
try {
// If there's no dispatcher we're definitely not in React
// This is an optimization to not need to run useContext. If in a future React version
// this works differently we can change it or just remove it.
const dispatcher = ReactInternals.ReactCurrentDispatcher.current;
if (dispatcher) {
// If there's a dispatcher then we may be inside of a hook.
// Attempt a useContext hook, which will throw an error if outside of render.
useContext(ReactRenderContext);
return true;
}
} catch {} // eslint-disable-line no-empty
return false;
};

const needsSelector = () => {
// If we're already tracking then we definitely don't need useSelector
if (!tracking.current) {
try {
// If there's no dispatcher we're definitely not in React
// This is an optimization to not need to run useContext. If in a future React version
// this works differently we can change it or just remove it.
const dispatcher = ReactInternals.ReactCurrentDispatcher.current;
if (dispatcher) {
// If there's a dispatcher then we may be inside of a hook.
// Attempt a useContext hook, which will throw an error if outside of render.
useContext(ReactRenderContext);
return true;
}
} catch {} // eslint-disable-line no-empty
return isInRender();
}
return false;
};

configureLegendState({
observableFunctions: {
get: (node: NodeInfo, options?: TrackingType | (GetOptions & UseSelectorOptions)) => {
if (needsSelector()) {
if (process.env.NODE_ENV === 'development' && warnUnobserved) {
if (isInRender()) {
console.warn(
'[legend-state] Detected a `get()` call in a React component. It is recommended to use the `use$` hook instead to be compatible with React Compiler: https://legendapp.com/open-source/state/v3/react/react-api/#use$',
);
}
} else if (needsSelector()) {
if (auto) {
return useSelector(() => get(node, options), isObject(options) ? options : undefined);
} else if (process.env.NODE_ENV === 'development' && warnUnobserved) {
console.warn(
'[legend-state] Detected a `get()` call in an unobserved component. You may want to wrap it in observer: https://legendapp.com/open-source/state/react-api/#observer-hoc',
'[legend-state] Detected a `get()` call in an unobserved component. You may want to wrap it in observer: https://legendapp.com/open-source/state/v3/react/react-api/#observer',
);
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/react/$.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { memo, ReactElement, NamedExoticComponent, ComponentProps } from 'react';
import { Computed } from './Computed';

type ComputedWithMemo = (params: {
children: ComponentProps<typeof Computed>['children'];
scoped?: boolean;
}) => ReactElement;

export const $ = memo(Computed as ComputedWithMemo, (prev, next) =>
next.scoped ? prev.children === next.children : true,
) as NamedExoticComponent<{
children: any;
scoped?: boolean;
}>;
14 changes: 12 additions & 2 deletions src/react/Memo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { memo } from 'react';
import { memo, ReactElement, NamedExoticComponent, ComponentProps } from 'react';
import { Computed } from './Computed';

export const Memo = memo(Computed, () => true);
type ComputedWithMemo = (params: {
children: ComponentProps<typeof Computed>['children'];
scoped?: boolean;
}) => ReactElement;

export const Memo = memo(Computed as ComputedWithMemo, (prev, next) =>
next.scoped ? prev.children === next.children : true,
) as NamedExoticComponent<{
children: any;
scoped?: boolean;
}>;
2 changes: 2 additions & 0 deletions src/react/useSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,5 @@ export function useSelector<T>(selector: Selector<T>, options?: UseSelectorOptio

return value;
}

export { useSelector as use$ };
3 changes: 2 additions & 1 deletion tests/react.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useObserveEffect } from '../src/react/useObserveEffect';
import { useSelector } from '../src/react/useSelector';
import { getNode } from '../src/globals';
import { Memo } from '../src/react/Memo';
import { $ } from '../src/react/$';
import { GlobalRegistrator } from '@happy-dom/global-registrator';
import { useComputed } from '../src/react/useComputed';
import { when } from '../src/when';
Expand Down Expand Up @@ -1796,7 +1797,7 @@ describe('Memo', () => {
const Test = function Test() {
return (
<div>
<Memo>{obs$.test}</Memo>
<$>{obs$.test}</$>
</div>
);
};
Expand Down

0 comments on commit 2e1f471

Please sign in to comment.