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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/React/Basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,17 @@ exports.toReactComponent = function(_unionDict) {
};
};
};

exports.createContext = function(defaultValue) {
return function () {
return React.createContext(defaultValue);
};
};

exports.contextProvider = function(context) {
return context.Provider;
};

exports.contextConsumer = function(context) {
return context.Consumer;
};
53 changes: 53 additions & 0 deletions src/React/Basic.purs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ module React.Basic
, ReactComponentInstance
, toReactComponent
, Ref
, ReactContext
, createContext
, contextConsumer
, contextProvider
, provider
, consumer
) where

import Prelude
Expand Down Expand Up @@ -369,6 +375,53 @@ foreign import toReactComponent
-> { render :: Self props state -> JSX | spec }
-> ReactComponent { | jsProps }

foreign import data ReactContext :: Type -> Type

-- | Create a `ReactContext` given a default value. Use `provider` and `consumer`
-- | to provide and consume context values. Alternatively, use `contextProvider`
-- | and `contextConsumer` directly if a `ReactComponent` is required for interop.
-- |
-- | ```purs
-- | 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)
-- | ]
-- | ]
-- | ]
Comment on lines +385 to +396

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 🙇

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

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

foreign import contextConsumer
:: forall a
. ReactContext a
-> ReactComponent { children :: a -> Array JSX }

-- | Create a provider `JSX` given a context value and children.
-- |
-- | __*See also:* `createContext`, `consumer`__
provider :: forall a. ReactContext a -> a -> Array JSX -> JSX
provider context value children =
element (contextProvider context) { value, children }

-- | Create a consumer `JSX` from a context value to children.
-- |
-- | __*See also:* `createContext`, `producer`__
consumer :: forall a. ReactContext a -> (a -> Array JSX) -> JSX
consumer context children =
element (contextConsumer context) { children }

-- |
-- | Internal utility or FFI functions
Expand Down