diff --git a/README.md b/README.md index 58b0a7fe..0c093d4d 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,8 @@ Coming from `react-use`? Check out our Like `useState`, but its state setter is guarded against setting the state of an unmounted component. - [**`useSet`**](https://react-hookz.github.io/web/?path=/docs/state-useset--example) — Tracks the state of a `Set`. + - [**`useSsrState`**](https://react-hookz.github.io/web/?path=/state-usessrstate--example) — Combination + of hook and context provider that allows to seamlessly declare that application is server-rendered. - [**`useToggle`**](https://react-hookz.github.io/web/?path=/docs/state-usetoggle--example) — Like `useState`, but can only be `true` or `false`. - [**`useThrottledState`**](https://react-hookz.github.io/web/?path=/docs/state-usethrottledstate--example) diff --git a/src/index.ts b/src/index.ts index 5b26ac9d..7e5b8dc2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,6 +35,7 @@ export { useRafState } from './useRafState/useRafState'; export { useRenderCount } from './useRenderCount/useRenderCount'; export { useSafeState } from './useSafeState/useSafeState'; export { useSet } from './useSet/useSet'; +export { useSsrState, SsrStateProvider, SsrStateProviderProps } from './useSsrState/useSsrState'; export { useToggle } from './useToggle/useToggle'; export { useThrottledState } from './useThrottledState/useThrottledState'; export { diff --git a/src/useSsrState/__docs__/example.stories.tsx b/src/useSsrState/__docs__/example.stories.tsx new file mode 100644 index 00000000..196d37ac --- /dev/null +++ b/src/useSsrState/__docs__/example.stories.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { SsrStateProvider, useSsrState, useToggle } from '../..'; + +export const Example: React.FC = () => { + const [ssrDisabled, toggleDisabled] = useToggle(false, true); + + // eslint-disable-next-line react/no-unstable-nested-components + const StateDetector = () => { + return SSR mode {useSsrState() ? 'enabled' : 'disabled'}; + }; + + return ( + +
+ + {' '} + +
+
+ ); +}; diff --git a/src/useSsrState/__docs__/story.mdx b/src/useSsrState/__docs__/story.mdx new file mode 100644 index 00000000..be1cdfd0 --- /dev/null +++ b/src/useSsrState/__docs__/story.mdx @@ -0,0 +1,34 @@ +import {Canvas, Meta, Story} from '@storybook/addon-docs/blocks'; +import {ImportPath} from '../../__docs__/ImportPath'; +import {Example} from './example.stories'; + + + +# useSsrState + +Combination of hook and context provider that allows to seamlessly, and once for all +`@react-hookz/web` hooks, declare that application is server-rendered. All you have to do is wrap +your application with `` component - it will provide static value that can be only +changed via props and accessed over `useSsrState` hook. + +Every isomorphic hook that has to change it's behavior depending on environment will do it basing +off this hook state. + +In case there is no upper context provider, or provider has `disabled` prop - `useSsrState` will +yield false. + +#### Example + + + + + +## Reference + +```ts +function useSsrState(): boolean; +``` + +#### Importing + + diff --git a/src/useSsrState/__tests__/dom.tsx b/src/useSsrState/__tests__/dom.tsx new file mode 100644 index 00000000..e9f88369 --- /dev/null +++ b/src/useSsrState/__tests__/dom.tsx @@ -0,0 +1,35 @@ +import { renderHook } from '@testing-library/react-hooks/dom'; +import React from 'react'; +import { SsrStateProvider, useSsrState } from '../..'; + +describe('useSsrState', () => { + it('should be defined', () => { + expect(useSsrState).toBeDefined(); + }); + + it('should render', () => { + const { result } = renderHook(() => useSsrState()); + expect(result.error).toBeUndefined(); + expect(result.current).toBe(false); + }); + + it('should return false if rendered within disabled state provider', () => { + const wrapper: React.FC = ({ children }) => ( + {children} + ); + const { result } = renderHook(() => useSsrState(), { wrapper }); + + expect(result.error).toBeUndefined(); + expect(result.current).toBe(false); + }); + + it('should return true if rendered within enabled state provider', () => { + const wrapper: React.FC = ({ children }) => ( + {children} + ); + const { result } = renderHook(() => useSsrState(), { wrapper }); + + expect(result.error).toBeUndefined(); + expect(result.current).toBe(true); + }); +}); diff --git a/src/useSsrState/__tests__/ssr.tsx b/src/useSsrState/__tests__/ssr.tsx new file mode 100644 index 00000000..d2be7dd1 --- /dev/null +++ b/src/useSsrState/__tests__/ssr.tsx @@ -0,0 +1,35 @@ +import { renderHook } from '@testing-library/react-hooks/server'; +import React from 'react'; +import { SsrStateProvider, useSsrState } from '../..'; + +describe('useSsrState', () => { + it('should be defined', () => { + expect(useSsrState).toBeDefined(); + }); + + it('should render', () => { + const { result } = renderHook(() => useSsrState()); + expect(result.error).toBeUndefined(); + expect(result.current).toBe(false); + }); + + it('should return false if rendered within disabled state provider', () => { + const wrapper: React.FC = ({ children }) => ( + {children} + ); + const { result } = renderHook(() => useSsrState(), { wrapper }); + + expect(result.error).toBeUndefined(); + expect(result.current).toBe(false); + }); + + it('should return true if rendered within enabled state provider', () => { + const wrapper: React.FC = ({ children }) => ( + {children} + ); + const { result } = renderHook(() => useSsrState(), { wrapper }); + + expect(result.error).toBeUndefined(); + expect(result.current).toBe(true); + }); +}); diff --git a/src/useSsrState/useSsrState.tsx b/src/useSsrState/useSsrState.tsx new file mode 100644 index 00000000..047a1e15 --- /dev/null +++ b/src/useSsrState/useSsrState.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +const context = React.createContext(false); + +export interface SsrStateProviderProps extends React.PropsWithChildren { + disabled?: boolean; +} + +export const SsrStateProvider: React.FC = ({ disabled, children }) => { + return {children}; +}; + +export function useSsrState(): boolean { + return React.useContext(context); +}