Skip to content

RFC: Hybrid implicit/explicit state #5

@EricRibeiro

Description

@EricRibeiro

I'm creating this issue to propose an enhancement over how we pass state to components. Instead of having to pass it for all components, we would only need to pass it to the top-level ones.

For example, currently we have to pass the state to all instances of MenuItem inside Menu:

const state = useMenuState();

<MenuButton {...state}>Open menu</MenuButton>
<Menu {...state}>
  <MenuArrow {...state} />
  <MenuItem {...state}>Item 1</MenuItem>
  <MenuItem {...state}>Item 2</MenuItem>
  <MenuSeparator {...state} />
  <MenuItem {...state}>Item 3</MenuItem>
</Menu>

I'm proposing that we improve this so we don't need to explicitly pass state to MenuItem instances anymore. It would leverage React Context and subscribe to state changes internally.

const state = useMenuState();

<MenuButton {...state}>Open menu</MenuButton>
<Menu {...state}>
  <MenuArrow />
  <MenuItem>Item 1</MenuItem>
  <MenuItem>Item 2</MenuItem>
  <MenuSeparator />
  <MenuItem>Item 3</MenuItem>
</Menu>

Note: MenuItem should keep accepting state being passed directly as props for this reason. But it wouldn't be required anymore.

Motivation

Besides being easier to write code like that, it would also make it easier to colocate state.

In our Menu example, all MenuItem components will re-render when state gets updated. But if we abstract the state into a separate component that receives children, they won't re-render:

import {
  useMenuState,
  Menu as BaseMenu,
  MenuButton,
  MenuItem,
} from \"reakit/Menu\";

function Menu({ button, ...props }) {
  const state = useMenuState();
  return (
    <React.Fragment>
      <MenuButton {...state} ref={button.ref} {...button.props}>
        {(buttonProps) => React.cloneElement(button, buttonProps)}
      </MenuButton>
      <BaseMenu {...state} {...props} />
    </React.Fragment>
  );
}

function App() {
  return (
    <Menu button={<button>Open menu</button>}>
      <MenuItem>Item 1</MenuItem>
      <MenuItem>Item 2</MenuItem>
      <MenuItem>Item 3</MenuItem>
      ...
      <MenuItem>Item 500</MenuItem>
    </Menu>
  );
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions