1

I want to compile some kind of typescript component library to use in my main application. I work with a kind of monorepo structure and locally reference the component library from its directory. The actual setup, compiling of typescript lib and usage in my my main application do work fine.

However if the component imported from my lib uses any of @material-ui/core components in its jsx tree it will result in an error Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: [...] error. Which i cannot really understand, as those components work fine when i simply copy the code over to main application without compilation in between.

So my best guess it has something to do with commonjs, es modules etc. But i am not really familiar with this topic and i do not know of this is really to blame or how to find a solution. @material-ui/core however should be considered a peerDependency and not compiled in the component library. This the compiled code will need to properly import it from the main apps dependencies. I am compiling my library as commonjs module.

Basic setup is

app/ #create-react-app here
    package.json # with following contents
        ...
        "dependencies": {
            "@material-ui/core": "4.11.4",
            "components": "file:../component-lib",
        ...
component-lib/
    dist/
    src/
        index.tsx
    package.json
    tsconfig.json

A component that would trigger the error if put in index.tsx is (Note that replacing <Typography> with i.e. <span> would result in everything working just fine.

import {
    Typography,
    TypographyProps,
} from '@material-ui/core';

import React from 'react';

/**
 * Properties for InfoLine component
 */
export type InfoLineProps = {
    infos: Array<[React.ReactElement | string, React.ReactElement | string]>,
    infoTypographyProps?: TypographyProps,
    labelTypographyProps?: TypographyProps,
};

const InfoLine = ({
    infos,
    infoTypographyProps = { variant: 'body1' },
    labelTypographyProps = { variant: 'body1', color: 'textSecondary' },
    ...stackProps
}: InfoLineProps) => {
    return (
        <div>
            {infos.map(([icon, info]) => (
                <>
                    {
                        typeof icon === 'string'
                            ? (<Typography {...labelTypographyProps}>{icon}:&nbsp;</Typography>)
                            : icon
                    }
                    {
                        typeof info === 'string'
                            ? (<Typography {...infoTypographyProps}>{info}</Typography>)
                            : info
                    }
                </>
            ))}
        </div>
    );
};

export default InfoLine;

The tsconfig.json that is used when building via tsc:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2015",
    "allowJs": true,
    "declaration": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "jsx": "react-jsx"
  },
  "include": [
    "src/**/*"
  ]
}

1 Answer 1

3

I found the cause of this problem. It is about the way npm handles local dependencies and how Webpack (i think) handles module resolution.

So long story short my "peerDependency" was present in my component_lib/node_modules because i originally installed it as a "devDependency". Because npm is symlinking the local module and not processing dependencies the same way as other dependencies, this was causing the @material-ui/core component to be loaded from component_lib/node_modules/@material-ui/core rather than app/node_modules/@material-ui/core, as a peer dependency would normally be. Although it is the identical code that is executing this was causing the problem somehow and removing @material-ui from the component_lib/node_modules resolved the issues.

To me it is not an ideal solution as i would love to have a copy of @material-ui/core in the component_lib/node_modules for local development, tests, examples and so on. But a resolution of this would need fiddling with npm or webpacks module resolution. The first one feels hacky and the latter would most likely require to eject my create-react-app. Another solution would be to use another subdirectory for development with its own package.json, would probably work it seems not ideal to me.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.