So I have stumbled upon a React Hooks based component that uses objects to categorize various functions for readability.
For eg.
const MyComponent = (props) => {
const utilities = {
utility1: () => {
// ...some functionality
},
utility2: () => {
// ...some functionality
},
};
const renderers = {
renderer1: () => {
// ...some rendering logic
},
renderer2: () => {
// ...some rendering logic
return (
<span>{renderers.renderer1()}</span>
);
},
};
return (
// ...rendering logic
);
};
What I want to understand is why is renderer2 working correctly even when it calls renderer1?
What I understand is that the object will be declared when the code is executed and the declaration is not complete till all properties are defined (this is just my noob understanding, I may be entirely wrong).
I would be really interested in knowing why this little bit of code works, especially why does it work correctly?
Also, on a side note, compared to Class-based components, I feel Hooks-based components are not very readable, and this approach tries to mitigate the problem. So I was wondering if this is the best way to make a Hooks-based component readable, or if there are other better approaches for the same?
What I understand is that the object will be declared when the code is executed and the declaration is not complete till all properties are defined
This is not so. A variable declaration happens ahead of time - at compile time, before any code actually has had a chance to run. The assignement to a variable happens at runtime though. This includes evaluating the value being assigned.
So in the case you're talking about, when the object being assigned to renderers is being evaluated, the renderers variable is actually already declared.
Also, you have to consider the fact that renderers.renderer1() is not actually being called as part of the evaluation of this object - but only later on when renderers.renderer2() is actually be called, at which point both the object evaluation and the assignment will have completed, and thus renderers.renderer1() will be what you expect it to be.
Related
I just started using hooks in react and am creating a prototype custom hook for a framework.
The hook should take an object as an argument for initialization and cleanup (setting up/removing callbacks for example).
Here is my simplified Code so far:
export function useManager(InitObj) {
const [manager] = useState(() => new Manager());
useEffect(() => {
manager.addRefs(InitObj)
return () => manager.removeRefs(InitObj)
}, [manager]);
return manager;
}
to be used like this:
useManager({ cb1: setData1, cb2: setData2... })
In future Iterations the Manager might be a shared instance, so I need to be able to be specific about what I remove upon cleanup.
I put console.log all over the place to see If i correctly understand which code will be run during a render call. From what I can tell this code does 100% what I expeted it to do!
Unfortunately (and understandably) I get a warning because I did not include InitObj in the effects dependencies. But since I get an object literal simply putting it in there will cause the effect to be cleaned up/rerun on every render call since {} != {} which would be completely unnecessary.
My research so far only revealed blog posts like this one, but here only primitive data is used that is easily classified as "the same" (1 == 1)
So far I have found 3 possible solutions that I am not completely happy with:
using useMemo to memoize the object literal outside the hook
useManager(useMemo(() => { cb: setData }, []))
This adds more responsibility on the developer using my code => not desirable!
using useState inside the hook
const [iniOBj] = useState(InitObj);
A lot better already, but it adds state that does not feel like state. And it costs (minimal) execution time and memory, I would like to avoid that if possible.
using // eslint-disable-next-line react-hooks/exhaustive-deps
Works for sure, but there might still be other dependencies that might be missed if I simply deactivate the warning.
So my question is:
How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?
I half expect that the useState option will be my best choice, but since I am new to hooks there might still be something that eluded my understanding so far.
I have read that using an arrow function inline re-creates the function on every component re-render causing performance issues.
In my code I have this situation
render() {
return (
<View
onLayout={(event) => {
this.itemHeights[index] = event.nativeEvent.layout.height;
}}
>
...
)
}
I have thought to do something like this to get a better performance:
getItemHeight = (index, {nativeEvent: {layout: {height}}}) => this.itemHeights[index] = event.nativeEvent.layout.height;
render() {
return (
<View
onLayout={(event) => getItemHeight(index, event)}
>
...
)
}
But I am not really sure if this is better... at I have to invoke a function in a inline function. I did it because I need the parameter index, so I can't do:
<View
onLayout={getItemHeight}
>
I know that this may not be too expensive, but it is very expensive for my use case, as I have to re-render items on the fly.
Any ideas?
Declaration vs Execution
First of all I think you are confusing two things here.
The declaration of a function is something different than the execution of a function.
Also the component does not re render all the time, just because a function is redeclared inside the render functions return.
There is also a vast difference between the performance impact between the declaration of a function and its execution.
Referentially stable function vs inline functions
So what you are doing in your first example is redeclaring a function for the View component. This basically resets the value for the onLayout prop, because inline functions are not referentially stable.
~~In the second example (I figure this is a class component), you are using a referentially stable function, so the prop value of onLayout stays the same.~~
UPDATE: Actually this is not true, my bad. You are still using a instable function. But you could just bring that up as a class member like so:
public getItemHeight(index, {nativeEvent: {layout: {height}}}) {
this.itemHeights[index] = event.nativeEvent.layout.height;
}
// ...
// ... inside the render function:
return {
<View onLayout={this.getItemHeight}></View>
}
But I am not really sure if this is better... at I have to invoke a function in a inline function. I did it because I need the parameter index, so I can't do:
Actually this statement isn't true. You can also retrieve the index with a defined function, as I showed above.
Using this code you are just giving the prop a reference to the function that you want to be invoked by the callback. This will populate the arguments just fine.
When do components re render?
Components re render when there is a prop or state change.
So in your first example the View component will re render only if the parent component has to be re rendered. So the parent renders and therefore evaluates the it's sub components. This does trigger the render function of the View component and swaps out the function for onLayout.
In your second example (considering my suggested code change above) the View component would not trigger it's render function since the prop value of onLayout did not change.
But that does not mean, that your first example necessarily results in a DOM change and repaint (this is a different algorithm inside React). And the DOM change would be the really expensive part.
Rerender vs Repaint
Rerender is a process that React runs internally. React just triggers the render functions of the component that have to be rerendered. But the repaint inside the actually page during runtime only happens if the resulting DOM is different than before.
You can read more about this in the React Docs: https://reactjs.org/docs/reconciliation.html
So when would you have to use referentially stable functions?
I don't think that you should prematurely optimize such things. It can make the code more bloated and introduce complexity that isn't always necessary.
There is also a whole part on this here: https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation
If it does not drain your performance, why bother micro optimizing things?
Just because you read that inline function are recreated every time (btw. this also applies to non arrow functions. It's the inline declaration that makes it recreate every time) and therefore the render function is triggered every time, doesn't always mean that React has to repaint or that it has a big performance impact.
So never prematurely fiddle around on small performance gains, when there isn't even a problem.
An React based app is a huge beast of code. There will be plenty big picture problems to fix, once you run into performance issues.
I have a component that has complex rendering logic.
I try to carry out this logic to helper classes, for simplifying.
To do this, in the data section (for reactivity), I create class references as follows:
export default {
data: () => ({
state: new InitialState(this),
query: new QueryController(this)
})
}
As I understand it, at this point the context of this is not yet defined.
So, I have two questions.
1) Is there a way to pass the this component context in the data section (without lifecycle hooks)?
2) Is the approach with references to external classes of vuejs philosophy contrary?
Component instance is already available when data function runs, this is one of reasons why it has been forced to be a function.
Due to how lexical this works with arrow functions, it's incorrect to use them to access dynamic this. It should be:
data() {
return {
state: new InitialState(this),
query: new QueryController(this)
};
})
The problem with InitialState(this) is that the entire component instance is passed instead of relevant data, this breaks the principle of least privilege.
Despite Vue isn't focused on OOP, there's nothing wrong with using classes. One of possible pitfalls is that classes may not play well with Vue reactivity because it puts restrictions on the implementation. Another pitfall is that classes cannot be serialized to JSON and back without additional measures, this introduces limitations to how application state can be handled.
As I understand it, at this point the context of this is not yet defined.
Only because of the way you've written the code. The component instance does exist and is available. It is sometimes used to access the values of props for determining the initial values of data properties.
For example, here is an example from the documentation:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
export default {
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
}
The reason why your code doesn't work is because you are using an arrow function. If you change it to the following then this will be available:
export default {
data () {
return {
state: new InitialState(this),
query: new QueryController(this)
}
}
}
See also the note here:
https://v2.vuejs.org/v2/api/#data
Note that if you use an arrow function with the data property, this won’t be the component’s instance, but you can still access the instance as the function’s first argument
As to your other question about whether using classes like this is contrary to Vue...
I don't think the use of classes like this is encouraged but they can be made to work so long as you understand the limitations. If you have a clear understanding of how Vue reactivity works, especially the rewriting of properties, then it is possible to write classes like this and for them to work fine. The key is to ensure that any properties you want to be reactive are exposed as properties of the object so Vue can rewrite them.
If you don't need reactivity on these objects then don't put them in data. You'd be better off just creating properties within the created hook instead so the reactivity system doesn't waste time trying to add reactivity to them. So long as they are properties of the instance they will still be accessible in your templates, there's nothing special about using data from that perspective.
I think computed is a better way to do what you want
export default {
computed:{
state(){
return new InitialState(this);
},
query(){
return new QueryController(this);
}
}
}
Can I write React lifecycle methods as class properties?
I've been using class properties for a while as I like the fact that I no longer have to manually bind my methods, but I'd like to keep some consistency across my components and I'm wondering if there is any drawback on writing the React lifecycle methods as class properties
import React, { Component } from 'react';
class MyComponent extends Component {
render = () => {
return (
<div>Foo Bar</div>
);
}
}
export default MyComponent;
For example, is the context of this class property affected compared to the context in an equivalent method. Given that the render method in the above code is written as an arrow function, this concern seems relevant.
In a way, the true answer depends on your build pipeline and what the resulting Javascript output looks like. There are two primary possibilities:
Input Code
Let's start by saying you are writing the following before going through any sort of pipeline transformations (babel, typescript, etc):
class Test {
test = () => { console.log('test'); };
}
Output as class member variable.
In one possible world, your pipeline will also be outputting the test function as a member variable for the output class. In this case the output might look something like:
function Test() {
this.test = function() { console.log('test'); };
}
This means that whenever you write new Test() the test function is going to be recreated every single time.
Output as class prototype function
In the other major possibility, your pipeline could be recognizing this as a function property and escape it from the class instance to the prototype. In this case the output might look something like:
function Test() {
}
Test.prototype = {
test: function() { console.log('test'); }
}
This means that no matter how many times you call new Test() there will still be only one creation of the test function around in memory.
Desired behavior
Hopefully it's clear that you want your end result to have the function end up on the prototype object rather than being recreated on each class instance.
However, while you would want the function to not end up as a property, that doesn't necessarily mean you couldn't write it that way in your own code. As long as your build chain is making the correct transformations, you can write it any way you prefer.
Although, looking at the default babel settings (which your babeljs tag leads me to believe you are using) it does not make this transformation for you. You can see this in action here. On the left I've created one class with the function as a property and one class with the function as a class method. On the right hand side, where babel shows it's output, you can see that the class with the function as a property still has it being an instance-level property, meaning it will be recreated each time that class's constructor is called.
I did find this babel plugin, which seems like it might add this transformation in, but I've not used it for myself so I'm not positive.
In my experience, the most reason for writing a method as a class property is when the method will be passed as a callback, and you need it to always be bound to the instance. React lifecycle methods will always be called as a method, so there's no reason to bind them (and you incur a tiny memory penalty when you do). Where this makes a difference is when you're passing a function to a component as a callback (e.g. onClick or onChange).
Take this example:
class BrokenFoo extends React.Component {
handleClick() {
alert(this.props.message);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
The function represented by this.handleClick is not automatically bound to the component instance, so when the method tries to read the value of this.props it will throw a TypeError because this is not defined. Read this article if you're not familiar with this; the problem described in section 4.2 "Pitfall: extracting methods improperly" is essentially what's happening when you pass around a method without making sure it's bound correctly.
Here's the class, rewritten with the handler as a class property:
class HappyFoo extends React.Component {
handleClick = () => {
alert(this.props.message);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
Effectively, you can think of the handleClick definition in the second example as placing this code into the component's constructor (which is just about exactly the way Babel does it):
this.handleClick = () => {
alert(this.props.message);
}
This achieves the same thing as calling bind on the function (as described in the linked article) but does it a little differently. Because this function is defined in the constructor, the value of this in this.props.message is bound to the containing instance. What this means is that the function is now independent of the calling context; you can pass it around and it won't break.
The rule of thumb that I follow: by default, write methods as methods. This attaches the method to the prototype and will usually behave the way you'd expect. However, if the method is ever written without parentheses (i.e. you're passing the value and not calling it), then you likely want to make it a class property.
I have a container that I need to change the UI form showing the form or showing a success page.
The container has a state.showSuccess and I need the MyFormModule to be able to call the container to change the state.
The below code works but I'm getting the following warning:
JSX props should not use .bind()
How can I get this to work without using .bind()?
...
const myPage = class extends React.Component {
state = { showSuccess: false };
showSuccess() {
this.setState({
showSuccess: true,
});
}
render() {
const { showSuccess } = this.state;
if (showSuccess) {...}
....
<MyFormModule showSuccess={this.showSuccess.bind(this)} />
You should first understand WHY this is a bad practice.
The main reason here, is that .bind is returning a new function reference.
This will happen on each render call, which may lead to a performance hit.
You got 2 options:
Use the constructor to bind your handlers (this will run only once).
constructor(props) {
super(props);
this.showSuccess = this.showSuccess.bind(this);
}
Or create your handlers with arrow functions so they will use the
lexical context for this, hence you won't need to bind them at
all (you will need a babel plugin):
showSuccess = () => {
this.setState({
showSuccess: true,
});
}
You should not use this pattern (as others suggested):
showSuccess={() => this.showSuccess()}
Because this will as well create a new function on each render.
So you may bypass the warning but you are still writing your code in a bad practice design.
From the ESLint docs:
A bind call or arrow function in a JSX prop will create a brand new
function on every single render. This is bad for performance, as it
will result in the garbage collector being invoked way more than is
necessary. It may also cause unnecessary re-renders if a brand new
function is passed as a prop to a component that uses reference
equality check on the prop to determine if it should update.
Use an arrow function when defining showSuccess
showSuccess = () => {
this.setState({
showSuccess: true,
});
}
Use an arrow function since they automatically inherit the this context of wherever they are defined.
showSuccess={() => this.showSuccess()}
Here is a link to the facebook documentation on this subject, which lists this method among others as a solution. Interestingly, they also list using .bind in the prop as one of the solutions, even though it produces a warning when actually used.
From that documentation, you'll note that this is a potential performance issue, since the function will be recreated on every render:
Note:
Using an arrow function in render creates a new function each time the
component renders, which may have performance implications (see
below).
But also from the same link:
Is it OK to use arrow functions in render methods? Generally speaking,
yes, it is OK, and it is often the easiest way to pass parameters to
callback functions.
If you do have performance issues, by all means, optimize!
So I would say if your component will be re-rendering very frequently, you should use one of the other solutions: bind in the constructor, or define the method with an arrow function in the first place. But if not, use whatever method seems cleanest to you.