React with TypeScript using tsyringe for dependency injection

3.5k views Asked by At

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{}

4

There are 4 answers

2
Jordan Schnur On BEST ANSWER

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.

0
Florian Kopp On

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,
    });
    
0
Nick Olszanski On

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>
}
0
user15316261 On

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

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