Skip to content

Commit

Permalink
added use-sync-external-store
Browse files Browse the repository at this point in the history
  • Loading branch information
atellmer committed Nov 29, 2022
1 parent bae9490 commit fdf02d3
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 15 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import {
useReducer,
useReactiveState,
useDeferredValue,
useSyncExternalStore,
} from '@dark-engine/core';
import { render, createRoot, createPortal, useStyle } from '@dark-engine/platform-browser';
```
Expand Down Expand Up @@ -1024,6 +1025,22 @@ const Checkbox = createComponent(() => {
});
```

#### useSyncExternalStore

The hook is useful for synchronizing render states with an external state management library such as Redux.

```tsx
import { useSyncExternalStore } from '@dark-engine/core';
```

```tsx
const App = createComponent(() => {
const state = useSyncExternalStore(store.subscribe, store.getState); // redux store

return <div>{state.isFetching ? 'loading...' : 'ola! 🤪'}</div>;
});
```

Thanks everyone!

# LICENSE
Expand Down
31 changes: 16 additions & 15 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,28 @@ export * from './ref';
export * from './scope';
export * from './shared';
export * from './suspense';
export * from './use-callback';
export * from './use-context';
export * from './use-deferred-value';
export { useCallback } from './use-callback';
export { useContext } from './use-context';
export { useDeferredValue } from './use-deferred-value';
export { useEffect } from './use-effect';
export { useLayoutEffect } from './use-layout-effect';
export { useInsertionEffect } from './use-insertion-effect';
export * from './use-error';
export * from './use-event';
export * from './use-imperative-handle';
export * from './use-memo';
export * from './use-reducer';
export * from './use-ref';
export * from './use-update';
export * from './use-state';
export * from './use-reactive-state';
export * from './use-id';
export * from './view';
export * from './constants';
export { useError } from './use-error';
export { useEvent } from './use-event';
export { useImperativeHandle } from './use-imperative-handle';
export { useMemo } from './use-memo';
export { useReducer } from './use-reducer';
export { useRef } from './use-ref';
export { useUpdate } from './use-update';
export { useState } from './use-state';
export { useReactiveState } from './use-reactive-state';
export { useId } from './use-id';
export { useSyncExternalStore } from './use-sync-external-store';
export { walkFiber } from './walk';
export { unmountRoot } from './unmount';
export { batch } from './batch';
export * from './view';
export * from './constants';

export const version = process.env.VERSION;

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/use-sync-external-store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './use-sync-external-store';
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/** @jsx h */
import { render } from '@dark-engine/platform-browser';
import { createComponent } from '../component';
import { useSyncExternalStore } from './use-sync-external-store';

let host: HTMLElement = null;

jest.useFakeTimers();

beforeEach(() => {
host = document.createElement('div');
});

function createStore<T>(initialState: T) {
let state = initialState;
const listeners = new Set<() => void>();

const getState = () => state;

const setState = (fn: (prevState: T) => T) => {
state = fn(state);
listeners.forEach(fn => fn());
};

const subscribe = (listener: () => void) => {
listeners.add(listener);

return () => listeners.delete(listener);
};

return { getState, setState, subscribe };
}

describe('[use-sync-external-store]', () => {
test('works correctly', () => {
const store = createStore(0);
let state: number;

const App = createComponent(() => {
state = useSyncExternalStore(store.subscribe, store.getState);

return null;
});

render(App(), host);
jest.runAllTimers();
expect(state).toBe(store.getState());
expect(state).toBe(0);

store.setState(x => x + 1);
jest.runAllTimers();
expect(state).toBe(store.getState());
expect(state).toBe(1);

store.setState(x => x + 1);
jest.runAllTimers();
expect(state).toBe(store.getState());
expect(state).toBe(2);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useState } from '../use-state';
import { useEffect } from '../use-effect';

type Sunscribe = (cb: () => void) => Unsubscribe;
type Unsubscribe = () => void;

function useSyncExternalStore<T>(subscribe: Sunscribe, getSnapshot: () => T) {
const [state, setState] = useState(getSnapshot());

useEffect(() => {
const unsubscribe = subscribe(() => {
setState(getSnapshot());
});

return () => unsubscribe();
}, [getSnapshot]);

return state;
}

export { useSyncExternalStore };

0 comments on commit fdf02d3

Please sign in to comment.