5

I am currently having trouble with my React TypeScript project.

I created my project with npx create-react-app my-app --template typescript.

I recently added tsyringe for dependency injection and was trying to implement it for an apiService. After following the readme(https://github.com/microsoft/tsyringe#injecting-primitive-values-named-injection) for adding primitive values I have hit a block. I already add experimentalDecorators and emitDecoratorMetadata to my tsconfig.json file with no success.

The error actual error I am encountering is:

./src/ts/utils/NetworkService.ts 9:14
Module parse failed: Unexpected character '@' (9:14)
File was processed with these loaders:
 * ./node_modules/@pmmmwh/react-refresh-webpack-plugin/loader/index.js
 * ./node_modules/babel-loader/lib/index.js
You may need an additional loader to handle the result of these loaders.
| 
| let NetworkService = (_dec = singleton(), _dec(_class = (_temp = class NetworkService {
>   constructor(@inject('SpecialString')
|   value) {
|     this.str = void 0;

I am fairly sure this problem is caused by Babel, however I created this with npm create react-app --template typescript and do not seem to have access to the Babel configuration.

NetworkService.ts

@singleton()
export default class NetworkService
{

    private str: string;
    constructor(@inject('SpecialString') value: string) {
        this.str = value;
    }
}

Invocation method

bob() 
{
    const inst = container.resolve(NetworkService);
}

Registering Class in index.ts

container.register('SpecialString', {useValue: 'https://myme.test'});


@registry([
    { token: NetworkService, useClass: NetworkService },
])
class RegisterService{}

0

4 Answers 4

4

React-Scripts manages many of the configs related to the project. For many cases, this is fine and actually a nice feature. However, because React-Scripts uses Babel for it's development environment and does not expose the config.

You have to run npm run eject to expose the configurations.

Please note, this is a one-way operation and can not be undone. Personally, I prefer more control with my configuration.

After this you can edit the webpack.config.js in the newly created config folder.

Find the section related to the babel-loader in the dev-environment and add 'babel-plugin-transform-typescript-metadata' to the plugins array.

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

2 Comments

Thanks for this! I wish this was included in the auto configuration.
I added the plugin: plugins: [ isEnvDevelopment && shouldUseReactRefresh && require.resolve('react-refresh/babel'), require.resolve('babel-plugin-transform-typescript-metadata'), ] but I still get the error: Class constructor cannot be invoked without 'new'
3

Expanding on Jordan Schnur's reply, here are some more pitfalls I encountered when adding TSyringe to my CRA app:

Use import type with @inject

If you get this error "TS1272: A type referenced in a decorated signature must be imported with 'import type' or a namespace import when 'isolatedModules' and 'emitDecoratorMetadata' are enabled." replace import with import type for the offending imports. You will encounter this when working with @inject E.g. replace import { IConfig } from "iconfig" with import type { IConfig } from "iconfig"

Fixing Jest

Your Jest tests will also break with TSyringe, especially when using @inject. I got the error "Jest encountered an unexpected token" with details constructor(@((0, _tsyringe.inject)("")) ("@" marked as the offending token). I took the following steps to fix that in CRA:

Add the line import "reflect-metadata"; to the top of the file src/setupTests.ts

In config/jest/babelTransform.js replace line 18 and following:

From

    
    module.exports = babelJest.createTransformer({
      presets: [
        [
          require.resolve('babel-preset-react-app'),
          {
            runtime: hasJsxRuntime ? 'automatic' : 'classic',
          },
        ],
      ],
      babelrc: false,
      configFile: false,
    });
    

to:

    
    module.exports = babelJest.createTransformer({
      presets: [
        [
          require.resolve('babel-preset-react-app'),
          {
            runtime: hasJsxRuntime ? 'automatic' : 'classic',
          },
        ],
      ],
      plugins: [
        require.resolve('babel-plugin-transform-typescript-metadata')
      ],
      babelrc: false,
      configFile: false,
    });
    

Comments

1

I've created an simpler DI library that doesn't need decorators or polyfill. Works with CRA like a charm and has cool React bindings

iti

import { useContainer } from "./_containers/main-app"

function Profile() {
  const [auth, authErr] = useContainer().auth

  if (authErr) return <div>failed to load</div>
  if (!auth) return <div>loading...</div>

  return <div>hello {auth.profile.name}!</div>
}

Comments

0

Instead of eject, you may use a lib that "overrides" some of your params.

I used craco : https://www.npmjs.com/package/@craco/craco

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.