Skip to content

Commit

Permalink
feat: allow makeReactive to be used on custom hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
hlysine committed Jun 3, 2023
1 parent 331b766 commit 83d414d
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 30 deletions.
42 changes: 42 additions & 0 deletions src/__tests__/component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,48 @@ describe('makeReactive', () => {
const content = await findByText('Test component');
expect(content).toBeTruthy();
});
it('renders custom hooks without crashing', async () => {
const count = ref(0);
const useCount = makeReactive(function useCount() {
return count.value;
});
const Tester = function Tester() {
const count = useCount();
return <p>{count}</p>;
};

const { findByText } = render(<Tester />);
const content1 = await findByText('0');
expect(content1).toBeTruthy();

act(() => {
count.value++;
});

const content2 = await findByText('1');
expect(content2).toBeTruthy();
});
it('renders custom hooks in reactive component without crashing', async () => {
const count = ref(0);
const useCount = makeReactive(function useCount() {
return count.value;
});
const Tester = makeReactive(function Tester() {
const count = useCount();
return <p>{count}</p>;
});

const { findByText } = render(<Tester />);
const content1 = await findByText('0');
expect(content1).toBeTruthy();

act(() => {
count.value++;
});

const content2 = await findByText('1');
expect(content2).toBeTruthy();
});
it('accepts props', async () => {
const Tester = makeReactive(function Tester({
value,
Expand Down
96 changes: 66 additions & 30 deletions src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,36 +62,72 @@ function useReactivity<P extends {}>(component: React.FC<P>) {
return reactivityRef;
}

/**
* Converts a function component into a reactive component.
*
* If your function component makes use of a reactive value, the component has to be wrapped by `makeReactive` so
* that it can re-render when the reactive value changes.
*
* @example
* Simple usage of `makeReactive`.
* ```tsx
* export default makeReactive(function App() {
* const state = useReactive({ count: 1 });
* return <p>{state.count}</p>;
* });
* ```
*
* @example
* Once a component is made reactive, it may access reactive values from any sources, not just from props, contexts
* and hooks.
* ```tsx
* import { reactiveState } from './anotherFile';
*
* export default makeReactive(function App() {
* return <p>{reactiveState.count}</p>;
* });
* ```
* @typeParam T - A React function component.
* @param component The function component to be made reactive.
* @returns A reactive function component.
*/
export const makeReactive = <P extends {}>(
interface MakeReactive {
/**
* Converts a function component into a reactive component.
*
* If your function component makes use of a reactive value, the component has to be wrapped by `makeReactive` so
* that it can re-render when the reactive value changes.
*
* @example
* Simple usage of `makeReactive`.
* ```tsx
* export default makeReactive(function App() {
* const state = useReactive({ count: 1 });
* return <p>{state.count}</p>;
* });
* ```
*
* @example
* Once a component is made reactive, it may access reactive values from any sources, not just from props, contexts
* and hooks.
* ```tsx
* import { reactiveState } from './anotherFile';
*
* export default makeReactive(function App() {
* return <p>{reactiveState.count}</p>;
* });
* ```
* @typeParam P - The props of a React function component.
* @param component The function component to be made reactive.
* @returns A reactive function component.
*/
<P extends {}>(component: React.FC<P>): React.FC<P>;
/**
* Converts a custom hook to be reactive.
*
* If your custom hook makes use of a reactive value, the function has to be wrapped by `makeReactive` so
* that it can trigger a re-render on the component when the reactive value changes.
*
* @example
* Simple usage of `makeReactive`.
* ```tsx
* export default makeReactive(function useCount() {
* const state = useReactive({ count: 1 });
* return state.count;
* });
* ```
*
* @example
* Once a custom hook is made reactive, it may access reactive values from any sources, not just from props, contexts
* and hooks.
* ```tsx
* import { reactiveState } from './anotherFile';
*
* export default makeReactive(function useReactiveState() {
* return reactiveState.count;
* });
* ```
* @typeParam T - A React custom hook function.
* @param component The custom hook to be made reactive.
* @returns A reactive custom hook.
*/
<T extends (...args: any) => any>(
hook: T extends React.FC<any> ? never : T
): T;
}

export const makeReactive: MakeReactive = <P extends {}>(
component: React.FC<P>
): React.FC<P> => {
const ReactiveFC: React.FC<P> = (...args) => {
Expand Down

0 comments on commit 83d414d

Please sign in to comment.