Skip to content

Add support for React Context #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 2, 2019

Conversation

jvliwanag
Copy link
Contributor

Add support for https://reactjs.org/docs/context.html

Borrowed ffi definition from purescript-react, and added helper functions.

Copy link
Member

@maddie927 maddie927 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks! Looks pretty close -- I've add a couple notes

-- | ```
-- |
-- | __*See also:* `provider`, `consumer`, React's documentation regarding Context__
foreign import createContext :: forall a. a -> ReactContext a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have the type EffectFn1 a (ReactContext a). I know we've cheated in other places but we're working to get away from that 😅. Probably best to name this createContext_, hidden, and export createContext = runEffectFn1 createContext_ instead. Alternatively the FFI could be curried manually and this could just have the type a -> Effect (ReactContext a)

@maddie927
Copy link
Member

maddie927 commented Aug 2, 2019

Actually, I've been thinking about it a bit more and I think it'd be a good idea to preserve the React context object (the exact one returned from React.createContext). For example:

foreign import data ReactContext :: Type -> Type

foreign import contextProvider :: forall a. ReactContext a -> a -> Array JSX -> JSX

foreign import contextConsumer :: forall a. ReactContext a -> (a -> Array JSX) -> JSX

This would allow the ReactContext type to be used with React.useContext as well, either by passing it to an FFI file or via https://github.com/spicydonuts/purescript-react-basic-hooks.

@jvliwanag
Copy link
Contributor Author

If we follow

foreign import data ReactContext :: Type -> Type

And we've used up contextProvider and contextConsumer function names, what name would you suggest for:

foreign import contextProviderReactComponent :: forall a. ReactContext a -> ReactComponent { value :: a, children :: Array JSX }

Or might it be better we just declare, and use the ff directly without changing the names:

type ReactContext a =
{ "Provider": ReactComponent { value :: a, children :: Array JSX }
, "Consumer": ReactComponent { children :: a -> Array JSX }
}

@maddie927
Copy link
Member

I don’t think I’d do either. The two functions are the accessors. They could return the ReactComponents instead if that makes sense. The problem with telling PS it’s a record type is that it looks safe to clone/update, but doing so would break it. The opaque type with predefined accessor functions keeps external PS from fiddling with it.

@jvliwanag
Copy link
Contributor Author

I've changed ReactContext to be a foreign type and effectful, then added the accessors. But I retained the existing provider and consumer since I feel that's the usual case for using it if solely within purescript. Let me know though if you prefer I take those two out.

Copy link
Member

@maddie927 maddie927 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thank you!

@maddie927 maddie927 merged commit c97f284 into purescript-react:master Aug 2, 2019
Comment on lines +385 to +396
-- | render self =
-- | R.div_
-- | [ R.button
-- | { onClick: capture_ $ self.setState \s -> s { counter = s.counter + 1 }
-- | , children: [ R.text "Tick!" ]
-- | }
-- | , provider countContext self.state.counter
-- | [ consumer countContext \counter ->
-- | [ R.text $ "Ticks: " <> (show counter)
-- | ]
-- | ]
-- | ]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋 I'm late to the party as this PR has been merged for quite some time, and maybe my following question should have been posted elsewhere. if that's wrong I do apologize 🙇

I've tried using the context API but failed to create a context as it's actually an Effect. This example doesn't cover the creation part. Am I missing something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello 👋

You have two options:

  1. Use unsafePerformEffect at the module level to create your context. It bends the rules a tiny bit, but in a safe, predictable way.
  2. Define your context during app initialization. This is a pattern I've been using more often lately. Component creation is actually effectful as well, as noted here and explained more thoroughly here. This is also why the newer hooks api defines component creation as an effect. You can see a more complete example of this pattern used with context here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed the notification of your reply. Thank you very much, it confirms what I was suspecting.

I'll go with the first option as I don't have a purescript main entry point in my setup.

The second option is the most appealing obviously. My take on all of this would be that if you have an main entry-point you could totally avoid React context and simply use a MonadReader-like instead (assuming we don't care about interop I guess).

Thank you @spicydonuts 🙇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants