

GitHub - garronej/tss-react: ✨ makeStyles is dead, long live makeStyles! ✨
source link: https://github.com/garronej/tss-react
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

makeStyles is dead, long live makeStyles!
'tss-react'
is intended to be the replacement for 'react-jss'
and for
@material-ui v4 makeStyles
.
Seamless integration with mui v5 and v4.
Build on top of
@emotion/react
, it has virtually no impact on the bundle size alongside mui.Maintained for the foreseeable future, issues are dealt with within good delays.
Offers a type-safe equivalent of the JSS
$
syntax.Server side rendering support (e.g: Next.js).
As fast as
emotion
(see the difference with mui'smakeStyles
)@emotion
cache support.
$ yarn add tss-react @emotion/react
Quick start
Minimal setup
./makeStyles.ts
import { createMakeStyles } from "tss-react"; function useTheme() { return { "primaryColor": "#32CD32", }; } export const { makeStyles } = createMakeStyles({ useTheme });
./MyComponent.tsx
import { makeStyles } from "./makeStyles"; const useStyles = makeStyles<{ color: "red" | "blue" }>()( (theme, { color }) => ({ "root": { color, "&:hover": { "backgroundColor": theme.primaryColor, }, }, }), ); export function MyComponent(props: Props) { const { className } = props; const [color, setColor] = useState<"red" | "blue">("red"); const { classes, cx } = useStyles({ color }); return <span className={cx(classes.root, className)}>hello world</span>; }
Mui integration
Don't use <StyledEngineProvider injectFirst/>
but do this instead:
import { render } from "react-dom"; import { CacheProvider } from "@emotion/react"; import createCache from "@emotion/cache"; import { ThemeProvider } from "@mui/material/styles"; export const muiCache = createCache({ "key": "mui", "prepend": true, }); render( <CacheProvider value={muiCache}> <ThemeProvider theme={myTheme}> <Root /> </ThemeProvider> </CacheProvider>, document.getElementById("root"), );
./makeStyles.ts
import { useTheme } from "@mui/material/styles"; //WARNING: tss-react require TypeScript v4.4 or newer. If you can't update use: //import { createMakeAndWidthStyles } from "tss-react/compat"; import { createMakeAndWidthStyles } from "tss-react"; export const { makeStyles, withStyles } = createMakeAndWithStyles({ useTheme, /* OR, if you have extended the default mui theme adding your own custom properties: Let's assume the myTheme object that you provide to the <ThemeProvider /> is of type MyTheme then you'll write: */ //"useTheme": useTheme as (()=> MyTheme) });
WARNING: Keep @emotion/styled
as a dependency of your project. Even if you never use it explicitly,
it's a peer dependency of @mui/material
.
WARNING for Storybook: As of writing this lines storybook still uses by default emotion 10.
Material-ui and TSS runs emotion 11 so there is some changes
to be made to your .storybook/main.js
to make it uses emotion 11.
Avoiding import { makeStyles } from "../../../makeStyles"
If you don't want to end up writing things like:
import { makeStyles } from "../../../../../../makeStyles";
You can put "baseUrl": "src"
in
your tsconfig.json
and import things relative to your src/
directory.
Playground
Try it now:
API documentation
Exposed APIs
import { createMakeAndWithStyles, //<- Create an instance of makeStyles() and withStyles() for your theme. keyframes, //<- The function as defined in @emotion/react and @emotion/css GlobalStyles, //<- A component to define global styles. TssCacheProvider, //<- Provider to specify the emotion cache tss should use. useCssAndCx, //<- Access css and cx directly. // (Usually you'll use useStyles returned by makeStyles or createMakeStyles for that purpose // but if you have no theme in your project, it can come in handy.) } from "tss-react";
makeStyles()
Your component style may depend on the props and state of the components:
const useStyles = makeStyles<{ color: string }>()((_theme, { color }) => ({ "root": { "backgroundColor": color, }, })); //... const { classes } = useStyles({ "color": "grey" });
...Or it may not:
const useStyles = makeStyles()({ //If you don't need neither the theme nor any state or //props to describe your component style you can pass-in //an object instead of a callback. "root": { "backgroundColor": "pink", }, }); //... const { classes } = useStyles();
useStyles()
Beside the classes
, useStyles
also returns cx
, css
and your theme
.
css
is the function as defined in @emotion/css
cx
is the function as defined in @emotion/css
const { classes, cx, css, theme } = useStyles(/*...*/);
In some components you may need cx
, css
or theme
without defining
custom classes
.
For that purpose you can use the useStyles
hook returned
by createMakeStyles
.
makeStyles.ts
import { createMakeAndWithStyles } from "tss-react"; function useTheme() { return { "primaryColor": "#32CD32", }; } export const { makeStyles, useStyles, //<- This useStyles is like the useStyles you get when you // call makeStyles but it doesn't return a classes object. } = createMakeAndWithStyles({ useTheme });
./MyComponent.tsx
//Here we ca import useStyles directly instead of generating it from makeStyles. import { useStyles } from "./makeStyles"; export function MyComponent(props: Props) { const { className } = props; const { cx, css, theme } = useStyles(); return ( <span className={cx(css({ "color": theme.primaryColor }), className)}> hello world </span> ); }
withStyles()
It's like the material-ui v4 higher-order component API but type safe by design.
IMPORTANT NOTICE: Don't be afraid to use as const
when you get red squiggly lines.
You can pass as first argument any component that accept a className
props:
function MyComponent(props: { className?: string; colorSmall: string }) { return ( <div className={props.className}> The background color should be different when the screen is small. </div> ); } const MyComponentStyled = withStyles(MyComponent, (theme, props) => ({ "root": { "backgroundColor": theme.palette.primary.main, "height": 100, }, "@media (max-width: 960px)": { "root": { "backgroundColor": props.colorSmall, }, }, }));
You can also pass a mui component like for example <Button />
and you'll be able
to overwrite every rule name of the component (it uses the
classes
prop).
import Button from "@mui/material/Button"; const MyStyledButton = withStyles(Button, { "root": { "backgroundColor": "grey", }, "text": { "color": "red", }, "@media (max-width: 960px)": { "text": { "color": "blue", }, }, });
It's also possible to start from builtin HTML component:
const MyAnchorStyled = withStyles("a", (theme, { href }) => ({ "root": { "border": "1px solid black", "backgroundColor": href?.startsWith("https") ? theme.palette.primary.main : "red", }, }));
You can experiment with those examples here
live here, you can also run it locally with yarn start_spa
.
<GlobalStyles />
Sometimes you might want to insert global css.
You can use the <GlobalStyles />
component to do this.
It's styles
(with an s) prop should be of same type as the css()
function
argument.
import { GlobalStyles } from "tss-react"; function MyComponent() { return ( <> <GlobalStyles styles={{ "body": { "backgroundColor": "pink", }, ".foo": { "color": "cyan", }, }} /> <h1 className="foo">This text will be cyan</h1> </> ); }
keyframes
// Reexport from @emotion/react import { keyframes } from "tss-react"; import { makeStyles } from "./makeStyles"; export const useStyles = makeStyles()({ "svg": { "& g": { "opacity": 0, "animation": `${keyframes` 60%, 100% { opacity: 0; } 0% { opacity: 0; } 40% { opacity: 1; } `} 3.5s infinite ease-in-out`, }, }, });
Cache
By default, tss-react
uses an emotion cache that you can access with
import { getTssDefaultEmotionCache } from "tss-react"
.
Now if you want tss-react
to use a specific emotion cache you can provide it using
import { TssCacheProvider } from "tss-react"
.
If you are using tss-react
with mui v5, be aware that mui and tss can't share
the same cache. On top of that the cache used by mui should have "prepend": true
and
the cache used by tss should have "prepend": false
.
Composition and nested selectors ( $
syntax )
tss-react
unlike jss-react
doesn't support the $
syntax but there's type safe alternatives that
achieve the same results.
Selecting children by class name
In JSS you can do:
{ "parent": { "padding": 30, "&:hover $child": { "backgroundColor": "red" }, }, "child": { "backgroundColor": "blue" } } //... <div className={classes.parent}> <div className={classes.children}> Background turns red when the mouse is hover the parent </div> </div>
This is how you would achieve the same result with tss-react
const useStyles = makeStyles()((_theme, _params, createRef) => { const child = { "ref": createRef(), "background": "blue", } as const; //<- In many case 'as const' must be used so that it can be inferred as CSSObject return { "parent": { "padding": 30, [`&:hover .${child.ref}`]: { "background": "red", }, }, child, }; }); export function App() { const { classes } = useStyles(); return ( <div className={classes.parent}> <div className={classes.child}> Background turns red when mouse is hover the parent. </div> </div> ); }
Internal composition
When you want to reuse style within the same component.
import { makeStyles } from "./makeStyles"; import type { CSSObject } from "tss-react"; const useStyles = makeStyles<{ n: number; color: string }>()( (theme, { n, color }) => { const root: CSSObject = { "color": theme.primaryColor, "border": `${n}px solid black`, }; return { root, "foo": { ...root, //Style specific to foo color, }, }; }, );
Export rules
MyComponent.tsx
import { makeStyles } from "./makeStyles"; // You can always define the Theme type as: "export type Theme = ReturnType<typeof useTheme>;" import type { Theme } from "./makeStyles"; import type { CSSObject } from "tss-react"; //Can be used in another component export const getRootStyle = ( theme: Theme, params: { n: number }, ): CSSObject => ({ "color": theme.primaryColor, "border": `${params.n}px solid black`, }); const useStyles = makeStyles< Parameters<typeof getRootStyle>[1] & { color: string } >()((theme, { n, color }) => ({ "root": getRootStyle(theme, { n }), // Other styles... }));
Server Side Rendering (SSR)
There are some minimal configuration required to make tss-react
work with SSR.
The following instructions are assuming you are using tss-react
standalone
or alongside @material-ui
v5. You can find here
a Next.js setup with @material-ui
v4.
With Next.js
If you don't have a _document.tsx
Just create a file page/_document.tsx
as follow:
import { createDocument } from "tss-react/nextJs"; const { Document } = createDocument(); /* With mui v5 (or if you are using custom caches): import { muiCache } from "..."; const { Document } = createDocument({ "caches": [ muiCache ] }); If you are providing custom caches to tss-react using <TssCacheProvider value={tssCache} > you should pass it as well. const { Document } = createDocument({ "caches": [ muiCache, tssCache ] }); Generally speaking all the emotion caches used in your app should be provided. Just remember to first provide the caches used by mui then the caches used by tss. Example: const { Document } = createDocument({ "caches": [ muiCache1, muiCache2, tssCache1, tssCache2 ] }); */ export default Document;
You can find a working example here.
Or, if you have have a _document.tsx
but you haven't overloaded getInitialProps
Click to expand
Or, if you have have a _document.tsx
and an overloaded getInitialProps
Click to expand
With any other framework
yarn add @emotion/server
import { renderToString } from "react-dom/server"; import createEmotionServer from "@emotion/server/create-instance"; import { getTssDefaultEmotionCache } from "tss-react/cache"; import { createMakeStyles } from "tss-react"; const emotionServers = [ getTssDefaultEmotionCache(), //If you use custom cache(s) provide it/them here instead of the default, see example below. ].map(createEmotionServer); /* With mui v5 (or if you are using custom caches): import { muiCache } from "..."; const emotionServers = [ muiCache, getTssDefaultEmotionCache() ].map(createEmotionServer); If you are providing custom caches to tss-react using <TssCacheProvider value={tssCache} > you should pass it as well. const emotionServers = [ muiCache, tssCache ].map(createEmotionServer); Generally speaking all the emotion caches used in your app should be provided. Just remember to first provide the caches used by mui then the caches used by tss. Example: const emotionServers = [ muiCache1, muiCache2, tssCache1, tssCache2 ].map(createEmotionServer); */ const element = <App />; const html = renderToString(element); res.status(200).header("Content-Type", "text/html").send(`<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>My site</title> ${emotionServers .map(({ extractCriticalToChunks, constructStyleTagsFromChunks }) => constructStyleTagsFromChunks(extractCriticalToChunks(html)), ) .join("")} </head> <body> <div id="root">${html}</div> <script src="./bundle.js"></script> </body> </html>`);
Development
yarn yarn build #For automatically recompiling when file change #npx tsc -w # To start the Single Page Application test app (create react app) # This app is live here: https://garronej.github.io/tss-react/ yarn start_spa # To start the Server Side Rendering app (next.js) yarn start_ssr # To start the Server Side Rendering app that test the mui v4 integration. yarn start_muiV4
In SSR everything should work with JavaScript disabled
Click to expand
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK