react-wrap-with
TypeScript icon, indicating that this package has built-in type declarations

0.1.0 • Public • Published

react-wrap-with

Creates higher-order component (HOC) for wrapping component in another component. Also reduce code complexity for React Context by mixing components into a new component.

Background

This package targets React developers who build reusable components.

When using React Context or building reusable components, an intermediate component are often needed. This package will help reduce code complexity by wrapping as an intermediate component.

How to use

The following samples assumes a theme is set for the whole component via React Context with corresponding provider component and a HOC function.

Before

import { createContext } from 'react';

const ThemeContext = createContext();

const ThemeProvider = ({ children }) => <ThemeContext.Provider>{children}</ThemeContext.Provider>;

const withTheme = Component => props => (
  <ThemeProvider>
    <Component {...props} />
  </ThemeProvider>
);

export { ThemeProvider, withTheme };

After

import { createContext } from 'react';
import { wrapWith } from 'react-wrap-with';

const ThemeContext = createContext();

const ThemeProvider = ({ children }) => <ThemeContext.Provider>{children}</ThemeContext.Provider>;

const withTheme = wrapWith(ThemeProvider);

export { ThemeProvider, withTheme };

Extracting props

Let's assume a prop named accent need to be extracted and passed to the <ThemeProvider> component in the following manner:

const ButtonWithTheme = withTheme(Button);

// `accent` with value of `"blue"` will be passed to `<ThemeProvider>`, while `text` will be passed to `<Button>`.
render(<ButtonWithTheme accent="blue" text="Submit" />);

Before

const ThemeProvider = ({ accent, children }) => (
  <ThemeContext.Provider value={{ accent }}>{children}</ThemeContext.Provider>
);

const withTheme =
  Component =>
  ({ accent, ...props }) => (
    // "accent" props is extracted and passed to <ThemeProvider> only.
    <ThemeProvider accent={accent}>
      <Component {...props} />
    </ThemeProvider>
  );

After

import { Extract, wrapWith } from 'react-wrap-with';

const ThemeProvider = ({ accent, children }) => (
  <ThemeContext.Provider value={{ accent }}>{children}</ThemeContext.Provider>
);

// Mark "accent" prop for extraction.
const withTheme = wrapWith(ThemeProvider, { accent: Extract });

Props marked with Extract will not be passed to content component. To pass the prop to both the container component and the content component. Please use Spy.

Spying props

Spying is a useful technique to pass the prop to both the container component and the content component.

Before

const ThemeProvider = ({ accent, children }) => (
  <ThemeContext.Provider value={{ accent }}>{children}</ThemeContext.Provider>
);

const withTheme = Component => props => (
  // Pass "accent" prop to <ThemeProvider> without extracting it.
  <ThemeProvider accent={props.accent}>
    <Component {...props} />
  </ThemeProvider>
);

After

import { Spy, wrapWith } from 'react-wrap-with';

const ThemeProvider = ({ accent, children }) => (
  <ThemeContext.Provider value={{ accent }}>{children}</ThemeContext.Provider>
);

// Mark "accent" prop for spying.
const withTheme = wrapWith(ThemeProvider, { accent: Spy });

Both the <ThemeProvider> and the content component will receive the prop accent.

Initializing props

If not every props on the container component need to be extracted or spied, the withProps HOC can help setting some of the props to a fixed value.

import { withProps, wrapWith } from 'react-wrap-with';

const ThemeProvider = ({ accent, children }) => (
  <ThemeContext.Provider value={{ accent }}>{children}</ThemeContext.Provider>
);

const BlueThemeProvider = withProps(Theme, { accent: 'blue' });
const withBlueTheme = wrapWith(BlueThemeProvider);

Referencing

Refs are automatically forwarded to the content component. If { ref: Extract } is passed, the ref prop will reference the container component instead.

In TypeScript, you may need to explicitly set the generic types of forwardRef() function.

type Props = { text: string };

const Button = forwardRef<HTMLButtonElement, Props>(({ text }, ref) => <button ref={ref}>{text}</button>);

Button.displayName = 'Button';

Breaking changes

0.0.3 - Extract props signature changed

Related to pull request #30.

- wrapWith(Container, {}, 'effect')
+ wrapWith(Container, { effect: Extract })

0.0.3 - Initial props is removed and replaced by withProps HOC

Related to pull request #35.

- wrapWith(Container, { effect: 'blink', emphasis: Spy })
+ wrapWith(
    withProps(Container, { effect: 'blink' }),
    { emphasis: Spy }
  )

0.0.3 - No longer accept falsy component type, if falsy is expected, coalesce to <Fragment> instead

Related to pull request #36.

- wrapWith(undefined)
+ wrapWith(undefined || Fragment)

Behaviors

TypeScript: All containers must have props of children

Containers must allow children props. This is because container is going to wrap around another component in parent-child relationship.

If you are seeing the following error in TypeScript, please make sure the container component allow children props. React.PropsWithChildren<> is a helper type to add children to any props. If the component does not have props, use this prop type: { children?: ReactNode }.

Argument of type 'FC<Props>' is not assignable to parameter of type 'false | ComponentType<PropsWithChildren<EmptyProps>> | null | undefined'.
  Type 'FunctionComponent<Props>' is not assignable to type 'FunctionComponent<PropsWithChildren<EmptyProps>>'.
    Types of property 'propTypes' are incompatible.
      ...

Contributions

Like us? Star us.

Want to make it better? File us an issue.

Don't like something you see? Submit a pull request.

Package Sidebar

Install

npm i react-wrap-with

Weekly Downloads

2

Version

0.1.0

License

MIT

Unpacked Size

68.7 kB

Total Files

75

Last publish

Collaborators

  • compulim