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!
I am not using React.
I am using Stenciljs.
I have the following .tsx file:
export class MyComponent {
#Prop() message: string;
render() {
return (<div>{this.message}</div>);
}
}
I want to do this instead:
import myTemplate from '../my-template.??';
export class MyComponent {
#Prop() message: string;
render() {
return (myTemplate);
}
}
with ../my-template.?? containing:
<div>{this.message}</div>
Is it possible and how ? Thanks in advance for any help :)
Yes, you can absolutely do this, there are just a couple of things you need to tidy up:
Main file
import { Template } from '../template'; // No need for file extension but we're using a named export so we need the curly braces around 'Template'
export class MyComponent {
#Prop() message: string;
render() {
return ( // You don't technically need the parentheses here as you're just returning one thing
<Template /> // When outputting an imported component, it goes in angle brackets and the backslash closes it like an HTML element
)
}
}
Template
import React from 'react'; // template needs React
export const Template = () => { // defining the export in this way is known as a named export
return (
<p>A message here</p>
)
}
Okay, so that's going to get you a message output which is from your template. However, you were asking about passing a message to that template for it to output. That's totally easy as well - you just need to get some props in there. Here is the modified version of the above:
Main file
import { Template } from '../template';
export class MyComponent {
#Prop() message: string;
render() {
return (
<Template messageToOutput={message} /> // The first argument is the name of the prop, the second is the variable you defined above
)
}
}
Template
import React from 'react';
export const Template = (props) => { // props are received here
return (
<p>{props.messageToOutput}</p> // props are used here
)
}
That's how you pass data around in React - hope that helps!
I would like to add <p> tag in ExtendedComponent by calling super.render(), Problem is I don't know whether it is possible to modify already defined jsx object. Ideal would be to just write parentTemplate + <p>Some paragraph</p>, but that doesn't work.
class BaseComponent extends React.Component {
get title() {
return 'I am base component';
}
render() {
return <h1>Hello {this.title}</h1>;
}
}
class ExtendedComponent extends BaseComponent {
get title() {
return 'I am extended component';
}
render() {
var parentTemplate = super.render();
// append <p>Some paragraph</p> to parentTemplate
return parentTemplate;
}
}
ReactDOM.render(
<ExtendedComponent />,
document.getElementById('test')
);
Like azium mentioned in the comments, this is not common to do with react. However, if you need to do it, it can be accomplished like this:
render() {
var parentTemplate = super.render();
// append <p>Some paragraph</p> to parentTemplate
return <div>{parentTemplate}<p>Some paragraph</p></div>;
}
You have to wrap it inside a div since a react element only can return one element, not a list of them. parentTemplate is just like any other jsx, but it's in a variable. You use the {variableName} syntax to add variables into the JSX.
Another way to do this, which is more "react"-like:
class BaseComponent extends React.Component {
render() {
return <h1>Hello {this.props.title}</h1>;
}
}
BaseComponent.propTypes = {
title: React.PropTypes.string
};
BaseComponent.defaultProps = {
title: 'I am base component'
};
class ExtendedComponent extends React.Component {
render() {
return (
<div>
<BaseComponent title="I am extended component"/>
<p>Some paragraph</p>
</div>
);
}
}
ReactDOM.render(
<ExtendedComponent />,
document.getElementById('test')
);
JSFiddle
Here ExtendedComponent is a higher-order component rather than one that inherits from BaseComponent. Most of the time this is a seperation of concerns that is easier to reason about, and most react apps are built this way rather than on inheritation.
Hope this helps!
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
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 :)