-
Notifications
You must be signed in to change notification settings - Fork 751
add render component extension point #1064
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
Conversation
| var constructorFromGlobal = require("./src/getConstructor/fromGlobal") | ||
| var constructorFromRequireContextWithGlobalFallback = require("./src/getConstructor/fromRequireContextWithGlobalFallback") | ||
|
|
||
| var renderWithReactDOM = require("./src/renderComponent/withReactDOM") |
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.
folder structure and import pattern is taking reference from getConstructor for consistency
| // Render `component` using the specified `renderFunction` from `react-dom`. | ||
| // Override this function to render components in a custom way. | ||
| // function signature: ("hydrate" | "render", component, node, props) | ||
| renderComponent: renderWithReactDOM, |
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.
The comment and pattern here intends to mirror getConstructor. Let me know if
- the function signature can be better represented
- if the type of
renderFunctionName("hydrate" | "render") should be made more generic, i.e.string - export the
renderWithReactDOMfunction or assign it in a constant, e.g.ReactRailsUJS.DEFAULT_RENDER_COMPONENT_FUNCTION
Reference:
react-rails/react_ujs/index.js
Lines 63 to 66 in d5da111
| // Get the constructor for a className (returns a React class) | |
| // Override this function to lookup classes in a custom way, | |
| // the default is ReactRailsUJS.ComponentGlobal | |
| getConstructor: constructorFromGlobal, |
| if (hydrate && typeof ReactDOM.hydrate === "function") { | ||
| component = ReactDOM.hydrate(component, node); | ||
| renderComponent("hydrate", component, node, props); | ||
| } else { | ||
| component = ReactDOM.render(component, node); | ||
| renderComponent("render", component, node, props); | ||
| } |
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.
Assignment to component is removed here. It appears unused, since the function returns immediately afterwards. In addition, the return value from ReactDOM.render has been marked as deprecated.
ReactDOM.render()currently returns a reference to the rootReactComponentinstance. However, using this return value is legacy and should be avoided because future versions of React may render components asynchronously in some cases. If you need a reference to the rootReactComponentinstance, the preferred solution is to attach a callback ref to the root element.
| // Render React component via ReactDOM, for example: | ||
| // | ||
| // - `renderComponent("hydrate", component, node, props)` -> `ReactDOM.hydrate(component, node);` | ||
| // - `renderComponent("render", component, node, props)` -> `ReactDOM.render(component, node);` | ||
| // | ||
| module.exports = function(renderFunctionName, component, node) { | ||
| ReactDOM[renderFunctionName](component, node); | ||
| }; |
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.
The last argument props here is unused in this default render function. However, it is provided to support extensibility for custom implementations. The hot reload variation, for uses props to re-render with a freshly-reloaded component constructor.
Let me know if
- function should declare the
propsargument even if it's unused (for self-documentation/ease of copy-and-pasting) - function signature should change to pass
constructorinstead of/in addition tocomponent. The existing implementation of cached turbolinks components would need extra considerations ifcomponentis not passed to this function
Previous proposal referenced: #595 (comment)
- from
ReactDOM.render(React.createElement(constructor, props), node);
- to
ReactRailsUJS.renderComponent(node, constructor, props) // .... then ... renderComponent: function(node, constructor, props) { ReactDOM.render(React.createElement(constructor, props), node); },
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.
Why props is required where component is already passed in? Hot reload requires a "fresh" constructor and component while re-rendering. props is required to be passed onto the "fresh" constructor. #1065 (comment)
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.
Considerations for removing props from the render function: while it is trivial to obtain props via node#getAttribute and JSON.prase inside this render function, the work is already done in mountComponents.
Furthermore, existing implementation of turbolinks would likely be required to move here in such case; this increases the complexity to override the render function. The intention is to keep the render function as simple as possible such that it has minimal impact to existing features when it's overriden
I do not have benchmarks/metrics to support a performance argument
|
This is looking good, I'll need some time to check compatibility of this with existing codebases and I may look at adding tests. |
|
Any status on this? Would love to have something more first-class like this for wrapping top-level components! |
|
@EdmondChuiHW, @souporserious given the discussion on #982, do you agree that this PR is no longer needed? If you think it's still useful, please request to reopen this. |
Summary
Add extension point to delegate component rendering to an override-able function, similar to
getConstructor. Also add callback to component unmountThis allows custom implementations that renders parent wrapper components, e.g.
Hot reloading
Built-in support for hot reloading 🔥 would also be added via this extension point. See this PR: #1065
Stack-ability
It is also (optionally) designed to be "stackable". Allowing conditional overrides based on env var or any arbitrary boolean:
in another file:
In the above example, the final render "stack" should look like:
Other Information
Inline comments have been added to discusses options considered and their tradeoffs.
Also uncertain on how to fix the Travis CI build error