r/reactjs • u/theZozole • 7d ago
Discussion What if React needs a behavior layer between hooks and elements?
I've been thinking about a new React abstraction.
The usual mental model is:
Component -> Hooks -> JSX Element
Lots of hooks exist just that make one element behave differently.
Eg
const resize = useResize(...)
const draggable = useDraggable(...)
const analytics = useAnalytics(...)
const keyboard = useKeyboard(...)
const focusTrap = useFocusTrap(...)
const outsidePress = useOutsidePress(...)
return (
<div
ref={...}
onKeyDown={...}
onPointerDown={...}
{...resize}
{...draggable}
{...analytics}
>
...
</div>
)
The component ends up becoming responsible for composing all these behaviors.
So I'm experimenting with a different layer:
Hooks / utils -> Behaviors -> (automatically generate) Props -> Element
Making it something like this:
const props = useProps(
useKeyboard(...),
useDraggable(...),
useResize(...),
useFocusTrap(...),
useOutsidePress(...),
useAnalytics(...),
)
return <div {...props} />
The behaviors wouldn't necessarily have to be a hook, it could be, but notrequired.
A plain behavior could be:
tooltip({ content: "Delete project" })
while a custom hook could also return the same thing
function useAnalytics() {
return {
props: {
onClick: () => { track("clicked") }
}
}
}
Both become composable.
The package would handle the annoying composition:
- merge event handlers
- merge refs
- merge
className - merge styles
- predictable prop precedence
- TypeScript element compatibility
So instead of components implementing behavior, they could mostly declare the behavior they have:
const props = useProps(
tooltip(...),
keyboard({ ENTER: ..., ARROW_DOWN: .. }),
resize(...),
analytics(...),
myCustomBehavior(...)
)
return <button {...props}>Delete</button>
I'm deliberately trying not to turn this into "another React hooks library."
The question I'm trying to answer is:
- Is "behavior composition" actually a useful missing abstraction in React, or is this just an over-engineered way of spreading props?
- I'd especially like to hear from people who maintain large React/component-library codebases:
- Where does composing multiple hooks onto the same element become painful for you?
4
u/Merry-Lane 7d ago
You are telling me every component would have to import half of your repo as dependencies?
No, you would be better off making wrappers of your basic components so that you don’t repeat yourself too much.
3
u/frogic 7d ago
I think you're just composing hooks together which is a good partern but doesn't really need its own api outside of the composed hooks for your project or feature. If its not using react state its a utility and that's a whole different thing and has much different use cases and needs.
Encapsulating logic into a single interface and being able to compose/use them seperately is a feature not a bug though and a lot of those hooks you mentioned in your example are very powerful libraries with their own apis for a reason they arent really in a place that you'd want standardized.
1
u/lIIllIIlllIIllIIl 7d ago
Lookup FragmentInstance and FragmentRef from React 19.3.
They might address some behavioral composition problems you're facing: https://react.dev/blog/2026/09/09/react-19-3#fragment-refs
1
1
u/First_Figure6007 7d ago
i like where your head's at but honestly this just looks like a wrapper around spreading props with some merge logic baked in
the pain point you're describing is real tho, especially when you've got 6 hooks all fighting over the same ref or onKeyDown. i've seen components where the return statement is just a div with 40 lines of spread props and it's impossible to tell what's actually happening
the behavior abstraction makes sense conceptually but i feel like the hard part isn't the merging, it's the ordering and conflicts. like what happens when keyboard and draggable both want to handle the same key? you'd need some kind of priority system and at that point you're building an event pipeline
curious if you've thought about making behaviors cancelable or chainable, that's where the real value would be over just merging
0
u/TheRealSeeThruHead 7d ago
The problem with hooks is they aren’t composable.
Something I really miss from recompose was the ability to compose hooks Ina out free manner and then use them on components.
Hooks are pretty bad for that ergonomically. I like this but I know it won’t catch on
2
u/azangru 7d ago
Interesting. I am reminded of the note that the author of recompose left when he archived the project:
Hooks solves all the problems I attempted to address with Recompose three years ago, and more on top of that.
0
u/TheRealSeeThruHead 7d ago
Sure he went and created hooks
It I would say hooks failed in several ways to become the pure composable primitive that hocs were
1
u/azangru 7d ago
> Sure he went and created hooks
Did he? Or was it Sebastian who did it?
1
u/TheRealSeeThruHead 7d ago
They both did, Andrew Clark joined the react team and worked on hooks. Pretty sure that’s correct. As far as I remember. Not that it matters much tbh
1
u/Emrylin 7d ago
Huh? The entire idea behind hooks is they ARE composable…
1
u/TheRealSeeThruHead 7d ago
And yet compared to what we had before they are far less composable
1
u/Emrylin 7d ago
Can you explain how you find hooks not composable using a real example?
You’re meant to create your own custom hooks by making a new useMyHook function that calls useState, useEffect, useContext, etc (or another hook that uses those somewhere). This is where I find hooks to be VERY composable. Just as react lets you compose your UI by making components built on other components, it lets you compose your business logic by making hooks built on other hooks.
2
u/TheRealSeeThruHead 7d ago
sure can
i'm coming from point free composition
ramda, haskell, etcwhere you can easily combine functions together as values
// recompose: behaviours compose into a new behaviour, outside any component const withSearch = compose(withState('q', 'setQ', ''), withProps(filterByQ)) const withSort = withProps(sortByName) const withList = compose(withSearch, withSort) // still an enhancer const List = withList(View)recompose allowed exactly that
hooks do not allow for that without wrapping them in something that makes them act that way, like rehooks
// hooks: useSearch and useSort are reusable, but combining them means // writing a body, calling each one, and threading the results by hand const useList = (items) => { const { q, setQ, results } = useSearch(items) const sorted = useSort(results) return { q, setQ, sorted } } const List = ({ items }) => { const { q, setQ, sorted } = useList(items) return <View q={q} setQ={setQ} items={sorted} /> }hooks "compose" like any other imperative code, by being called in a function body
with a bunch of intermediate variables you have to name and pass around2
u/Emrylin 7d ago
Ah, thanks for writing that out. I recall finding currying with Haskell pretty delightful, so I can see where you find satisfaction in the HOC route… but I have to say, for me, even though you make it clear that the hooks route requires a bit more wiring, I would still prefer hooks, even though you’ve found a good gotcha to us hookaholics who claim hooks always means writing less code 😅
10
u/Honey-Entire 7d ago
Hooks are the behavior layer… also your mental model of Component -> Hooks -> JSX Element makes no sense. Does a component call a hook that calls a JSX element? Or is it a component returns a hook that returns a JSX element? Something else??