I am using a library component which is declared this way:
import React, { ReactNode } from 'react';
export declare class Splide extends React.Component<SplideProps> {
When trying to use it in my projects, typescript throws the following error:
TS2786: 'Splide' cannot be used as a JSX component. Its instance type 'Splide' is not a valid JSX element. Type 'Splide' is missing the following properties from type 'ElementClass': context, setState, forceUpdate, props, and 2 more.
If I ignore the error it seems the component works properly once deployed, so I'm guessing it's a typescript issue
I could confirm that modifying the library to be written this way fixed the issue:
import React, { ReactNode, Component } from 'react';
export declare class Splide extends Component<SplideProps> {
as does this:
import * as React from 'react';
export declare class Splide extends React.Component<SplideProps> {
However I don't really want to keep a modified version of the library locally, and since nobody else has mentioned that issue it's probably a problem with my setup. I could just #ts-ignore every usage of that component or create a middleware component that extends and fixes that component but again, not great practices.
Some additional points I checked:
I saw mention of using resolutions in package.json to enforce #types/react and react versions, but it didn't work
I double checked inside react, and the class Component is inside the namespace React so as far as I can tell React.Component should work
Related
Writing unit testing in react using jest and enzyme. While checking with a component state , it throws an error "ReactWrapper::state() can only be called on class components ".
import React from 'react';
import { mount } from 'enzyme';
import expect from 'expect';
import CustomerAdd from '../CustomerAdd'
import MUITheme from '../../../../Utilities/MUITheme';
import { ThemeProvider } from '#material-ui/styles';
describe('<CustomerAdd />', () => {
const wrapper = mount(
<ThemeProvider theme={MUITheme}>
<CustomerAdd {...mockProps}></CustomerAdd>
</ThemeProvider>
);
test('something', () => {
expect(wrapper.find(CustomerAdd).state('addNewOnSubmit')).toEqual(true);
});
});
In the above code CustomerAdd Component is class component.I don't what wrong with my code. Can any one help me out of this problem. Thanks in advance.
So your default export
export default withStyles(styles)(CustomerAdd);
exports functional(HOC) wrapper about your class-based component. And it does not matter if name of class and import in
import CustomerAdd from '../CustomerAdd'
are equal. Your test imports wrapped version and after calling .find(CustomerAdd) returns that HOC not your class. And you're unable to work with instance.
Short time solution: export class directly as named export.
export class CustomerAdd extends React.Component{
...
}
export default withStyles(styles)(CustomerAdd);
Use named import in your tests:
import { CustomerAdd } from '../CusomerAdd';
Quick'n'dirty solution: use .dive to access your underlying class-based component:
expect(wrapper.find(CustomerAdd).dive().state('addNewOnSubmit')).toEqual(true);
It's rather antipattern since if you add any additional HOC in your default export you will need to monkey-patch all related tests with adding appropriate amount of .dive().dive()....dive() calls.
Long-term solution: avoid testing state, it's implementation details.
Instead focus on validating what's been rendered. Then you are safe in case of lot of different refactoring technics like replacing class with functional component, renaming state/instance members, lifting state up, connecting component to Redux etc.
So I have a project where I have some react components that look like this (all in the same file List.js):
export default class List extends Component {
render(){//stuff}
}
List.Item = (props, context) => {
//function component stuff
}
Basically there's a react function component as a key of another react component if that makes sense. This is something I can't change, it's part of a library I am using. I'm trying to define a index.d.ts for this library, but when I try to define "List" like so:
//in index.d.ts
export as namespace myLibrary
export declare class List extends React.Component<any, any> {
Item: React.FunctionComponent<any>
}
how in the world do I add List.Item as an attribute of the type "list"?? I have tried overriding it by defining a second class/interface that extends the first, tried using a constructor to assign it to 'this', and tried the above, but none did the job
turns out I just needed "static" on my Item field in my List class. that was extremely hard to find
I'm using the package react-translate to localise my app.
import React from 'react';
import { translate } from 'react-translate';
class Hello extends React.Component {
render() {
return (
<div>
{this.props.t('test_string')}
</div>
);
}
}
export default translate('Hello')(Hello);
In the snippet above, translate is a High Order Component, that adds the function t to the properties of Hello.
Everything works fine but lint keeps complaining because t is not in the propTypes.
error 't' is missing in props validation react/prop-types
Is that normal? I guess I'm doing something wrong but I cannot tell what...
Edit:
As #stevejay says, I could add the t to my propTypes but I don't like this solution because - from my total ignorance in react - 1) t is not a property of the component itself, nor something I want to manually pass and 2) I have to add the property to all the models where I have already added the HOC and it seems redundant
To silence the linter, you need to just add propTypes to your Hello component:
import React from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-translate';
class Hello extends React.Component {
...
}
Hello.propTypes = {
t: PropTypes.func.isRequired
}
Any props that a component you create uses should be declared in that way.
I have a very basic program of react with tsx, I am getting an error which I am not able to figure out why
import React from 'react';
// import {connect} from 'react-redux'
export class Home extends React.Component {
render(){
console.log(this.props)
return (
<div>Working</div>
)
}
}
import * as React from 'react'
import * as ReactDOM from 'react-dom';
import {Home} from './Components/Home.component'
class App extends React.Component<any,any>{
render(){
return(
<Home value="abc" />
)
}
}
ReactDOM.render( <App />, window.document.getElementById("app"))
git clone this for code
After pulling down your repo and inspecting it, I realised that you do not have react typings for typescript.
Typings is the simple way to manage and install TypeScript definitions
Adding this line
"#types/react": "^16.0.25" // or another version you prefer
to the package.json and running npm i or yarn if you are using yarn as a package manager, one more time, solved the issue.
Try it out and let me know if this solves it on your side :)
PS: TypeScript requires you to describe the shape of your objects and your data. If you look at the other answer I provided earlier, it was pretty much a long and complicated version of You need to specify a type that describes your props and need to pass this to the component in question
Typescript needs to know the shape of the props and state passed to a component. If you really want to stop Typescript from enforcing typings in your component (which, btw, defeats the whole purpose of using Typescript), then, the component that needs access to the props or state passed to it has to specify the type or shape so to speak, as any. That is, your component will look something like this
export class Home extends React.Component<any, any>
instead of
export class Home extends React.Component
which btw, is an incorrect way of extending a class if that class expects props and/or state.
Passing any type for props and state means that the component in question must accept any kind of shape (type) for both props and state.
Try this
import * as React from "react";
import * as ReactDOM from 'react-dom';
export class Home extends React.Component<any, any> {
render() {
console.log(this.props)
return (
<div>Working</div>
)
}
}
class App extends React.Component{
render() {
return (
<Home value="abc" />
)
}
}
ReactDOM.render(<App />, document.getElementById("app"));
and everything should work as expected because you got Typescript out of your way in terms of type-checking for you.
You can also view the demo here
If you actually wanted to enforce the shape (type) of the props and/or state you would then have to define these shapes with, usually, an interface or inline type annotation. Here is an example of the same code above that enforces the shape of the props using the former method:
import * as React from "react";
import { render } from "react-dom";
interface Props {
value:string,
name:string
}
export default class Home extends React.Component<Props>{
render() {
console.log(this.props)
return (
<div>Working. The props values are: {this.props.value} {this.props.name}</div>
)
}
}
class App extends React.Component {
render() {
return (
<Home value="abc" name="def"/>
)
}
}
render(<App />, document.getElementById("root"));
Now, here you could never be able to add any other prop to the Home component that is not defined in the Props interface.
For example doing something like:
<Home value="abc" name="DEF" somethin="else"/>
would not compile because somethin is not defined in the interface that is used by the Home component.
To enforce the shape of the state you'd have to do the same thing as for the props, i.e. define a contract (interface).
Also, note that you still need to access your props via this NOT Props as this is just a type definition of the structure not holder of the values themselves.
You can view the demo for this alternative here
I have a class extending react.component. Another class extends from this class and connects to a redux-store.
import React from 'react'
class A extends React.Component{
constructor(props){super(props); ...}
...
}
import React from 'react'
import {connect} from 'react-redux'
#connect(store=>{...})
class B extends A{
constructor(props){super(props); ...}
...
}
class C extend A{....}
Unfortunately this doesn't work. Isn't it possible to connect a class to a store without explicitly extending the React.Component?
Error:
Uncaught TypeError: Super expression must either be null or a function, not undefined
I imported class a via index.js. This didn't work, importing the file itself worked.
As Vincent Taing said, it's not a good practice what you are trying to do. Instead use a HOC ( high order component ) to handle all of your logic (handlers, etc ), connect this component with your store and return your UI component with your handler.