I'm finding a small issue causing let instantiated variables to be modified incorrectly within React functional components.
Expected Behavior
I have a variable (an object) that is modified by follow-on logic like this inside a React functional component:
const SomeComponent = () => {
let foo = {}
if (someCondition) {
delete foo[prop]
}
if (someOtherCondition) {
delete foo[anotherProp]
}
...
return (<> Object.keys(foo).map(key => {...})</>)
}
So basically, I'm trying to use only specific properties on this object based on other conditions, like a specific conditional use of the Component.
The foo variable is an object that's returned from a query to a database. Specifically, in the parent Component to SomeComponent, I'm running a query that returns information (in the form of an object) about a company. foo is one property from that return that is also an object. When the query is complete, the result of the query is passed down into SomeComponent, so foo is not stateful in SomeComponent.
The Problem
I'm noticing that, when the above Component is brought into the DOM the first time, it works as expected. So, for example, if I bring the Component into the DOM under "circumstance 1", it will work properly. However, if I then bring it into the DOM under another, different circumstance (let's call it "circumstance 2"--a circumstance when the foo object should have a different set of props deleted from it that when rendered under "circumstance 1", foo will not be modified correctly.
Specifically, I notice that foo will have both the properties deleted from it as if it met both someCondition and someOtherCondition.
If you have any leads on this challenge, I'd appreciate it!
Related
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);
}
}
}
I am attempting to assign an object that is passed into a React.Component as a property to a state value like so,
state = {
rota: this.props.rota
}
render() {
// const { cleaning, chairs, creche, flowers } = this.state.rota;
const { cleaning, chairs, creche, flowers } = this.props.rota;
console.log(creche);
}
The commented out section where it get's the value of the state prints out an empty string, however the property values are correct. Am I doing something wrong when assigning the props.rota to the state.rota?
I am using typescript and have done exactly this in another place in my program - however the property being passed in was a value type (string) rather than an object type.
Thanks!
That's usually a bad practice.
Take your case.
You get a value as prop from a parent component.
The inner component will already be re rendered every time that this value changes in the parent component.
If you go with an approach like yours, what most probably you will do, is to change that value inside the inner component too (through state), whose changes will not be reflected on the parent component.
You are actually breaking the design pattern of uni directional data flow, on which react relies a lot on.
So my personal opinion is to lift up the state in this case and avoid such kind of situations. Use callbacks instead if you want to communicate changes to the parent, or use some state management (context, redux, etc..).
Or design a better solution using HOC or render props Components.
Even though #quirimmo has pretty much answered your question, if you want to do this sometime in the future, the easiest way would be to use a constructor function and pass the props in as a param, and then just set that as the default value of the state
class SomeComponent extends Component {
constructor(props){
super(props);
this.state = {
rota: props.rota,
}
}
}
This makes sure that the prop is actually available in the moment you want to set the initial state, since the constructor is the first function that is called in the component lifecycle.
I have read in multiple places that stateless functions in React are not supposed to have inner functions. Why is it so, though it works?
const Foo = () => {
let bar = () => {
return <span>lorem ipsum</span>
}
return <div>{bar()}</div>
}
This works. But, why is this not supposed to be done?
N.B. This answer assumes that the use of the word "method" was incorrect, and that we are actually talking about an inner function, as in the example provided in the question.
A stateless component is defined as a function which returns something that can be rendered by React:
const MyStatelessComponent = function (props) {
// do whatever you want here
return something; // something must be render-able by React
}
To (re-)render the component, React calls the function, so it makes sense to perform expensive computations in advance and save their result outside of the function.
In your toy example, the function bar is declared once per render, and only used once. Let's assume that it was slightly more complicated and pass it a single parameter:
const Foo = () => {
let bar = text => {
return <span>{text}</span>
}
return <div>{bar("lorem ipsum")}</div>
}
By moving bar outside of the component, you don't need to create the function once per render, you just call the function that already exists:
const bar = text => {
return <span>{text}</span>
}
const Foo = () => {
return <div>{bar("lorem ipsum")}</div>
}
Now your component is ever-so-slightly more efficient, since is does less work every time it is called.
Also note that bar is almost the same as a stateless component now, and could easily be turned into one by making the function take a props object rather than a single string argument.
But the bottom line is that you can do whatever you want inside the stateless component. It just is worth bearing in mind that it will happen once per (re-)render.
While still valid code, as far as react is concerned, the above example is not a stateless component.
A stateless component is basically a shortcut to the render method of a stateful component (without the same life-cycle) and should "ideally" only return data, not define methods or actually manipulate or create additional data or functionality. With a stateful component, ideally, you do not define methods within the render method so none should be added in a stateless component.
By defining a method, function, or parameter inside of a stateless component but outside of the render method, you are essentially saying that there is a possibility of manipulation within the stateless component, which defeats the purpose.
Mind you, it's still valid code...but just not "react" ideal.
The function Foo is basically the render method of the React component. Therefore, it will be called everytime the component needs to be rendered. By declaring a local function inside it, it will create a new function everytime the component re-renders, which is bad.
Either declare the function outside or implement a stateful component instead.
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 been working with the Component Factory Resolver for awhile and while I think it's pretty slick, there is one thing that drives me nuts. I would love to wrap most of the repeated code into a helper function that renders the component.
In my case I have a dashboard component where we render quite a few different components by altering singleton services to trigger visibility or not. Rather than having a ton of these create component code blocks, I was wondering if anyone has successfully create a helper-like function that a few variables can be passed into to achieve the same effect, thus eliminating a lot of the repetitive code.
Below is my attempt at a helper function and the call to activate it. The component gets created, but the destroy function doesn't work. I have narrowed it down to the Component Reference not actually being saved to the globally accessible component. Is there a way to store component references within a global array? If so how would you go about dynamically accessing them as components are added/destroyed?
Subscription within ngOnInit
// Subscribe to Create User Modal Visibility
this._ComponentVisibilityService.createUserVisibility$.subscribe(
_createUserVisibility => {
this.renderComponent(
this.createUserModal,
CreateUserComponent,
this.createUserModalContainer,
_createUserVisibility
)
}
)
Function within the dashboard component
renderComponent(component, template, container, visibility) {
if (visibility) {
// Destroy previously built components if not already destroyed
if (component) component.destroy();
// Generate component factory
const componentFactory = this._ComponentFactoryResolver.resolveComponentFactory(template);
// Render the component
component = container.createComponent(componentFactory);
} else {
// Destroy the component if visibility is false
if (component) component.destroy()
}
}
So ended up doing some digging last night and found that Typescript considers any to be a basic (primitive) type. So from what I can tell, none of the methods, or structure are available to component, unless altered to not fit into the "basic" category, otherwise its passed as a value and not a reference. However, like in javascript, an object is not considered a primitive type so I refactored the component variable to be cast as an object with a property component as ComponentRef<any>, and it worked!
Component Property Declaration
createUserModal: { component: ComponentRef<any> } = { component: null }
Function Declaration
renderModal(component: { component: ComponentRef<any> }, template, container, visibility) {
// Create Component...
}
This combination allows the object to passed in as a reference which in turn allows the function to alter the value of DashboardComponent property this.createUserModal, which in turn allows all of the methods to be called on it as normal.