RFC: Hooks
See original GitHub issueUPDATE: Some gists with ideas:
- https://gist.github.com/diegohaz/f0b3b198745e381c37c60f317212d546
- https://gist.github.com/mattfwood/79c8fb52abfeeb71b6de32c53cdf1bca
React hooks were announced and will be available in React 16.7. I’m opening this issue to talk about a future addition (or maybe an eventual replacement) on Reakit’s API.
My initial idea is to expose hooks instead of components and let the user choose the underlying element in a more straightforward way. The hooks will return objects with html attributes that can be passed direct to elements.
Today:
import { Popover } from "reakit";
const MyPopover = () => (
<Popover.Container initialState={{ visible: true }}>
{popover => (
<Popover.Toggle {...popover}>
Toggle
<Popover placement="top" {...popover}>
<Popover.Arrow />
Popover
</Popover>
</Popover.Toggle>
)}
</Popover.Container>
);
Tomorrow:
import { usePopover } from "reakit";
function MyPopover() {
const { toggle, popover, arrow } = usePopover({ placement: "top", visible: true });
return (
<button {...toggle}>
Toggle
<div {...popover}>
<div {...arrow} />
Popover
</div>
</button>
);
}
The returned object would look like this:
{
toggle: {
className: "HiddenToggle-kd7ngs-0",
onClick: () => toggle(),
"aria-expanded": false,
"aria-haspopup": true
},
popover: {
className: "Popover-s6tfv8-0",
"aria-hidden": true,
"data-placement": "bottom",
"role": "group"
},
arrow: {
className: "PopoverArrow-sc-1tkjw1-0",
children: (
<svg viewbox="0 0 30 30">
<path class="stroke"
d="M23.7,27.1L17,19.9C16.5,19.3,15.8,19,15,19s-1.6,0.3-2.1,0.9l-6.6,7.2C5.3,28.1,3.4,29,2,29h26 C26.7,29,24.6,28.1,23.7,27.1z"
>
</path>
<path class="fill"
d="M23,27.8c1.1,1.2,3.4,2.2,5,2.2h2H0h2c1.7,0,3.9-1,5-2.2l6.6-7.2c0.7-0.8,2-0.8,2.7,0L23,27.8L23,27.8z"
>
</path>
</svg>
)
}
}
The hooks would be decoupled so the users could use their own state handlers with it (just as they can do today by replacing Popover.Container
for something else):
import { useState } from "react";
import { usePopover, usePopoverState } from "reakit";
function MyPopover() {
// const state = usePopoverState({ visible: true });
const [visible, setVisible] = useState(true);
const state = {
visible,
toggle: () => setVisible(!visible),
show: () => setVisible(true),
hide: () => setVisible(false)
};
const { toggle, popover, arrow } = usePopover({ placement: "top", ...state });
return (
<button {...toggle}>
Toggle
<div {...popover}>
<div {...arrow} />
Popover
</div>
</button>
);
}
Composing multiple hooks could be as simple as spreading two objects on elements:
<div {...overlay} {...portal} />
But, since they can have conflicting attributes (like both having ref
, onClick
etc.), we could expose a compose
helper that would guarantee that those attributes would be composed, not overwritten.
Today:
import { Overlay, Portal } from "reakit";
const Modal = () => (
<Overlay.Container>
{overlay => (
<Overlay as={Portal} {...overlay}>Overlay</Overlay>
)}
</Overlay.Container>
);
Tomorrow:
import { useOverlay, usePortal, compose } from "reakit";
function Modal() {
const { portal } = usePortal();
const { overlay } = useOverlay();
return <div {...compose(portal, overlay)}>Overlay</div>;
}
Well, these are just my initial thoughts, and actually I’m not sure if they make sense, so feedback is really appreciated.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:13
- Comments:12 (12 by maintainers)
Top GitHub Comments
Just my 2 cents - I was experimenting with hooks a little bit, looking also through many implementations in the wild and I came to conclusion that for ref-related hooks it’s best to force people giving a ref to a library. It just seems like it composes best. Example:
No - I’m not aware of any perf-related downsides to your solution (other than the hooks list gets longer with each ref per component, but it’s probably insignificant).
I just think that my proposed way of doing things composes better, because… you dont have to compose in any special way 😅 Just pass an argument and you are ready to go. It also allows passing a ref received from prop directly to the hook, so it just makes more sense to me (also when discussing this on twitter people seemed to lean towards my solution). I agree this looks quite different - because without hooks I probably wouldn’t propose a similar solution, but with hooks it somehow changes my mind about this and after thinking about it a little bit it just seems like a better solution, even if you have to do a little bit of plumbing in your render function.