restrictions on hooks captured by indexed monads
jamesdbrock opened this issue · 4 comments
Indexed monads seem to be a useful tool for capturing the restrictions on hooks, like in purescript-react-basic-hooks.
https://twitter.com/paf31/status/1197185171176341504
working on a pure model for React hooks based on indexed monads, inspired by purescript-react-hooks.
https://twitter.com/paf31/status/1231614635754819584
The screenshot is fairly useless right now, but I'm going to polish this up and turn it into a blog post when I have something a bit more complete.
https://twitter.com/paf31/status/1231614926503993345
I sure wish I could find that blog post. I don't think it exists?
I had a conversation last week which I will paraphrase like this:
Me: Why does react-basic-hooks need indexed monads?
@robertdp : Because React needs certain operations to be called in a certain order, and the indexed Render monad enforces that ordering at the type level.
Which makes sense, and it also seems to be what Phil is describing on Twitter. But I would like to understand it better. Which React “restrictions on hooks”, exactly, are captured by Render?
It's not exactly that React needs certain operations to be called in a certain order. It's more that React uses the order in which hooks are run as a way of tracking them, and associates internal React state with each hook. This means that if the order of hook evaluation changes in a component then the behaviour is undefined and likely highly unsafe (unless React provides some guarantees about this, but I don't think they do).
Render is just an indexed monad that represents Effects, but the restrictions are modelled by Hook:
type Hook (newHook :: Type -> Type) a
= forall hooks. Render hooks (newHook hooks) aLike Phil was using tuples to build up the sequence of hooks, react-basic-hooks uses newtypes and Render:
customHook :: forall hooks. Render hooks (UseEffect Number (UseMemo Boolean Int (UseState String hooks))) Unit
customHook = React.do
_ <- React.useState "hi"
_ <- React.useMemo false \_ -> 1
React.useEffect 22.2 mempty
pure unitThank you @robertdp .
Here's the reference to the rule which is enforced by the Hook type. This is why we need the indexed monad Render.
https://reactjs.org/docs/hooks-rules.html
React relies on the order in which Hooks are called.
ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple
useStateanduseEffectcalls.
Yep -- implementing React hooks with plain monads would still allow broken hook rules. Using indexed monads captures the same rules in the type system that eslint-plugin-react-hooks does in JS, except dependency list tracking.
And also, for those following along, react-basic-hooks recommends the “qualified-do” syntax because then we can write do blocks with the indexed monad Render.
https://pursuit.purescript.org/packages/purescript-react-basic-hooks/docs/React.Basic.Hooks#v:bind