1.1.1 โ€ข Published 4 years ago

react-hoist-hook-class v1.1.1

Weekly downloads
1
License
MIT
Repository
github
Last release
4 years ago

react-hoist-hook-class

Don't refactor your code, just install this library ๐Ÿ˜

NPM JavaScript Style Guide

โš™๏ธ Install

npm:

npm install --save react-hoist-hook-class

yarn:

yarn add react-hoist-hook-class

๐Ÿงช What is this library for?

This library makes hook and class interoperability smooth, written in TypeScript, fully typed to ensure each component expects its right props ๐ŸŽฏ

(Works with React & React-Native, no external libraries needed ๐Ÿš€)

๐Ÿงฌ Exported methods

RenderHook

It uses a render props pattern that helps you to use hooks inside Class component.

How it works?

  • RenderHook will render hook props into your class component, this component allows to be more flexible while using hooks

example:

import React, { PureComponent } from 'react';
import { View, Text, Pressable } from 'react-native';
import { RenderHook } from 'react-hoist-hook-class';

class ClassRenderHook extends PureComponent {
  render() {
    return (
      <RenderHook hook={useXXX} args={[ARGUMENTS_TO_PASS_TO_THE_HOOK]}>
        {({ returned, values, from, hook }) => (
          <View>
            <Text>
              YOUR CODE: {returned} - {values}
            </Text>
            <Pressable onPress={from}>
              <Text>Your custom {hook}</Text>
            </Pressable>
          </View>
        )}
      </RenderHook>
    );
  }
}

withHook

Inject returned values from a custom hook into your class props

How it works?

  • withHook accepts 2 functions
    • First one accepts a Class component as a first argument, second and beyond can be props to be injected.
    • Second one accepts a hook as a first argument, second and beyond can be hooks arguments.

example:

import React, { PureComponent, useEffect, useState } from 'react';
import { View } from 'react-native';
import { withHook } from 'react-hoist-hook-class';

function useWhatever(defaultValue: any) {
  const [whatever, setWhatever] = useState<any>(defaultValue);

  useEffect(() => {
    setInterval(setWhatever, 3000, defaultValue);
  }, [defaultValue]);

  return {
    whatever,
    setWhatever
  };
}

class ClassComponent extends PureComponent<ReturnType<typeof useWhatever>> {
  componentDidMount() {
    setInterval(this.props.setWhatever, 5000, 'For Ever');
  }

  render() {
    const { date } = this.props;

    return (
      <View>
        <Text>Current date: {date}</Text>
      </View>
    );
  }
}

// export const ClassUsingHook = ([props]) => withHook(ClassComponent [, props])(useHook [, args]);
export const ClassUsingHook = () =>
  withHook(ClassComponent)(useWhatever, 'Not For Ever');

withUIHook

Inject Class component as a children to your function component in order to pass custom props to it

How it works?

  • First function accepts your hook component and the second one your class component

example:

import React, { PureComponent } from 'react';
import { withUIHook } from 'react-hoist-hook-class';

// don't forget to overload children prop if you are using TS :)
interface ComponentProps {
  children(): React.ReactNode;
  children(props: YourProps): React.ReactNode;
}

function FunctionComponent({ children }: ComponentProps) {
  const [anyState, setAnyState] = useState<any>();

  const handleAnyStateAction = (whatever: any) => {
    setAnyState(whatever);
  };

  return (
    <div>
      <h1>Hello from function component! :) {anyState}</h1>
      {children({ handleAnyStateAction })}
    </div>
  );
}

class ClassComponent extends PureComponent<YourProps, YourState> {
  constructor(props: YourProps) {
    super(props);
    this.state = {
      yourStringState: 'Hello from class state! :)'
    };
  }

  doClassThings = () => {
    const { handleAnyStateAction: hookAction } = this.props;

    hookAction(this.state.yourStringState);
  };

  render() {
    return (
      <div>
        <button onClick={this.doClassThings}>Do something</button>
      </div>
    );
  }
}

export const HookRenderingClass = () =>
  withUIHook(FunctionComponent)(ClassComponent);

๐Ÿคนโ€โ™‚๏ธ Visual example (GIF)

ExampleHoistHookClass

๐Ÿ‘จโ€๐Ÿ’ป Basic usage (render props)

RenderHook (no args)

import React, { PureComponent, useState } from 'react';
import { RenderHook } from 'react-hoist-hook-class';

function useCounter(defaultCounter: number = 0, boost: number = 1) {
  const [counter, setCounter] = useState<number>(defaultCounter);

  return {
    counter,
    increment: () => setCounter((c) => c + boost),
    decrement: () => setCounter((c) => c - boost),
    reset: () => setCounter(0)
  };
}

export class ClassWithRenderProps extends PureComponent {
  render() {
    return (
      <RenderHook hook={useCounter}>
        {({ counter, increment, decrement, reset }) => (
          <div>
            <h1>COUNTER CLASS RENDER PROPS: {counter}</h1>
            <button type='button' onClick={increment}>
              INCREMENT
            </button>
            <button type='button' onClick={decrement}>
              DECREMENT
            </button>
            <button type='button' onClick={reset}>
              RESET
            </button>
          </div>
        )}
      </RenderHook>
    );
  }
}

RenderHook (with args)

If there are no more options, you can use bind to bind arguments to the hook

import React, { PureComponent, useState } from 'react';
import { RenderHook } from 'react-hoist-hook-class';

function useCounter(
  defaultCounter: number = 0,
  {
    incrementBy = 1,
    decrementBy = 1
  }: { incrementBy?: number; decrementBy?: number } = {}
) {
  const [counter, setCounter] = useState<number>(defaultCounter);

  return {
    counter,
    increment: () => setCounter((c) => c + incrementBy),
    decrement: () => setCounter((c) => c - decrementBy),
    reset: () => setCounter(0)
  };
}

export class ClassWithRenderProps extends PureComponent {
  render() {
    return (
      <RenderHook
        hook={useCounter}
        args={[10, { incrementBy: 10, decrementBy: 5 }]}
      >
        {({ counter, increment, decrement, reset }) => (
          <div>
            <h1>COUNTER CLASS RENDER PROPS: {counter}</h1>
            <button type='button' onClick={increment}>
              INCREMENT
            </button>
            <button type='button' onClick={decrement}>
              DECREMENT
            </button>
            <button type='button' onClick={reset}>
              RESET
            </button>
          </div>
        )}
      </RenderHook>
    );
  }
}

๐Ÿ‘ฉโ€๐Ÿ’ป Basic usage (hocs)

withHook

import React, { PureComponent, useState } from 'react';
import { withHook } from 'react-hoist-hook-class';

function useCounter(defaultCounter: number = 0, boost: number = 1) {
  const [counter, setCounter] = useState<number>(defaultCounter);

  return {
    counter,
    increment: () => setCounter((c) => c + boost),
    decrement: () => setCounter((c) => c - boost),
    reset: () => setCounter(0)
  };
}

type ClassProps = ReturnType<typeof useCounter>;

class ClassCounterHook extends PureComponent<ClassProps> {
  render() {
    const { counter, increment, decrement, reset } = this.props;

    return (
      <div>
        <h1>CLASS COUNTER VALUE: {counter}</h1>
        <button onClick={increment}>INCREMENT</button>
        <button onClick={decrement}>DECREMENT</button>
        <button onClick={reset}>RESET</button>
      </div>
    );
  }
}

export const ExampleWithHook = () =>
  withHook(ClassCounterHook)(useCounter, 10, 20);

withUIHook

import React, { PureComponent, useState } from 'react';
import { withUIHook } from 'react-hoist-hook-class';

function useCounter(defaultCounter: number = 0, boost: number = 1) {
  const [counter, setCounter] = useState<number>(defaultCounter);

  return {
    counter,
    increment: () => setCounter((c) => c + boost),
    decrement: () => setCounter((c) => c - boost),
    reset: () => setCounter(0)
  };
}

type ClassProps = ReturnType<typeof useCounter>;

interface CounterHookProps {
  children(): React.ReactNode;
  children(props: ClassProps): React.ReactNode;
}

function CounterHookSharedVersion({ children }: CounterHookProps) {
  const { counter, increment, decrement, reset } = useCounter(0);

  return (
    <div className='counter__div_hook'>
      <h1>COUNTER HOOK COMPONENT SHARED VALUE: {counter}</h1>
      {children({ counter, increment, decrement, reset })}
    </div>
  );
}

class CounterClassSharedVersion extends PureComponent<ClassProps & OtherProps> {
  render() {
    const { counter, increment, decrement, reset } = this.props;

    return (
      <div>
        <h1>CLASS SHARED COUNTER VALUE: {counter}</h1>
        <button onClick={increment}>INCREMENT</button>
        <button onClick={decrement}>DECREMENT</button>
        <button onClick={reset}>RESET</button>
      </div>
    );
  }
}

export const ClassWithUIHook = () =>
  withUIHook(CounterHookSharedVersion)(CounterClassSharedVersion);

Advanced usage

import React, { PureComponent, useState } from 'react';
import { withHook, withUIHook } from 'react-hoist-hook-class';

function useCounter(defaultCounter: number = 0, boost: number = 1) {
  const [counter, setCounter] = useState<number>(defaultCounter);

  return {
    counter,
    increment: () => setCounter((c) => c + boost),
    decrement: () => setCounter((c) => c - boost),
    reset: () => setCounter(0)
  };
}

type CounterHook = ReturnType<typeof useCounter>;

type HookChildrenProps = {
  resetUIHook: () => void;
};

type ClassProps = CounterHook & HookChildrenProps;

interface HookProps {
  children(): React.ReactNode;
  children(props: HookChildrenProps): React.ReactNode;
}

class ClassWithCounterHookAndPropsFromHook extends PureComponent<ClassProps> {
  render() {
    const { counter, increment, decrement, reset, resetUIHook } = this.props;

    return (
      <div>
        <h1>CLASS COUNTER VALUE: {counter}</h1>
        <button onClick={resetUIHook}>RESET HOOK UI COUNTER</button>
        <button onClick={increment}>INCREMENT</button>
        <button onClick={decrement}>DECREMENT</button>
        <button onClick={reset}>RESET</button>
      </div>
    );
  }
}

const ClassWithCounterHookUI = (props: HookChildrenProps) =>
  withHook(ClassWithCounterHookAndPropsFromHook, props)(useCounter, 0, 10);

function HookRenderingClassPassingCustomProps({ children }: HookProps) {
  const { counter, increment, reset: resetUIHook } = useCounter(10, 50);

  return (
    <div>
      <h1>HOOK COUNTER VALUE: {counter}</h1>
      <button onClick={increment}>INCREMENT</button>
      {children({ resetUIHook })}
    </div>
  );
}

export const AdvancedUsage = () =>
  withUIHook(HookRenderingClassPassingCustomProps)(ClassWithCounterHookUI);

License

MIT ยฉ x0s3