2
votes

I've been using Material UI components to create my own custom reusable components package. Here is an example of custom component located in package, just for demo purpose:

import PropTypes from 'prop-types';
import {makeStyles, ThemeProvider} from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';

const useStyles = makeStyles({
    'buttonPackage': {
        backgroundColor: theme.palette.primary.main,
        color: 'rgba(12, 0, 0, 1)',
        minWidth: '147px',
        padding: '8px 8px 9px 8px',
    },
});

const CustomButton = ({children, theme}) => {
    const classes = useStyles();

    return (
        <ThemeProvider theme={theme}>
            <Button className={classes.buttonPackage}>
                {children}
            </Button>
        </ThemeProvider>
    );
};

export default CustomButton;

If it is not wrapped in ThemeProvider, global theme variables are not applied to custom component, although whole app is wrapped in ThemeProvider.

The problem occurs when I try to do further customization in React app part, for components that have very specific styling and cannot be considered as reusable. Here is an example of React view component, containing both custom component from package and component that is customized inside view component, again, this is just a demo code:

import React from 'react';
import {makeStyles} from '@material-ui/core/styles';
import {Button} from '@material-ui/core';
import {CustomButton, theme} from '@name-of-my-package/ui-components';

const useStyles = makeStyles(theme => ({
    buttonApp: {
        height: '100%',
    },
}));

const GenericView = () => {
    const classes = useStyles();

    return (
        <React.Fragment>
            <CustomButton theme={theme}>Button imported from package</CustomButton>
            <Button className={classes.buttonApp}/>Button customized in app</Button>
        </React.Fragment>
    );
};

export default GenericView;

When used in development, everything works fine. When used in production, some styling properties are shared between components customized in package and react app. Valid classes are bundled, but are not applied on first load of the page. When you click around the application (example, change route from navigation component), valid styles are applied and everything looks OK. I noticed that there is style overlapping between component customized in reusable component package, and component that is customized in React app.

Is this approach even possible, having custom component package and do further customization in app part, including Themes?

UPDATE 16.06.2020. : for now, solution is to completely not use JSS styling in app part of the project. JSS styling classes made in app part of the project overlap with names of JSS styling classes made in reusable components package and that causes the issue.

1

1 Answers

1
votes

Solution for this problem included several steps:

  1. Changed config of Webpack of React part of the app to include Material UI aliases. This way, both Material UI components used in React app and components from reusable components package use the same instance of React context, used by Material UI for theme purposes. Here is an example of webpack alias property that needs to be added:
alias: {
    'react': require.resolve('react'),
    'react-dom': require.resolve('react-dom'),
    '@material-ui/core': path.resolve('./node_modules/@material-ui/core'),
    '@material-ui/core/styles': path.resolve('./node_modules/@material-ui/core/styles'),
    '@material-ui/lab': path.resolve('./node_modules/@material-ui/lab'),
    '@material-ui/icons': path.resolve('./node_modules/@material-ui/icons'),
    '@material-ui/pickers': path.resolve('./node_modules/@material-ui/pickers'),
},
  1. Changed the way of importing components when using them. If you import components, use paths defined in webpack aliases. For instance, don't import Button component like:
import Button from '@material-ui/core/Button';

import it like this:

import {Button} from '@material-ui/core';
  1. In reusable components package Rollup configuration, Material UI packages have been set as external dependencies. This way, reusable components should use material UI which is installed in React part of the app:
external: [
    'react',
    'react-dom',
    '@material-ui/core',
    '@material-ui/core/styles',
    '@material-ui/lab',
    '@material-ui/icons',
    '@material-ui/pickers',
],
  1. One more thing is added, in reusable components package, a new component is created, MyStylesProvider. This wrapper adds unique prefix to styling classes generated for reusable components. This way, in production, there will be no overlap of styles:
import React from 'react';
import {
    StylesProvider,
    createGenerateClassName,
} from '@material-ui/core/styles';

const generateClassName = createGenerateClassName({
    productionPrefix: 'so', // change this string to whatever you like
    seed: 'StackOverflow', // change this string to whatever you like
});

const MyStylesProvider = props => {
    return (
        <StylesProvider
            injectFirst={true}
            generateClassName={generateClassName}
        >
            {props.children}
        </StylesProvider>
    );
};

export default MyStylesProvider;

then I use it in React part of the app like this:

import React from 'react';
import {theme, MyStylesProvider} from '@package/reusable-components';
import {renderRoutes} from 'react-router-config';
import {ThemeProvider} from '@material-ui/core/styles';
import {CssBaseline} from '@material-ui/core';

const AppView = ({route}) => (
    <ThemeProvider theme={theme}>
        <CssBaseline />
        <MyStylesProvider>
            {renderRoutes(route.routes)}
        </MyStylesProvider>
    </ThemeProvider>
);

export default AppView;

This may not be the most optimal way to do it, but it works. I would love to hear other people's opinions and experiences with exporting reusable Material UI components.