-
Notifications
You must be signed in to change notification settings - Fork 348
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
Add support for React Hooks under a new React module namespace #351
Conversation
As far as I’m concerned (please correct me if I’m wrong), right now there is now way using JSX to pass data-* attributes like data-testid to built in components. Is there any chance something may change in that matter? |
@baransu please see #230 for the tracking issue and a workaround - unfortunately it's not possible to add support for these attributes directly because they are dynamic. If facebook/react#1259 was adopted then we could support them first-class. This is true in both the current record-based api as well as this new hooks api. |
Ricky and I discussed this in private, but one proposal I wanted to make would be to try to avoid special casing react dom in the PPX. The current design prevents use cases such as defining a single module with multiple react components that depend upon each other. For instance,
is not supported because all lower case names are required to be string DOM components. Granted this can be addressed by adding a wrapper module around componentA, but this is extra boiler point that it would be nice to avoid. Instead we could support both use cases by exporting the dom string names from ReactDOM. One issue right now is that strings are not inlined by bucklescript, so the bindings would introduce the cost of shipping a compiled form of ReactDOM. Perhaps we can work with bucklescript to support some external string type that is always inline at the use point? |
Bringing over from Discord as per Ricky I co-locate pretty much everything within a component, including the React component logic. Up until now, the requirement of ReasonReact on a module Person = {
type t = {
name: string,
age: int,
};
// Canonical Reason/OCaml would dictate this be `make`
let create = (~name, ~age) => {name, age};
let component = ReasonReact.statelessComponent("Person");
let make = (~person, _children) => {
...component,
render: _self =>
<div>
<span> {ReasonReact.string(person.name)} </span>
<span> {ReasonReact.string(string_of_int(person.age))} </span>
</div>,
};
}; Usage would look like: let me = Person.create(~name="Blaine", ~age=99);
let rendered = <Person person=me /> The new addition of the module Person = {
type t = {
name: string,
age: int,
};
let make = (~name, ~age) => {name, age};
[@react.component]
let component = (~person) =>
<div>
<span> {ReasonReact.string(person.name)} </span>
<span> {ReasonReact.string(string_of_int(person.age))} </span>
</div>;
}; But that makes the usage less ergonomic (if you aren't using the implicit let me = Person.create(~name="Blaine", ~age=99);
let rendered = <Person.component person=me /> Since we are adding an attribute to all our React components, why can't we look for an attribute that indicates the method to compile a top-level usage instead of implying [@react.component.default]
let component = (~person) => <div />;
[@react.component] [@react.default]
let component = (~person) => <div />;
[@react.component default]
let component = (~person) => <div />; This would keep the ergonomics of the |
I was also hoping to discuss writing the PPX in Reason. I think it'd really help contributions. I understand that it's probably desirable to keep an OCaml version around to make it easier to bring into BuckleScript. Maybe we could set up something like the BuckleScript-TEA project that keeps 2 copies of the project and has scripts that just refmt between them? |
Can components annotated with the new ppx be used within components created with older versions of the ppx (< 0.6)? |
We could changing the default component name from |
@wegry: Yes. Either version can be used within the other with some constraints. Only one version of JSX can be used in a file. So if you want to render a record and a function component in the same file, you will need to write out the call manually for at least one (primitive components like div are not included in this - can be used with either). /* one of these has to be written out manually: */
<>
<MyRecordComponent/>
<MyFunctionComponent/>
</> |
@bordoley That's basically where I've ended up as well. I think using the |
Does the PPX work in module interface files (.rei)? For product code it doesn’t matter much, but you could imagine library authors wanting to restrict the modules and functions they export. |
@bordoley this ppx supports signatures and .rei files. The last example in the initial post (labelled "interface") shows what the syntax looks like. Are you looking for something more specific? |
src/ReasonReactRouter.re
Outdated
}; | ||
|
||
let useUrl = () => { | ||
let (url, setUrl) = React.useState(() => dangerouslyGetInitialUrl()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should be able to provide a default url here what do you think ? use case for example is if you are coming from ssr and want to start rendering from a different route.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't you do a redirect to the correct route?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean ReasonReact.Router.push('mynewroute');
instead ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No I mean on the server side issuing a 302 or whatever type of redirect it should be. It seems like you client and server should always be in agreement about how URLs map to routes and how that wraps to data.
Maybe I'm not understanding what you mean by "want to start rendering from a different route" - can you expand?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
want to avoid this:
https://pennyworth.dblechoc.now.sh/more can you see the flashing ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
express middleware
let app = <App client={client} initialPath={here i can get in from the req knowing i am insdie an express middleware} />;
// first render for ssr cache
renderToStaticMarkup(app);
my actual component
[@react.component]
let make = (~client, ~inititalPath) => {
// if i could pass an inititalPath here would be good
let url = ReasonReactRouter.useUrl();
<div>
<GraphqlHooks.Provider value=client>
<main>
{switch (url.path |> mapPathToRoute) {
| Home => <Home />
| More => <More />
}}
</main>
</GraphqlHooks.Provider>
</div>;
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean. Yeah I think this is reasonable. I think that serverValue
or staticValue
might be a better term - initial
implies it's fixing an issue with the client not being able to pick up the URL when it first loaded, but it's actually an issue with getting the path via browser APIs on the server. But yes, adding a param here makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome thank you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see #358
@bordoley thought more about the |
@phated I think we are going to stick with |
This PR provides APIs for all hooks as well as element creation APIs, core components (Suspense, Fragment) and other top-level React APIs. It moves
Router
outReasonReact.re
since it can be used in with either type of components. It adds a ppx (to be moved into BuckleScript). Finally, it provides a module calledReasonReactCompat
, which has bindings that can be used for both forwards and backwards compatibility (these functions will be very familiar to anyone who has worked with JS interop in the current API).This new way of writing components comes with a number of benefits.
First - components written this way are valid ReactJS components and can be used in interop scenarios without additional binding.
Second - it provides a way forward to support newer React features like Suspense. It will also make writing components for concurrent mode much easier.
Usage
Things to Note
children
- in ReactJS the type of children is quite complex. Originally this implementation wrapped multiple static children (<A> <B/> <C/> </A>
) with a fragment, but now it wraps with an array and includesnull
in the children object. This only applies for non-primitive components which accept >1 static children. For all other components behavior is identical to the JS variant of JSX.TODOs
[@react.component]
tag is something we should be careful to document well