I want to extend the MUI5 components and also use their component prop. To do that according to the documentation I make it work in the below code:
import Button, { ButtonProps } from '#mui/material/Button';
import React from 'react';
export type MyButtonProps<C extends React.ElementType> = ButtonProps<C, { component?: C }> & {
myOtherField: string;
};
export function MyButton<C extends React.ElementType>(props: MyButtonProps<C>) {
const { myOtherField, ...buttonProps } = props;
return <Button {...buttonProps}></Button>;
}
However when I try to access anything belongs to MUI5 button attributes, vscode is not helping me to find it
see the below screenshot:
But after adding it manually it is not complaining about it.
see the below screenshot and check that it is inferring the type as any
However if I remove the <C extends React.ElementType> logic from the type it is working like a charm (but it has a drawback since it is now complaining about the component prop when I use it anywhere in my application):
What can be the problematic part in the first place when I am extending MUI5 button prop types?
thank you in advance.
This is the easy way I found for your usecase:
type NewButtonProps = ButtonProps & {
otherField: number;
component: React.ElementType;
};
function Button3({ otherField, ...props }: NewButtonProps) {
const y = props.onClick;
const x = otherField;
return <Button {...props}></Button>;
}
All the attributes is understand by typescript now:
Related
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 want to create a wrapper around a component called ContentEditable, so that I can add a few more props and call the final component RichTextArea.
However, when I am creating the component, I am unable to pass the default props of ContentEditable via RichTextArea. This is what I expect:
<RichTextArea placeholder="Some placeholder" className="styles" html="<span>Hello</span>">
Where className and html belongs to ContentEditable and placeholder belongs to my wrapper.
I get the following error from Typescript:
No overload matches this call. Overload 1 of 2, '(props: Readonly):...
What's the correct way to achieve this?
Here's my crude implementation of RichTextArea:
import React from 'react';
import ContentEditable from "react-contenteditable";
export interface RichTextAreaProps extends ContentEditable {
placeholder ?: string
}
const RichTextArea = (props : RichTextAreaProps) => {
return (
<ContentEditable
{...props }
html={props.placeholder}
onChange={e => console.log()}
/>
);
};
export default (RichTextArea);
Could someone point out what I am doing wrong and how should I fix this?
You should extend base component props type,
import React from 'react';
import ContentEditable from "react-contenteditable";
// You have to find the type of props passing in the ContentEditable
export interface RichTextAreaProps extends IContentEditableProps {
placeholder ?: string
}
const RichTextArea = (props : RichTextAreaProps) => {
return (
<ContentEditable
{...props }
html={props.placeholder}
onChange={e => console.log()}
/>
);
};
You can choose a better way,
export interface RichTextAreaProps {
placeholder ?: string
baseComponentProps?: IContentEditableProps;
}
I go through the library you used, and I found the props type at bottom of this link.
export interface Props extends DivProps {
html: string,
disabled?: boolean,
tagName?: string,
className?: string,
style?: Object,
innerRef?: React.RefObject<HTMLElement> | Function,
}
You can use it like,
export interface RichTextAreaProps extends Props{
placeholder ?: string
}
Hope this can help!
In the Creating a component section of React Typescript Starter example, Creating a component, there is a basic React component in Typescript:
// src/components/Hello.tsx
import * as React from 'react';
export interface Props {
name: string;
enthusiasmLevel?: number;
}
function Hello({ name, enthusiasmLevel = 1 }: Props) {
if (enthusiasmLevel <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}
return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
</div>
);
}
export default Hello;
// helpers
function getExclamationMarks(numChars: number) {
return Array(numChars + 1).join('!');
}
I am new to Typescript. It seems that the interface Props is used by Typescript to do props type checks (similar to what the Proptypes npm package does). So the question is:
If I am already using this kind of Typescript
interface syntax do to props type check, do I still need to use
Proptypes package like this in the same component?
import PropTypes from 'prop-types';
Hello.propTypes = {
name: PropTypes.string,
enthusiasmLevel: PropTypes.number
};
Besides, why does here use export interface? what is the purpose
of exporting the interface Props? Is it compulsory?
Firstly I recommend declaring your components the ES6 way
const Hello: React.FC<IHello> = ({ name, enthusiasmLevel = 1 }) => {}
Your interface defines the contract of your component / The accepted parameters
export interface IHello {
name: string;
enthusiasmLevel?: number;
}
You are exporting this, so you can import your interface from other files / components which want to make use of the Hello component. For example you can use your Hello component like so from another component:
const props: IHello = {
name: "John",
enthusiamsLevel: 5
}
<Hello {...props} />
If I am already using this kind of Typescript interface syntax do to props type check, do I still need to use Proptypes in the same component?
You always want type strong definitions in TypeScript. So when declaring your prop variable in another component, you don't want to do const props: any = {
If you decide to change your interface declaration for this component later on, you would be forced to update all your references which uses this interface. - You might want to require 1 more prop variable and in that case you would want to update your usages of this interface. If you are not used to TypeScript this can seem quite hideous at first - but the benefit of always having strong type definitions will show over time. Especially when you update your type definitions.
I'm using styled-system and one key of the library is to use the shorthand props to allow easy and fast theming.
I've simplified my component but here is the interesting part:
import React from 'react'
import styled from 'styled-components'
import { color, ColorProps } from 'styled-system'
const StyledDiv = styled('div')<ColorProps>`
${color}
`
const Text = ({ color }: ColorProps) => {
return <StyledDiv color={color} />
}
I have an error on the color prop which says:
Type 'string | (string | null)[] | undefined' is not assignable to
type 'string | (string & (string | null)[]) | undefined'.
I think that's because styled-system use the same naming as the native HTML attribute color and it conflicts.
How do I solve this?
color seems to be declared in react's declaration file under HTMLAttributes - it's not exported.
I had to work around this by creating a custom prop
Example is using #emotion/styled but also works with styled-components
// component.js
import styled from '#emotion/styled';
import { style, ResponsiveValue } from 'styled-system';
import CSS from 'csstype';
const textColor = style({
prop: 'textColor',
cssProperty: 'color',
key: 'colors'
});
type Props = {
textColor?: ResponsiveValue<CSS.ColorProperty>
}
const Box = styled.div<Props>`
${textColor};
`
export default Box;
// some-implementation.js
import Box from '.';
const Page = () => (
<Box textColor={['red', 'green']}>Content in a box</Box>
);
This seems to only happen when you pass the prop down from an ancestor/parent component to a custom component rather than directly to the "styled" component. I found a discussion about it in the styled-components GitHub issues. Following the thread from there there is discussion of utilising transient props and their ultimate inclusion in styled-components v5.1.
This however didn't seem to solve the problem completely in my case.
The problem appears to be due to the component in question returning an HTML div element and so it is extended correctly (by React.HTMLAttributes) to include color: string | undefined as a DOM attribute for that element. This is of course not compatible with ColorProps hence the error. Styled-components filters out a whitelist that includes color however this won't happen in your custom or HOC.
This can be resolved in a number of ways, but the cleanest seems to be adding as?: React.ElementType to your type definition.
In this case:
import React from 'react'
import styled from 'styled-components'
import { color, ColorProps } from 'styled-system'
interface Props extends ColorProps { as?: React.ElementType }
const StyledDiv = styled('div')<Props>`
${color}
`
const Text = ({ color }: Props) => {
return <StyledDiv color={color} />
}
This way the extension by React.HTMLAttributes is replaced by React.ElementType and so there is no longer a conflict with the color DOM attribute.
This also solves problems with passing SpaceProps.
NOTE:
It appears styled-system has been unceremoniously abandoned. There are a few open issues about what is being used to replace it. My recommendation after a little deliberation is system-ui/theme-ui. It seems to be the closest direct replacement and has a few contributors in common with styled-system.
Instead of using ColorProps, try using color: CSS.ColorProperty (`import * as CSS from 'csstype'); Here is a gist showing how I'm creating some a typed "Box" primitive with typescript/styled-system: https://gist.github.com/chiplay/d10435c0962ec62906319e12790104d1
Good luck!
What I did was to use Typescript cast capabilities and keep styled-system logic intact. e.g.:
const Heading: React.FC<ColorProps> = ({ color, children }) => {
return <HeadingContainer color={(color as any)} {...props}>{children}</HeadingContainer>;
};
Just to add to xuanlopez' answer - not sure what issue the 5.0.0 release specifically resolves - but using $color as the renamed prop rather than textColor designates it as a transient prop in styled components so as a prop it won't appear in the rendered DOM.
Building on Chris' answer, and using the latest docs on on custom props.
// core/constants/theme.ts
// Your globally configured theme file
export const theme = { colors: { primary: ['#0A43D2', '#04122B'] } }
// core/constants/styledSystem.ts
import {
color as ssColor,
ColorProps as SSColorProps,
TextColorProps,
compose,
system,
} from 'styled-system'
// Styled-system patch for the color prop fixing "Types of property 'color' are incompatible"
// when appling props to component that extend ColorProps.
export interface ColorProps extends Omit<SSColorProps, 'color'> {
textColor?: TextColorProps['color']
}
export const color = compose(
ssColor,
system({
// Alias color as textColor
textColor: {
property: 'color',
// This connects the property to your theme, so you can use the syntax shown below E.g "primary.0".
scale: 'colors'
}
})
)
// components/MyStyledComponent.ts
import { color, ColorProps } from 'core/constants/styledSystem.ts'
interface MyStyledComponentProps extends ColorProps {}
export const MyStyledComponent = styled.div<MyStyledComponentProps>`
${color}
`
// components/MyComponent.ts
export const MyComponent = () => <MyStyledComponent textColor="primary.0">...
EDIT: updating to styled-components ^5.0.0 fixes this
https://github.com/styled-components/styled-components/blob/master/CHANGELOG.md#v500---2020-01-13
Hay,
I want to create reuseable compoenent in React like for example DialogBox.
It has required fields like type, message and title.
Type can be one of 'yesNo' string or 'ok' and it defines how much buttons should be shown (yes & no, ok).
Message and title are simple text that are displayed inside of dialog box.
Simple view of this:
IMG
And I created DialogBox component that I can use like:
<DialogBox type={'yesNo'} message={'Message'} title={'Title'} />
But I want to use predefined const like that:
<DialogBox type={DialogBox.TYPE.YES_NO} message={'Message'} title={'Title'} />
With simple one import DialogBox:
import Dialogbox from './DialogBox.js'
To achieve DialogBox.TYPE.YES_NO
In my component DialogBox I created static object TYPE with predefined elements:
...
static TYPE = {
YES_NO: 'yesNo',
OK: 'ok'
}
...
And everything was beautiful to the time when I wanted to use that statics to check props in child component:
ButtonArea.propTypes = {
type: PropTypes.oneOf(Object.values(DialogBox.TYPE))
};
And I got a circular dependencies error and my DialogBox.TYPE inside of props definition is undefined. That is how I can see it:
In DailogBox.js :
import ButtonArea from './BA';
export default class DialogBox extends Component {
static TYPE = {
YES_NO: 'yesNo',
OK: 'ok'
}
render() {
<ButtonArea type={this.props.type} />
}
}
In ButtonArea.js:
import DialogBox from './DB';
export default class ButtonArea extends Component {...}
ButtonArea.propTypes = {
type: PropTypes.oneOf(Object.values(DialogBox.TYPE))
};
And on checking propTypes DialogBox is an undefined cause of circular dependencies.
The question is.
Is there a way to create component to use it like component and which has inside const object definitions and avoid circular dependencies. Like :
<Test type={Test.TYPE.SUPER_TEST}/>
I don't want to import Test and TestConst and use it like that:
<Test type={TestConst.TYPE.SUPER_TEST}/>
You need to update the propsType for button , you need to check for the KEYS instead of VALUES, like below
ButtonArea.propTypes = {
type: PropTypes.oneOf(Object.keys(DialogBox.TYPE))
};