-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
v6 proposal regarding loss of focus when stateless functions are passed to Field
and Fields
#1555
Comments
This ties into the older discussion here #961, in particular #961 (comment) where @erikras wrote:
|
What you're proposing doesn't fix the issue. Every time that There's no way to make this work within React. The way reconciliation works within React makes this impossible. |
@clayne11 I believe you've misunderstood the crux of how my solution works. I'm suggesting not passing the function to |
Oh alright. I see what you mean. So within the const Field = ({component, render}) => {
if (render) {
return render()
}
return React.createElement(component)
} That's the gist of it? |
Yes, that's the gist of it. |
FWIW, it's feasible to build this as a wrapper around If it's a pattern other people feel is useful, it would be nice to revive this syntax within vanilla Going further, I occasionally even do the same thing with the whole form with a <Form form="myFormName" render={(formProps) => (
// all your form stuff goes here
)} /> This allows a syntax where you have access to various form-level or field-level props in closures instead of thinking about how to pass them down directly through a chain of component props. Arguably not for all people, and not for all times, but I've found it's a nice syntactical tool to reach for when it's convenient or helpful. |
This is how Presumably for this "just call the function" syntax to work, |
Yes excellent point about the closures, and for that reason, you definitely couldn't ignore changes to the That does mean every field that is constructed using the I'm sure you know this, but just to be very clear for anyone following the discussion and also to address the subtleties of #871, functions can be treated in two different ways:
class MyComponent extends Component {
someChunk() {
return <p>Some Chunk</p>
}
render() {
return (
<div>
<p>Main content</p>
{ this.someChunk() }
</div>
)
} Here there is certainly no expectation that I can see that someone could accidentally pass a functional component to the The reality is there are intrinsic subtleties to inline functions:
These subtleties are exactly why we have that big warning in the docs about not using inline stateless functional components. I don't think there is any way to escape these subtleties, so I suppose what I'm suggesting is rather than warning people not to use inline functions, and rather than avoiding inline functions altogether, warn them that inline functions need to be treated as plain functions (not functional components) and passed to You make a good point that performance would be affected. My proposal is solving the "loss of focus" issue, not the performance issue. |
Part of the trouble here is that react is treating a function as a component in a certain cases (i.e. when you pass it to
Too late now. |
In light of the discussion in #871 it makes sense to me to leave things as they are. Perhaps it would make more sense to have the component as a child to the <Field>
<MyInput/>
</Field> This is more idiomatic React and it would help with the issue of people passing in new functions in each |
That's how it works in React-Redux-Form, if you want to look there for implementation details. (more than willing to help!) It's a pattern that seems to work pretty well. Regarding rendering custom components, V1 is giving the option to have children-as-a-function: <Field>
{(fieldValue) => <MyInput ... />}
</Field> that provides the field. I know performance is a concern, so there's also a new prop: What do you think of those patterns? Would be glad to help! |
I'm not so sure about that statement, but I have looked at how Of course @davidkpiano would post as I was writing this. :-P (I had already linked to his library before his post showed up) |
@erikras Yeah, cloning is weird but it's working pretty well 😄 I don't prefer it either but I can't really think of a better way that allows for a succinct API. |
This feels (perhaps it is not, but I'm sharing my many-years-of-xml gut reaction) pretty disgusting. I'm sure you recall back when you had to do It's unclear to me that passing children-as-a-function is less disgusting than prop-as-a-function. |
Got my inspiration from here: https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9#.jw39d4gb6 |
I agree with you on that point. I'm not sure if there is actually a performance issue but every time I clone a node I have that same sinking feeling in the pit of my stomach that I'm doing something wrong. It would be good to get some input from someone more in the know about React performance. |
@clayne11 @erikras To be fair, Also, in V1, the |
Perhaps this is obvious, but "inline function as a child" vs. "inline function as a prop" is purely cosmetic. The child in JSX is just syntactic sugar for the 3rd argument to |
You do. I don't think allowing inline function definition is a good pattern at all and I don't think we should allow / encourage it. |
Why? In the most common cases (that is, without closures) pure and idempotent functions as children can be optimized, by having I'd like to see Redux-Form's API at a point where there isn't a sizable amount of nuances the developer has to "just know" by reading the docs, such as being forced to define the render function outside the |
Interesting. How awkward would HTML be if you could only nest things 2 levels deep, and you always had to define the child before the parent like this: MyParagraph:
MyBody:
Final Page:
That's how using redux-form without an inline API feels to me, and I'd gladly trade some performance for a less clumsy API. Just trying to help illustrate my opinion, definitely OK if you disagree. |
I'm not necessarily opposed to using children instead of a function, but you still have the exact same performance and closure issues because the children themselves are inline, and have access to all of the variables in scope. They are rendered every time (just like the function would be rendered every time), so there is no performance benefit. <Field>
<MyInput/>
<span someProp={somethingFromScope}>Hello</span>
</MyInput
</Field> So, children vs. functions is just a cosmetic issue, not an underlying shift in performance or closure issues. To the degree that you can optimize things for one approach, you can optimize them for the other approaches. The fundamental question here is whether specifying field code inline is a desirable API to have. |
IMO it's not really like that. I'm essentially saying that for each different type of component you want to support, you have to create a re-usable component. Presumably you want your forms to be consistent throughout your app, so you need to create one of each:
and maybe a couple others. I don't think it's particularly onerous to ask you to create these re-usable components before passing them into In React in general you're not really supposed to create new functional components inline. Also, In general with Personally, I'll write some extra boilerplate to significantly decrease the number of bugs that I have in my app and to ensure that I can track down performance problems. If you don't like writing boilerplate then you probably shouldn't be looking at That's my two cents at least. |
Yes I can see (although I don't completely share) your perspective regarding a fairly limited number of reusable form controls. Personally, probably 90% of mine are pre-defined, whereas 10% might be one-off things where inline would be nice. How about <Fields names={['field1', 'field2']} render={({ field1, field2 }) => (
some condition based on field1 and field2 ? <OneThing /> : <AnotherThing />
)} /> Would you move every chunk of logic like that into a pre-defined named component/function? |
I think we can agree reducing bugs and tracking down problems are good goals, so really the difference here seems to be what aspects of coding we feel helps us do that, and whether the library itself should encourage or discourage a given approach. My proposal supports both approaches, so I suppose the ultimate question is whether supporting both approaches is overall good or bad for the library as a whole, and of course that's not my call to make. My desired API is feasible through user land wrappers; my proposal was based on the assumption that an inline API was generally desirable to the rest of the community too. |
Excellent and thoughtful discussion here, guys. 👍 If only all internet discussions were as well argued and respectful. 😄 Let's define the problem that this issue is trying to address. ProblemIt's annoying having to define component render functions outside of
|
@erikras I like your proposal, as long as 6.0.0 final is cut before. 😄 I would say that stabilising redux-form as it stands should be the priority. |
@ruiaraujo That's the thing, though. My proposal would not be a breaking change. If anything, it would repair problems from people misusing the current way. |
@erikras I know it is not breaking hence it can be worked on after the final cut. 😉 |
@erikras I don't like that solution. A lot of people are moving to use I really think it should just stay the way it is now. Defining anything inline, be it a component or a function, is an anti-pattern in React and should not be encouraged. |
I agree it would be sticky to use Having two props forces users to think about what they're really trying to do; unfortunately, people will still be able to shoot themselves in the foot (which they already doing now, because not everyone reads the warning in the docs). I think two props is fine (although I don't think it's an anti-pattern to pass values to anonymous functions --- it's a powerful technique programmers use all the time, and merely being in react (or redux) doesn't fundamentally change that. The only reason closures start causing bugs is when you try to cache the function (i.e. ignore it in It's interesting to me that if we maintain the position that all inline code is discouraged, then every field is isolated in its own component guarded by Having content inside a function that is executed on every render is completely equivalent to having that same content directly inside the main form component, from a performance perspective. For example, suppose you have this in your form: Form = () => (
<SomeStylingWidget>
<p>Some extra arbitrary content goes here</p>
</SomeStylingWidget>
) Every time you rerender the form, you also have to rerender that widget content. In order to have better performance, you could move that widget stuff into a component guarded by class WidgetContent extends Component {
render() {
<SomeStylingWidget>
<p>Some extra arbitrary content goes here</p>
</SomeStylingWidget>
}
}
Form = () => (
<WidgetContent />
) However, one of advantages of react is that it efficiently resolves these differences for you so that you don't have to optimize things like this yourself in awkward ways. First you optimize for the programmer, then you optimize for performance. In other words, we want to be able to write code that is the most clear and convenient, without being forced into tricks to achieve performance (at least until performance itself becomes the problem). All of that said, expanding the API of a library has a lot of ramifications, so if you decide it's not the right time to add something like this, I completely understand. |
I think the issue with performance in
The issue isn't with having content inside of a function - the issue is defining a new function on every render pass. That's expensive within the JavaScript VM itself, and you're removing an entire layer of optimizations done by the VM even without accounting for React optimizations. That's my understanding of the Javascript VM at least. I'm happy to be proven wrong but that is one of my biggest issues with defining components inline, along with the fact that there's no reliable way to tell a function apart from a functional component as far as I know. I would love to get @gaearon's opinion on this discussion. |
Yep I agree inline functions are slower from the VM standpoint, and I agree there is no reliable way to distinguish functions from functions intended as functional components, and I agree @gaearon's input would be welcome here. I agree about v5 and v6 too, which is why I said "most" and "lion's share", although which aspect is contributing most to better performance I could be wrong on. |
I don't think changing behavior depending on propTypes is a good solution. Some people remove them in production which would change the behavior between dev and prod. |
I think you could have a heuristic like this:
Not sure if it covers all cases but maybe it could work. |
Thanks for that, @gaearon. That seems a little too close to the React metal than I would like. i.e. it feels like something that might break from a non-breaking release of React because it's sidestepping the public API. Overall this has been a great discussion, but I haven't seen an argument that is so powerful as to overcome the inertia of leaving the API as is. Thank you to you all. |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Regarding these docs:
I understand both the performance and the loss of focus issues that arise when you do something like this:
Nevertheless, this is a nice syntax to have. At times, defining the stateless function outside of your render function is fairly inconvenient and undesirable because it moves your code out of the immediate cognitive context. It would be nice to have this syntax, without the performance and loss of focus issues.
The most onerous of the issues is the loss of focus issue, which ultimately is caused because react thinks the functional component is a completely different component each time, which causes react to unmount the old one and mount a new one, rather than just changing the props on the existing one.
The crux of the mounting (and loss of focus) issue is that
redux-form
is treating these functions as functional components (i.e. passing them toreact.createElement
) rather than just arbitrary functions that return elements (i.e. JSX).My proposal is that we introduce a lower-level API to
<Field>
and<Fields>
that allows you to pass a function instead of a functional component:Note this is identical to the former code, except instead of passing our function as the
component
prop, we're passing it as therender
prop. Then,redux-form
internally would simply call this function rather than passing it toreact.createElement
.This approach allows what I believe was the originally desired syntax for stateless functions, but does not cause the loss of focus issues. Thoughts?
The text was updated successfully, but these errors were encountered: