What ES6/React magic is used in this class inheritance? - javascript

Going through the TodoMVC example of Redux I have found this unusual example of class inheritance. The class Header is probably extending React.Component as per usual (as should all React components, right?), but it is not explicitly stated in the code. What am I missing? How does this code work?
import React, { PropTypes } from 'react';
import TodoTextInput from './TodoTextInput';
export default class Header {
static propTypes = {
addTodo: PropTypes.func.isRequired
};
handleSave(text) {
if (text.length !== 0) {
this.props.addTodo(text);
}
}
render() {
return (
<header className='header'>
<h1>todos</h1>
<TodoTextInput newTodo={true}
onSave={::this.handleSave}
placeholder='What needs to be done?' />
</header>
);
}
}

If you don't need the methods defined by ReactComponent (setState() and forceUpdate()) you don't have to inherit from it.
As such, it isn't an example of class inheritance or magic because neither is happening here :)

Related

LitElement doesn't update child component from parent component

I don't understand the concept of reactivity in lit's web components architecture. From other frameworks I come up with the assumption that the following example would update without problem, but it doesn't work with lit.
I can see that the child components render method is only being called initially and not again after I click the button. But even if I call it manually via the Web Components DevTools, it doesn't re-render with the new state.
What do I have to change to make it work?
Parent component:
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import './show-planets';
#customElement('lit-app')
export class LitApp extends LitElement {
addPlanet() {
this.planetsParent.push('Pluto')
console.log('this.planetsParent', this.planetsParent)
}
#property({type: Array}) planetsParent = ['Mars'];
render() {
return html`
<button #click="${this.addPlanet}">click</button>
<show-planets .planetsChild="${this.planetsParent}"></show-planets>
`;
}
}
Child component:
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
#customElement('show-planets')
export class ShowPlanets extends LitElement {
#property({type: Array}) planetsChild = ['Pluto'];
render() {
console.log('this.planetsChild', this.planetsChild);
return html`<h1>Planets are: ${this.planetsChild}</h1>`;
}
}
LitElement's property system only observes changes to the reference. Recursively listening for changes to child properties would be prohibitively expensive, especially for large nested objects.
Therefore, setting a child or grandchild property of this.planetsParent will not trigger a render.
So what can we do if we need to update a nested child? Immutable data patterns can help us.
addPlanet() {
const [...rest] = this.planetsParent;
const newItem = 'Pluto';
this.planetsParent = [newItem, ...rest];
}
Reference: https://open-wc.org/guides/knowledge/lit-element/rendering/#litelement-rendering

Why does this.renderChart not exist on CombinedVueInstance? [duplicate]

While rewriting my VueJs project in typescript, I came across a TypeScript error.
This is a part of the component that has a custom v-model.
An input field in the html has a ref called 'plate' and I want to access the value of that. The #input on that field calls the update method written below.
Typescript is complaining that value does not exist on plate.
#Prop() value: any;
update() {
this.$emit('input',
plate: this.$refs.plate.value
});
}
template:
<template>
<div>
<div class="form-group">
<label for="inputPlate" class="col-sm-2 control-label">Plate</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputPlate" ref="plate" :value="value.plate" #input="update">
</div>
</div>
</div>
</template>
You can do this:
class YourComponent extends Vue {
$refs!: {
checkboxElement: HTMLFormElement
}
someMethod () {
this.$refs.checkboxElement.checked
}
}
From this issue: https://github.com/vuejs/vue-class-component/issues/94
Edit - 2021-03 (Composition API)
Updating this answer because Vue 3 (or the composition API plugin if you're using Vue 2) has some new functions.
<template>
<div ref="root">This is a root element</div>
</template>
<script lang="ts">
import { ref, onMounted, defineComponent } from '#vue/composition-api'
export default defineComponent({
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(root.value) // <div>This is a root element</div>
})
return {
root
}
}
})
</script>
Edit - 2020-04:
The vue-property-decorator library provides #Ref which I recommend instead of my original answer.
import { Vue, Component, Ref } from 'vue-property-decorator'
import AnotherComponent from '#/path/to/another-component.vue'
#Component
export default class YourComponent extends Vue {
#Ref() readonly anotherComponent!: AnotherComponent
#Ref('aButton') readonly button!: HTMLButtonElement
}
Original Answer
None of the above answers worked for what I was trying to do. Adding the following $refs property wound up fixing it and seemed to restore the expected properties. I found the solution linked on this github post.
class YourComponent extends Vue {
$refs!: {
vue: Vue,
element: HTMLInputElement,
vues: Vue[],
elements: HTMLInputElement[]
}
someMethod () {
this.$refs.<element>.<attribute>
}
}
son.vue
const Son = Vue.extend({
components: {},
props: {},
methods: {
help(){}
}
...
})
export type SonRef = InstanceType<typeof Son>;
export default Son;
parent.vue
<son ref="son" />
computed: {
son(): SonRef {
return this.$refs.son as SonRef;
}
}
//use
this.son.help();
This worked for me: use
(this.$refs.<refField> as any).value or (this.$refs.['refField'] as any).value
Avoid using bracket < > to typecast because it will conflict with JSX.
Try this instead
update() {
const plateElement = this.$refs.plate as HTMLInputElement
this.$emit('input', { plate: plateElement.value });
}
as a note that I always keep remembering
Typescript is just Javascript with strong typing capability to ensure type safety. So (usually) it doesn't predict the type of X (var, param, etc) neither automatically typecasted any operation.
Also, another purpose of the typescript is to make JS code became clearer/readable, so always define the type whenever is possible.
Maybe it will be useful to someone. It looks more beautiful and remains type support.
HTML:
<input ref="inputComment" v-model="inputComment">
TS:
const inputValue = ((this.$refs.inputComment as Vue).$el as HTMLInputElement).value;
In case of custom component method call,
we can typecast that component name, so it's easy to refer to that method.
e.g.
(this.$refs.annotator as AnnotatorComponent).saveObjects();
where AnnotatorComponent is class based vue component as below.
#Component
export default class AnnotatorComponent extends Vue {
public saveObjects() {
// Custom code
}
}
With Vue 3 and the Options API, this is what worked for me:
<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent({
methods: {
someAction() {
(this.$refs.foo as HTMLInputElement).value = 'abc';
},
},
});
</script>
The autocomplete doesn't bring the foo property from $refs because it's defined in the template, and apparently there's no information inferred from it.
However, once you force the casting of .foo to the HTML element type, everything works from there on, so you can access any element property (like .value, in the example above).
Make sure to wrap your exports with Vue.extend() if you are converting your existing vue project from js to ts and want to keep the old format.
Before:
<script lang="ts">
export default {
mounted() {
let element = this.$refs.graph;
...
After:
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
mounted() {
let element = this.$refs.graph;
...
I found a way to make it work but it is ugly in my opinion.
Feel free to give other/better suggestions.
update() {
this.$emit('input', {
plate: (<any>this.$refs.plate).value,
});
}
I spent a LONG time trying to find an answer to this using Vue 3, TypeScript with class components and (as it happens, although not relevant to this) TipTap. Found the answer from bestRenekton above which finally solved it, but it needed tweaking. I'm pretty sure this is TypeScript specific.
My child component has this at the start:
export default class WhealEditor extends Vue {
It includes this method (the one I want to call from the parent):
doThis(what: string) {
console.log('Called with ' + what)
}
And this right at the end:
export type EditorRef = InstanceType<typeof WhealEditor>
</script>
So this announces to any consumer of the child component that it can access it using the variable EditorRef. The parent component includes the child component in the template:
<WhealEditor ref="refEditor" />
The parent component then imports ref, and the child component and the exposed object:
import { ref } from 'vue'
import WhealEditor, { EditorRef } from './components/WhealEditor.vue'
I then have a method to get this object:
getEditor(): EditorRef {
// gets a reference to the child component
return this.$refs.refEditor as EditorRef
}
Finally, I can handle events - for example:
processButton(msg: string) {
// runs method in child component
this.getEditor().doThis(msg)
Like everything else to do with client script, it's so much harder than I expected!

Reusing code across React component

I have a bunch of components with methods like these
class Header extends Component {
sidebarToggle(e) {
e.preventDefault();
document.body.classList.toggle('sidebar-hidden');
}
sidebarMinimize(e) {
e.preventDefault();
document.body.classList.toggle('sidebar-minimized');
}
}
I'd like to move this duplicate code to a function such as
function toggleBodyClass(className, e) {
e.preventDefault();
document.body.classList.toggle('sidebar-mobile-show');
}
Then refactor the functions above like so
sidebarMinimize(e) {
toggleBodyClass('sidebar-minimized', e);
}
In the past, I would have used a mixin, but the React docs now discourage their use.
Should I just put this function in a regular JavaScript module and import it in the component modules, or is there a particular React construct for reusing code across components?
You could make a High Order Component with those functions as so:
import React, { Component } from 'react';
export default function(ComposedComponent) {
return class ExampleHOC extends Component {
sidebarToggle(e) {
e.preventDefault();
document.body.classList.toggle('sidebar-hidden');
}
sidebarMinimize(e) {
e.preventDefault();
document.body.classList.toggle('sidebar-minimized');
}
render()
return <ComposedComponent { ...this.props } />;
}
}
}
Then take whatever component you wish to augment with those properties by wrapping them in the HOC:
ExampleHOC(Header);
Should I just put this function in a regular JavaScript module and import it in the component modules
Yes. That would be a pretty standard way to share code between JavaScript files. I don't believe you need to or should do anything React-related to achieve this.
However, it is important to understand that you shouldn't directly interact with the DOM ever from a React component. Thanks #ShubhamKhatri for the heads up.
In my opinion, you are correct in putting the function in a regular JavaScript module and import it in the component modules.
Since a typical answer OOP answer would be to create another class extending React.Component adding that function. Then extend that class so every component you create will have that function but React doesn't want that.
One thing to verify that you are correct is in this pattern I believe.
https://reactjs.org/docs/composition-vs-inheritance.html
inherence solve your problem , create new class that extends Component and extend from your new class to share functionality and reduce the code
class SuperComponent extends Component
{
sidebarToggle(e) {
e.preventDefault();
document.body.classList.toggle('sidebar-hidden');
}
sidebarMinimize(e) {
e.preventDefault();
document.body.classList.toggle('sidebar-minimized');
}
}
---------------------------------------------------------------------
class Home extends SuperComponent
{
someMethod()
{
this.sidebarMinimize();
}
}
class Main extends SuperComponent
{
someMethod()
{
this.sidebarToggle();
}
}
Other Solution
create utils class and use it in your component
class UIUtiles
{
static sidebarToggle(e) {
e.preventDefault();
document.body.classList.toggle('sidebar-hidden');
}
static sidebarMinimize(e) {
e.preventDefault();
document.body.classList.toggle('sidebar-minimized');
}
}
class Home extends SuperComponent {
someMethod(e) {
UIUtiles.sidebarToggle(e);
UIUtiles.sidebarMinimize(e);
}
}

React propTypes outside Component

I was into functional Javascript previously, Recently I started with Object oriented Javascript and React Library. This question is more of understanding the code.
Why below code don't work
class MyComponent extends React.Component{
propTypes : {
name: React.PropTypes.string.isReequired,
location: React.PropTypes.string
}
render(){
return(
<h1>Hello This is {this.props.name} and I live in
{this.props.location}</h1>
);
}
}
ReactDOM.render(
<MyComponent name="Node" location="DOM"/>,
document.getElementById('root')
);
Whereas this code works,
class MyComponent extends React.Component{
render(){
return(
<h1>Hello This is {this.props.name} and I live in {this.props.location}</h1>
);
}
}
MyComponent.propTypes = {
name: React.PropTypes.string.isReequired,
location: React.PropTypes.string
}
ReactDOM.render(
<MyComponent name="Node" location="DOM"/>,
document.getElementById('root')
);
Can someone help me understand this? Thanks.
You need to use static word (to define the static property) because, propTypes need to be declared on the class itself, not on the instance of the class , and use =.
Check the DOC.
Like this:
static propTypes = {
name: React.PropTypes.string.isRequired,
location: React.PropTypes.string
}
Inside an ES6 class, static properties look like this
class X extends Y {
static staticThing = {
...
}
}
note the =
"assigning" a static property the ES5 way looks like the second way you have it there
Typically, you'll use the second way for functional components whereas you might as well use the first way (albeit properly with an =) for ES6 style class components.
also, make sure you have your React.PropTypes correct - isReequired should be isRequired

React extends defautProps

I am still on the way of the conquest React. I prefer to use a es6 component-based approach when creating React classes. And I detained at the moment when it is necessary to inheritance of some existing class with already defined defaultProps static property.
import {Component} from 'react';
class MyBox extends Component {
}
// Define defaultProps for MyBox
MyBox.defaultProps = {
onEmptyMessage: 'Nothing at here'
}
When I define a static property defaultProps of class that extend MyBox, it completely overwrites defaultProps of parent class.
class MyItems extends MyBox {
render() {
// this.props.onEmptyMessage is undefined here
// but this.props.onRemoveMessage is present
return <i>{this.props.onEmptyMessage}</i>; //<i></i>
}
}
// Define defaultProps for MyItems
MyItems.defaultProps = {
onRemoveMessage: 'Are you sure?'
}
But i need to extend defaultProps of parent class, not overwrite. I understand that is possible by extending directly defaultProps property of parent class.
MyItems.defaultProps = _.extend(MyBox.defaultProps, {
onRemoveMessage: 'Are you sure?'
});
But i think that such trick is dirty. Is there a way to perform it according to React plan?
In my opinion you the issue is not how you can merge the properties but how you can compose it without extending. You would have your components like this:
class MyBox extends Component {
static defaultProps = { title: 'Box title' }
render() {
return (
<div className={'mybox ' + this.props.className}>
<h1>{this.props.title}</h1>
{this.prop.children}
</div>
)
}
}
class MyItems extends Component {
render() {
return (
<MyBox className="itembox" title="Items title">
<i>{this.props.children}</i>
</MyBox>
)
}
}
Then use it like that
<MyBox>
box
</MyBox>
or
<MyItems>
items box
</MyItems>
That will render:
<div class="mybox">
<h1>Box title</h1>
box
</div>
or
<div class="mybox itembox">
<h1>Items title</h1>
<i>items box</i>
</div>
It looks like you have extending/overriding the className or the title, but you have composed. If you use this way of building your UI, you'll see it will be easier and faster

Categories