Skip to content

Commit

Permalink
feat: add createGlobalState hook generator
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich authored Feb 4, 2020
2 parents f7338da + 08d1393 commit fda7199
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
- [`useMediatedState`](./docs/useMediatedState.md) — like the regular `useState` but with mediation by custom function. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usemediatedstate--demo)
- [`useFirstMountState`](./docs/useFirstMountState.md) — check if current render is first. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usefirstmountstate--demo)
- [`useRendersCount`](./docs/useRendersCount.md) — count component renders. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-userenderscount--demo)
- [`createGlobalState`](./docs/createGlobalState.md) — cross component shared state.[![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-createglobalstate--demo)
<br/>
<br/>
- [**Miscellaneous**]()
Expand Down
32 changes: 32 additions & 0 deletions docs/createGlobalState.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# `useGlobalState`

A React hook which creates a globally shared state.

## Usage

```tsx
const useGlobalValue = createGlobalState<number>(0);

const CompA: FC = () => {
const [value, setValue] = useGlobalValue();

return <button onClick={() => setValue(value + 1)}>+</button>;
};

const CompB: FC = () => {
const [value, setValue] = useGlobalValue();

return <button onClick={() => setValue(value - 1)}>-</button>;
};

const Demo: FC = () => {
const [value] = useGlobalValue();
return (
<div>
<p>{value}</p>
<CompA />
<CompB />
</div>
);
};
```
31 changes: 31 additions & 0 deletions src/createGlobalState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useLayoutEffect, useState } from 'react';
import useEffectOnce from './useEffectOnce';

export function createGlobalState<S = any>(initialState?: S) {
const store: { state: S | undefined; setState: (state: S) => void; setters: any[] } = {
state: initialState,
setState(state: S) {
store.state = state;
store.setters.forEach(setter => setter(store.state));
},
setters: [],
};

return (): [S | undefined, (state: S) => void] => {
const [globalState, stateSetter] = useState<S | undefined>(store.state);

useEffectOnce(() => () => {
store.setters = store.setters.filter(setter => setter !== stateSetter);
});

useLayoutEffect(() => {
if (!store.setters.includes(stateSetter)) {
store.setters.push(stateSetter);
}
});

return [globalState, store.setState];
};
}

export default createGlobalState;
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,4 @@ export { default as useMeasure } from './useMeasure';
export { useRendersCount } from './useRendersCount';
export { useFirstMountState } from './useFirstMountState';
export { default as useSet } from './useSet';
export { createGlobalState } from './createGlobalState';
33 changes: 33 additions & 0 deletions stories/createGlobalState.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { storiesOf } from "@storybook/react";
import React, { FC } from "react";
import { createGlobalState } from "../src";
import ShowDocs from "./util/ShowDocs";

const useGlobalValue = createGlobalState<number>(0);

const CompA: FC = () => {
const [value, setValue] = useGlobalValue();

return <button onClick={() => setValue(value + 1)}>+</button>;
};

const CompB: FC = () => {
const [value, setValue] = useGlobalValue();

return <button onClick={() => setValue(value - 1)}>-</button>;
};

const Demo: FC = () => {
const [value] = useGlobalValue();
return (
<div>
<p>{value}</p>
<CompA />
<CompB />
</div>
);
};

storiesOf("State|createGlobalState", module)
.add("Docs", () => <ShowDocs md={require("../docs/createGlobalState.md")} />)
.add("Demo", () => <Demo />);
21 changes: 21 additions & 0 deletions tests/createGlobalState.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { act, renderHook } from '@testing-library/react-hooks';
import createGlobalState from '../src/createGlobalState';

describe('useGlobalState', () => {
it('should be defined', () => {
expect(createGlobalState).toBeDefined();
});

it('both components should be updated', () => {
const useGlobalValue = createGlobalState(0);
const { result: result1 } = renderHook(() => useGlobalValue());
const { result: result2 } = renderHook(() => useGlobalValue());
expect(result1.current[0] === 0);
expect(result2.current[0] === 0);
act(() => {
result1.current[1](1);
});
expect(result1.current[0] === 1);
expect(result2.current[0] === 1);
});
});

0 comments on commit fda7199

Please sign in to comment.