-
-
Notifications
You must be signed in to change notification settings - Fork 5.3k
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
[RFC] Replace injected elements by injected components #3246
Comments
I agree, this would be less confusing! |
Small note: List tag in example with MyPagination in "Proposal" section is not closed. Maybe also some missing closing code (curly brace, etc). |
@deksden thanks, fixed |
Fixed by #3262 |
I think we went too fast on this one. One of my argument is partly wrong:
Material-ui sometimes expects elements, sometimes components. In fact, it expects elements more often than components. There isn't any rationale in that choice. React itself accepts elements in the So there is no consensus as to whether injections should take the form of elements and components. Also, after migrating our injections, there are still some props that we pass to other libraries - like material-ui for instance. Some of these props are still elements. So even after making our codebase consistent, the userland code still features both element and component injections. The benefit of less confusion isn't obvious. Besides, #3262 shows that the migration will be very painful - it took @djhi about a day to migrate the react-admin code. I think the cost will be the same for a moderate size project. So the first Con is a big con. So I'm thinking about 3 possible paths from here:
I'll think about it a bit more before making a decision. Comments are welcome. |
A very welcome change :) |
I have witnessed another side effect that is a strong con for that change: injecting a dynamic component has an unexpected behavior, e.g: const MyList = ({ resource, ... props }) => (
<List
{...props}
resource={resource}
pagination={p => <MyPagination {...p} perPage={resource== "posts" ? 10 : 25} />}
/>
); In that example, the This does not happen if I inject an element, e.g.: const MyList = ({ resource, ... props }) => (
<List
{...props}
resource={resource}
pagination={<MyPagination {...p} perPage={resource== "posts" ? 10 : 25} />}
/>
); See https://codesandbox.io/s/nifty-burnell-wybop for a test. This is a gotcha that few developers will expect. The workaround is to wrap the inline component with a In my opinion, this is enough to Revert the migration (proposition 2 in my previous comment). |
There are few other ways to tackle it: First, lets clarify the issue:
What we have here is a function declaration inside JSX. What can we do to not make developers fall into it? Educate the developers to not declare the function inside the JSX. if you pass pre-declared class or function, you won't have any issue. We don't have to use React.useMemo. Or We can actually make pagination not to take a component, but only a regular function that takes parameters, and inside the component call it like so: function List({props:{paginationRender: (paginationParams: IpaginationParams) => {}}) {
<div>{props.paginationRender()}</div>
} And NOT: function List(props :{paginationRender: (paginationParams: IpaginationParams) => {}}) {
<div><props.paginationRender /></div>
} For example: React Router route component: you have prop for component, and a prop named render. If you can actually pass the same inline function to component or render, |
@Bnaya Thanks for your explanation and react-router sample. I can confirm what you say. Using only render props, we could keep the nice syntax and avoid the TS issues you mentioned before |
Thank you for this amazing library:) There are some implications when calling user function as part of the render or the lib render, If we want to bulletproof the lib, |
For render prop isolation see: |
Changing from injected elements to injected render props won't bring us any of the pros I listed in the issue description. It only forces users to do a painful migration with no added benefit IMO. |
It would resolves the TS issue though and the props discoverability as you'll receive them explicitly, no? |
I'm not sure if i follow. I i will take your pitfall example: const MyList = ({ resource, ... props }) => (
<List
{...props}
resource={resource}
pagination={p => <MyPagination {...p} perPage={resource== "posts" ? 10 : 25} />}
/>
); If inside Or i'm missing something? We can also take React Router approach, and have both component and render. |
This RFC is supposed to solve a problem. The pros to using component injection, that I listed in the description, are:
I think render props only help with item 3, to some extent. But the price to pay (migrate existing code using element injection to code using render prop) is high, because react-admin uses injection a lot. So my point is, using render props, the cost / benefit ratio for the end user isn't good enough to justify the change. |
Note that react-router v6 encourages passing elements rather than components. |
In React parlance, an element is an object that becomes part of the virtual DOM, and a component is a function (or a class) that renders an element.
Current situation
In react-admin, many components accept elements as props, like for instance
List
:It's quite unusual in the react world. Most JSX libraries that do some kind of dependency injection use components for that, like for instance material-ui:
The reason to use elements in the first place was to allow for injection of dynamic elements, i.e. depending on the current props.
Also, the
children
props accepts elements, not components. Allowing the injection of elements in other props seemed like a logical extension to the React convention.Problem
However, in some places in the code, we do have to accept components because that's what our dependencies expect. Like for instance in
Resource
:The result is confusion: react-admin users never know when they must inject components, and when they must inject elements.
Another problem with element dependencies is runtime and compile time props validation. For instance, even though the
Pagination
component cannot work without apage
and atotal
prop, we cannot mark these props asrequired
because we must accept that injected as<Pagination />
by developers (and then, theList
component injects thepage
andtotal
props).Proposal
So my proposal is: let's move all injections in react-admin to components.
That's a major BC break for sure, but I feel like the upgrade task isn't that hard (it's a matter of looking for the
{<
string and replacing it smartly everywhere).Besides, the motivation for accepting elements (having a dependency configured via props) is still possible if we inject components. It just has to be a dynamic component:
And, from my point of view, configuring dependencies based on props is more the exception than the rule.
You may wonder: but then, how about the
children
prop? Should it still accept an element? The answer is yes, because that's the default behavior in react.Feel free to voice your opinion.
pros
cons
The text was updated successfully, but these errors were encountered: