npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-hooks-compose

v2.0.20

Published

Compose React Hooks

Downloads

2,273

Readme

react-hooks-compose

Build Status Coverage Status NPM Version

Installation

npm i react-hooks-compose

Why react-hooks-compose?

react-hooks-compose provides an ergonomic way to decouple hooks from the components that use them.

React Hooks are great. They encapsulate state logic and make it more reusable. But what if you have pure presentational components that you want to use with different state? What if you want to test your presentaional component in isolation?

React Hooks invert the Container/Presenter pattern, putting the container inside the presenter. This makes it hard to use the same presentational component with different hooks, and clunky to test presentational components by themselves.

One option:

import { Presenter } from './presenter';
import { useCustomHook } from './hooks';

const Wrapper = () => {
  const { foo, bar } = useCustomHook();
  return <Presenter foo={foo} bar={bar} />;
};

export default Wrapper;

This works fine, but you end up with an extra component just to connect the hook to the Presenter. If you want to test the presenter in isolation, you have to export it separately. there must be a better way!

Basic Usage

composeHooks passes values from hooks as props, and allows you to pass any other props as normal. This allows you to export the hook, stateful component, and purely presentational component separately.

import composeHooks from 'react-hooks-compose';

const useForm = () => {
  const [name, setName] = useState('');
  const onChange = e => setName(e.target.value);
  return { name, onChange };
};

// Other props (in this case `icon`) can be passed in separately
const FormPresenter = ({ name, onChange, icon }) => (
  <div className="App">
    <div>{icon}</div>
    <h1>Hello, {name}!</h1>
    <input value={name} onChange={onChange} />
  </div>
);

export default composeHooks({ useForm })(FormPresenter);

You can think of composeHooks like react-redux's connect HOC. For one thing, it creates an implicit container. You can think of the object passed into composeHooks as mapHooksToProps, similar to the object form of mapDispatchToProps.

Compose multiple hooks:

const Presenter = ({ name, onChange, foo, bar, value }) => (
  <div className="App">
    <h1>Hello, {name}!</h1>
    <p>Context value is {value}</p>
    <p>
      foo is {foo}, bar is {bar}
    </p>
    <input value={name} onChange={onChange} />
  </div>
);

export default composeHooks({
  useForm,
  useFooBar,
  value: () => useContext(MyContext), // Usage with `useContext`
})(FormPresenter);

Usage with useState

If you compose with useState directly (i.e. the prop is an array), the prop will remain an array and should be destructured before use:

const FormPresenter = ({ nameState: [name, setName] }) => (
  <div className="App">
    <h1>Hello, {name}!</h1>
    <input value={name} onChange={e => setName(e.target.value)} />
  </div>
);

export default composeHooks({
  nameState: () => useState('Calvin'),
})(FormPresenter);

Usage with useEffect

useEffect is supported - the most common usage would be in a custom hook. For example:

const usePostData = data => {
  const [postStatus, setPostStatus] = useState(SUCCESS);

  useEffect(() => {
    setPostStatus(LOADING);
    postData(data).then(() => {
      setPostStatus(SUCCESS);
    }).catch(err => {
      setPostStatus(ERROR);
    });
  }, [data]);

  return { postStatus };
};

const App = ({ postStatus }) => { ... };

export default compose({ usePostData })(App);

Pass in props for initial values

If your hooks need access to props to set their initial values, you can pass a function to composeHooks. This function receives props as an argument, and should always return an object:

const useForm = (initialValue = '') => {
  const [value, setValue] = useState(initialValue);
  const onChange = e => setValue(e.target.value);
  return { value, onChange };
};

const FormContainer = composeHooks(props => ({
  useForm: () => useForm(props.initialValue),
}))(FormPresenter);

<FormContainer initialValue="Susie" />;

Testing

composeHooks is great for testing. Any props you pass in will override the hooks values, so you can test the presenter and container with a single export:

// band-member.js
const BandMember = ({singer, onClick}) => {...} // <-- Presenter

export default composeHooks({ useName })(BandMember);

// band-member.test.js
it('returns Joey if singer is true', () => {
  // Pass in a `singer` boolean as with any presentational component.
  // Containers don't usually allow this.
  const {getByLabelText} = render(<BandMember singer />);
  expect(getByLabelText('Name').textContent).toBe('Joey');
});

it('updates name to Joey when Get Singer button is clicked', () => {
  // If you don't pass in props, the component will use the hooks provided
  // in the module. In this case, `useName` returns `singer` and `onClick`.
  const {getByLabelText} = render(<BandMember />);
  expect(getByLabelText('Name').textContent).toBe('Johnny');
  fireEvent.click(getByText('Get Singer'));
  expect(getByLabelText('Name').textContent).toBe('Joey');
})