I've been using React for a while, and now I want to switch to using React with TypeScript. However, I've grown used to JSS styles (via the react-jss package), and I can't understand how I'm supposed to use them with TypeScript. I also use the classnames package, to assign multiple class names conditionally, and I get TypeSCript errors for that.
Here is my React component template:
import React, { Component } from 'react';
import withStyles from 'react-jss';
import classNames from 'classnames';
const styles = theme => ({
});
class MyClass extends Component {
render() {
const { classes, className } = this.props;
return (
<div className={classNames({ [classes.root]: true, [className]: className})}>
</div>
);
}
};
export default withStyles(styles)(MyClass);
I'm just learning TypeScript, so I'm not sure I even understand the errors I get. How would I write something like the above in TypeScript?
UPDATE
Here is how I finally converted my template:
import React from 'react';
import withStyles, { WithStylesProps } from 'react-jss';
import classNames from 'classnames';
const styles = (theme: any) => ({
root: {
},
});
interface Props extends WithStylesProps<typeof styles> {
className?: string,
}
interface State {
}
class Header extends React.Component<Props, State> {
render() {
const { classes, className } = this.props;
return (
<div className={classNames({ [classes.root as string]: true, [className as string]: className})}>
</div>
);
}
};
export default withStyles(styles)(Header);
Things to keep in mind:
when defining the styles object, any member of classes that is referenced in the render method has to be defined. Without TypeScript, you could get away with "using" lots of classes and not defining them, like a placeholder; with TypeScript, they all have got to be there;
in a call to the classnames function, all the keys must be typed. If they come from a variable that could be null or undefined, you have to add as string, or to convert them to string otherwise. Other than this, the className property works the same as without TypeScript.
With TypeScript, you'll need to define your props as shown in here. It is also recommended to use function component if your React component only need render method
For your case, the code should look like this:
import React from 'react';
import withStyles, { WithStyles } from 'react-jss';
import classNames from 'classnames';
const styles = theme => ({
root: {
}
});
interface IMyClassProps extends WithStyles<typeof styles> {
className: string;
}
const MyClass: React.FunctionComponent<IMyClassProps> = (props) => {
const { classes, className } = props;
return (
<div className={classNames({ [classes.root]: true, [className]: className})}>
</div>
);
};
export default withStyles(styles)(MyClass);
Related
This question is a little confusing so here's the case:
I'm trying to basically create a "use client" version of a React component that causes errors in NextJS 13:
"use client";
import { Link as ScrollLink } from "react-scroll";
import React from "react";
type ClientScrollLinkProps = {
to: string;
children: React.ReactNode;
className?: string;
};
const ClientScrollLink = ({
children,
to,
className = "",
}: ClientScrollLinkProps) => {
return (
<ScrollLink to={to} className={className} smooth={true} offset={-50}>
{children}
</ScrollLink>
);
};
export default ClientScrollLink;
This works great but I don't want to limit the component's props and redefine every prop for it. Is there a way to just spread the props without changing anything else? Kind of like this?
/* this doesn't work but this is basically what I'm looking for */
"use client";
import { Link as ScrollLink, LinkProps } from "react-scroll";
import React from "react";
interface ClientScrollLinkProps extends LinkProps {}
const ClientScrollLink: React.FC<ClientScrollLinkProps> = (props) => {
return (
<ScrollLink {...props} />
);
};
export default ClientScrollLink;
I am wrapping my class component with material UI withStyles to inject classes as a property.
export default withStyles(styles)(myComponent)
I have
const styles = ( (theme:Theme) => createStyles({className:CSS_PROPERTIES})
I am trying to declare an interface for my props as follows
interface MyComponentProps { classes : any }
What should I put instead of ANY ?
Based on this documentation piece, here is how you should do it:
import { withStyles, createStyles, Theme, WithStyles } from '#material-ui/core';
const styles = (theme:Theme) => createStyles({className:CSS_PROPERTIES})
interface MyComponentProps extends WithStyles<typeof styles> {
// you can type additional none-style related props of MyComponent here..
}
const MyComponent = ({ classes }: MyComponentProps) => {
// your component logic ....
};
export default withStyles(styles)(myComponent)
It's my first app I try to build using Typescript. I want to keep styles and components in separate files to make the code more descriptive and clear. Project will consist of dozens of components and I'll use props to call the classes. Each component will look more or less like this:
import * as React from 'react'
import withStyles from "#material-ui/core/styles/withStyles"
import { LandingPageStyles } from "./landing-page-styles"
interface LandingPageProps {
classes: any
}
class LandingPage extends React.Component<LandingPageProps> {
get classes() {
return this.props.classes;
}
render() {
return(
<div className={this.classes.mainPage}>
Hello Typescript
</div>
)
}
}
export default withStyles(LandingPageStyles)(LandingPage)
And simplified styles module :
import { createStyles } from "#material-ui/core";
export const LandingPageStyles = () => createStyles({
mainPage: {
textAlign: "center",
minHeight: "100vh",
}
})
In every component I want to have the classes props with type of any. Is there a way to avoid declaring interface for each component? It works now but I don't like my current solution beacuse of repetition the same code in every single component.
The proper way to do it is as bellow. Material-ui expose WithStyles interface that you can inherit to include classes props. The main advantage is that you IDE will handle autocompletion for the defined jss class. But anyway Typescript is more verbose than Javacript. With React you often have to repeat obvious things.
import * as React from 'react'
import {withStyles, WithStyles} from "#material-ui/core"
import { LandingPageStyles } from "./landing-page-styles"
interface LandingPageProps extends WithStyles<typeof LandingPageStyles> {
}
class LandingPage extends React.Component<LandingPageProps> {
get classes() {
return this.props.classes;
}
render() {
return(
<div className={this.classes.mainPage}>
Hello Typescript
</div>
)
}
}
export default withStyles(LandingPageStyles)(LandingPage)
The best solution is to declare interface which extends WithStyles . So in component there is need to declare:
import * as React from 'react'
import withStyles, { WithStyles } from "#material-ui/core/styles/withStyles"
import { LandingPageStyles } from "./landing-page-styles"
interface LandingPageProps extends WithStyles<typeof LandingPageStyles>{
}
class LandingPage extends React.Component<LandingPageProps> {
get classes() {
return this.props.classes;
}
render() {
return(
<div className={this.classes.mainPage}>
Hello Typescript
</div>
)
}
}
export default withStyles(LandingPageStyles)(LandingPage)
I am looking for a pattern to use react material design with typescript but I am having a few issues.
export defaults seems to be an anti pattern, and the options in my tsconfig will not allow me to do that. I am wondering is their a better way to do this without disabling it through the tsconfig.
what about a component with state which uses a class instead of functions, how would I apply withstyles to a class.
Could anybody please help me to apply the withstyles to my typical stateful component pattern without introducing anti patterns.
Typical Statefull component pattern:
import * as React from "react";
export namespace Header {
export interface Props {
}
}
export class Header extends React.Component<Header.Props> {
constructor(props: Header.Props, context?: any) {
super(props, context);
}
public render(): JSX.Element {
return (
<div> HEADER HTML </div>
);
}
}
MATERIAL UI RECOMMENDED APPROACH:
import s from './Styleguide.scss';
function Styleguide() {
...
}
function LinksComponent() {
...
}
function ButtonsComponent() {
...
}
const Links = withStyles(s)(LinksComponent);
const Buttons = withStyles(s)(ButtonsComponent);
export {
Links,
Buttons
};
export default withStyles(s)(Styleguide);
just export your styled component as a named export?
import * as React from "react";
import s from './Styleguide.scss';
export namespace Header {
export interface Props {
}
}
class BareHeader extends React.Component<Header.Props> {
constructor(props: Header.Props, context?: any) {
super(props, context);
}
public render(): JSX.Element {
return (
<div> HEADER HTML </div>
);
}
}
const Header = withStyles(s)(BareHeader);
export { Header };
I have a library of material-ui styled React components.
Styled components look like this:
import React from "react";
import withStyles from "#material-ui/core/styles/withStyles";
import headerStyle from "material-kit-pro-react/assets/jss/material-kit-pro-react/components/headerStyle.jsx";
class Header extends React.Component {
// ...
}
export default withStyles(headerStyle)(Header);
I want to customize the style of the component, but I want to keep the library untouched to avoid conflicts with future updates.
So I decided to create my own component:
import React from "react";
import withStyles from "#material-ui/core/styles/withStyles";
import Header from "material-kit-pro-react/components/Header/Header.jsx";
export default withStyles(theme => ({
primary: {
color: "#000"
}
}))(Header);
But with this approach the JSS engine creates two classes for the same component (Header-primary-25 WithStyles-Header--primary-12). That's not exactly a problem, but in my case there's a small conflict with a method in the component that expects only ONE class:
headerColorChange() {
const { classes, color, changeColorOnScroll } = this.props;
// ...
document.body
.getElementsByTagName("header")[0]
.classList.add(classes[color]) // classes[color] is a string with spaces (two classes)
;
// ...
}
Ok. It's not a big deal. I can modify that method and fix the problem.
But when I extend the class:
import React from "react";
import Header from "material-kit-pro-react/components/Header/Header.jsx";
export default class extends Header {
constructor(props) {
super(props);
this.headerColorChange = this.headerColorChange.bind(this);
}
headerColorChange() {
const { classes, color, changeColorOnScroll } = this.props
console.log(classes[color])
}
}
I get this error related to withStyles:
withStyles.js:125 Uncaught TypeError: Cannot read property '64a55d578f856d258dc345b094a2a2b3' of undefined
at ProxyComponent.WithStyles (withStyles.js:125)
at new _default (Header.jsx:11)
at new WithStyles(Header) (eval at ./node_modules/react-hot-loader/dist/react-hot-loader.development.js (http://localhost:8080/bundle.js:137520:54), <anonymous>:5:7)
And now I'm lost.
What's the best way to customize an already styled component?
How can I extend and override the constructor of a HoC (withStyles)?
Edit to provide a better and minimal example:
I have a component I can't modifiy because it's provided by a library:
./Component.jsx:
import React from "react";
import withStyles from "#material-ui/core/styles/withStyles";
class Component extends React.Component {
constructor(props) {
super(props);
this.headerColorChange = this.headerColorChange.bind(this);
}
componentDidMount() {
window.addEventListener("scroll", this.headerColorChange);
}
headerColorChange() {
const { classes } = this.props;
// THIS FAILS IF classes.primary IS NOT A VALID CLASS NAME
document.body.classList.add(classes.primary);
}
render() {
const { classes } = this.props;
return <div className={classes.primary}>Component</div>
}
}
export default withStyles(theme => ({
primary: {
color: "#000"
}
}))(Component);
I want to customize the primary color of the component. The straight way:
./CustomizedComponent.jsx:
import React from "react";
import withStyles from "#material-ui/core/styles/withStyles";
import Component from "./Component";
export default withStyles({
primary: {
color: "#00f"
}
})(Component)
Ok. The color of the has changed to #0ff. But then the component fails in document.body.classList.add(classes.primary) because classes.primary contains two class names concatenated (Component-primary-13 WithStyles-Component--primary-12). If I were allowed to modify the original component there would be no problem. But I can't, so I decided extend it and override the headerColorChange:
./CustomizedComponent.jsx:
import React from "react";
import withStyles from "#material-ui/core/styles/withStyles";
import Component from "./Component";
class MyComponent extends Component {
constructor(props) {
super(props)
this.headerColorChange = this.headerColorChange.bind(this);
}
headerColorChange() {
}
}
export default withStyles({
primary: {
color: "#00f"
}
})(MyComponent)
But then I get the error:
withStyles.js:125 Uncaught TypeError: Cannot read property '64a55d578f856d258dc345b094a2a2b3' of undefined
at ProxyComponent.WithStyles (withStyles.js:125)
at new MyComponent (Header.jsx:7)
at new WithStyles(Component) (eval at ./node_modules/react-hot-loader/dist/react-hot-loader.development.js (http://0.0.0.0:8080/bundle.js:133579:54), <anonymous>:5:7)
at constructClassInstance (react-dom.development.js:12484)
at updateClassComponent (react-dom.development.js:14255)
Questions are:
Is it possible to fully override the styles of the component so I get only one class name?
How can I extend a component returned by withStyles function as a higher order component?