I need some really basic dependency injection for a react project in a similar syntactical manner as in C# or Java. Using context or injection through props is not an option. I tried three solutions so far:
Inversify
Inversify-React-Decorators
React.DI
However, none of theses solutions worked, which raised the questions if there is some sort of configuration issue.
Using Inversify / Inversify-React-Decorators / Rect.DI
SomeService.ts
import { inject, injectable } from "inversify";
import { TYPES, ITokenProvider, IFileService } from "../injectables";
import { lazyInject, DIContainer } from "../inversify.config";
export class SomeService {
#inject(TYPES.ITokenProvider) private tokenProvider!: ITokenProvider; //Inversify
//#lazyInject(TYPES.ITokenProvider) private tokenProvider!: ITokenProvider;//Inversify-r-d
//#Inject tokenProvider!: TokenProvider; //react-di
(...)
}
inversify.config.ts
import "reflect-metadata";
import { Container } from "inversify";
import getDecorators from "inversify-inject-decorators";
import { TYPES, ITokenProvider, } from "./injectables";
import { TokenProvider } from "./Services/TokenProvider"
const DIContainer = new Container();
DIContainer.bind<ITokenProvider>(TYPES.ITokenProvider).toConstructor(TokenProvider);
const { lazyInject } = getDecorators(DIContainer, false);
export { DIContainer, lazyInject }
injectables.ts
export interface ITokenProvider {
getSomeToken(): Promise<string>
}
TokenProvider.ts
import "reflect-metadata";
import * as microsoftTeams from "#microsoft/teams-js";
import { injectable } from "inversify";
import { ITokenProvider } from '../injectables';
#injectable()
export class TokenProvider implements ITokenProvider {
public constructor() { }
public async getSomeToken(): Promise<string> {
(...)
}
}
App.tsx (used by react-di instead of inversify.config)
#Module({
providers: [
{ provide: AccessTokenProvider, useClass: AccessTokenProvider },
]
})
Errors
React-DI and Inversify won'r resolve the dependency, causing an undefined error for the property.
Inversify decorators causes following error:
TokenProvider.ts:8 Uncaught ReferenceError: Cannot access 'SomeService' before initialization
at Module.SomeService (VM8 main.chunk.js:248)
at Module../src/inversify.config.ts (inversify.config.ts:11)
(...)
Config
tsconfig.json
{
"include": [
"src/*"
],
"compilerOptions": {
"target": "es5",
"jsx": "react",
"allowSyntheticDefaultImports": true,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"types": [ "reflect-metadata" ]
}
}
I also tried moving the "reflect-metadata" around different classes.
Nested #lazyInject not working :https://github.com/inversify/InversifyJS/issues/941
Related
Getting below error
cannot find module #assets/logo-filled.png from src/component/index.tsx
if I replace import logo from #assets/logo-filled.png with import logo from ../../../assets/logo-filled.png
This line of import is working fine while running the application. But while running the test scripts npm test -> react-scripts test the above error is popping up
test.test.tsx
import React from "react";
import { render } from "#testing-library/react";
import {Logo} from "../components/logo";
describe('Describe...', () => {
test('test ...', () => {
console.log('the test');
render(<Logo withoutText={true}/>);
expect(1).toBe(1)
});
});
Logo.tsx
import React from 'react';
import { Stack, Image, Text } from '#fluentui/react';
import { logoContainerStyles, textStyles } from './styles';
// ERROR thrown by below line
import logo from '#assets/icon-16.png';
// Works fine with below commented line
/** import logo from '../../../assets/icon-16.png'; */
export interface LogoProps {
withoutText?: boolean;
}
export const Logo: React.FC<LogoProps> = props => {
const { withoutText } = props;
return (
<Stack styles={logoContainerStyles} horizontalAlign="center">
<Image src={logo} alt="logo" width={64} shouldFadeIn={false} />
<Text styles={textStyles} variant="xLarge">
Hello StackOverflow
</Text>
</Stack>
);
};
ts.config.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"moduleResolution": "node",
"allowJs": true,
"jsx": "react",
"lib": ["esnext", "dom", "dom.iterable"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"outDir": "dist",
"noImplicitReturns": true,
"pretty": true,
"typeRoots": ["node_modules/#types"],
"baseUrl": "src",
"paths": {
"#assets/*": ["../assets/*"]
}
},
"include": ["src"]
}
Any kind of help will much be appreciated.
We need a little bit more details on your setup. But it has to do with tsconfig.json and it's paths property. Also take a closer look at jest property in package.json, it is used to define aliases for tests.
In the jest.config.js use ,
moduleNameMapper: {
// Handle other modules
// Handle module aliases
"^#/assets/(.*)$": "<rootDir>/assets/$1",
},
If you want use module aliases with TypeScript
"paths": {
"#/assets/*": ["assets/*"],
}
I'm integrating vitest with a NextJS13 app, but running into problems with a simple test run.
Not sure what the problem is, I tried to do some tweaking with the vitest.config.ts but no luck. I tried adding the dir option, modified the include option to grab files from the source file but no luck.
I thought maybe it had to do with the tsconfig.json file, but it's still outputting the error.
This is the directory of the file
Here are the files in question:
vitest.config.ts
/// <reference types="vitest" />
import { defineConfig } from 'vitest/config'
import react from '#vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
setupFiles: 'setupTests.ts',
// dir: './src'
// includeSource: ['src/**/*.{js,ts,tsx}'],
},
});
tsconfig.json
{
"compilerOptions": {
"target": "ES2017",
"lib": ["es6", "dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "ESNEXT",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
"incremental": true,
// "paths": {
// "src": ["./src/*"]
// }
},
"exclude": ["node_modules"],
"include": ["vitest.config.ts","**/*.ts", "**/*.tsx", "next-env.d.ts",
"next.config.js"]
}
DataTable.test.tsx - src/common/components/DataTable/DataTable.test.tsx
// components
import DataTable from 'src/common/components/DataTable';
// dependencies
import {describe, it} from 'vitest'
import {screen, render} from '#testing-library/react'
describe('DataTable test', () => {
it('render the app', () => {
// arrange
render(<DataTable />)
// act
const assetText = screen.getByText("asset")
// assert
// expect(assetText).toBeInTheDocument()
})
})
DataTable component - src/common/components/DataTable/DataTable.tsx
export const DataTable = () => {
return (
<div>
<h1>assets</h1>
</div>
);
};
Index.tsx - src/common/components/DataTable/index.tsx
import { DataTable } from 'src/common/components/DataTable/DataTable';
export default DataTable;
I'm new to vitest and nextjs, your help/guidance will be appreciated.
There are two things needed here to make the import DataTable from 'src/common/components/DataTable'; import work:
TypeScript needs the paths compilerOption set.
Vite needs to have the same alias set.
The "paths" compilerOption in TypeScript will need a /* on the end of the "src" key to be able to resolve paths underneath the "src" directory (see tsconfig.json reference):
{
"compilerOptions": {
"paths": {
"src/*": ["./src/*"]
}
}
}
Vite/Vitest will also need to know how to resolve "src/common/components/DataTable", and that would usually be done with the resolve.alias setting, but rather than duplicating the alias here, you could also use a plugin, like vite-tsconfig-paths, to add the path aliases if finds in relevant tsconfig.json files:
import react from "#vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
export default {
plugins: [tsconfigPaths(), react()],
};
I'm trying to configure storybook to display a dark theme, and so far i didn't find any solution to this problem.
so i followed the storybook docs,
and i'v setup the manager.js file like so:
// .storybook/manager.js
import { addons } from '#storybook/addons';
import { themes } from '#storybook/theming';
addons.setConfig({
theme: themes.dark,
});
i'v also printed the theme to the console so i see it arrives:
it may be worth mentioning that when the browser reload this file is read,
but if i change the source code and save the hot-reload don't work..
Here's how i specified the same theme for docs in .storybook/preview.js:
// .storybook/preview.js
import React from "react";
import { appTheme } from "../src/Common/theme";
import { ThemeProvider } from "styled-components";
import { makeDecorator } from "#storybook/addons";
import { addParameters, addDecorator } from "#storybook/react";
import defaultNotes from "./general-docs.md";
import { themes } from "#storybook/theming";
export const parameters = {
docs: {
theme: themes.dark
}
};
addParameters({
notes: defaultNotes,
options: {
showRoots: true
}
});
const withStyledTheme = storyFn => {
return <ThemeProvider theme={appTheme}>{storyFn()}</ThemeProvider>;
};
const styledThemed = makeDecorator({
name: "styled-theme",
wrapper: withStyledTheme
});
addDecorator(styledThemed);
addParameters(parameters);
this is how the main.js file looks like:
module.exports = {
stories: ["../src/**/*.stories.(ts|tsx|js|jsx|mdx)"],
addons: [
"#storybook/preset-create-react-app",
"#storybook/addon-actions",
"#storybook/addon-links",
"#storybook/addon-actions/register",
"#storybook/addon-knobs/register",
"#storybook/addon-notes/register-panel",
"storybook-addon-designs",
"#storybook/addon-docs/preset"
]
};
i work with typescript in this project,
so here is the tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"baseUrl": "src"
},
"include": ["src"]
}
What am i missing here ?
I've been stuck on the same problem for a while, but the solution was in the doc all along:
//.storybook/preview.js
import { themes } from '#storybook/theming';
export const parameters = {
docs: {
theme: themes.dark,
},
};
So yeah, you have to specify the theme twice, in two different files
I am trying to implement a MobX - React App. But having trouble updating/ re-rendering the value. Store seems to load correctly and it is setting the initial value in the label. But any further value changes are not getting reflected.
OrganisationNameStore store :
import {action, observable} from 'mobx';
import OrganisationName from '../modules/OrganisationName';
export class OrganisationNameStore{
#observable public orgName: OrganisationName = new OrganisationName();
#action.bound
public clear(): void{
this.orgName = new OrganisationName();
}
#action.bound
public handleTextChange(event: React.FormEvent<HTMLInputElement>) {
this.orgName.name = event.currentTarget.value;
}
}
// Interface is required for TypeScript to be Type safe
export interface IOrganisationNameStore {
orgName: OrganisationName;
clear(): void;
handleTextChange(event: React.FormEvent<HTMLInputElement>): void;
getOrganisationName(): string;
}
Parent Store file :
import { OrganisationNameStore } from './OrganisationNameStore';
// Have all the stores here
export const stores = {
organisationNameStore: new OrganisationNameStore(),
};
OrganisationName class :
export default class OrganisationName {
public name!: string;
constructor () {
this.clear = this.clear.bind(this);
this.clear();
}
public clear(): OrganisationName {
this.name = 'Mobx Text 1';
return this;
}
}
Index :
import React from 'react';
import './index.css';
import * as serviceWorker from './serviceWorker';
import ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
import { stores } from './stores/store';
import App from './App';
ReactDOM.render(
<Provider {...stores}>
<App />
</Provider>
, document.getElementById('root')
);
serviceWorker.unregister();
App.tsx File :
import React from 'react';
import './App.css';
import { observer, inject } from 'mobx-react';
import {IOrganisationNameStore} from './stores/OrganisationNameStore'
interface IAppProps /*extends WithStyles<typeof styles> */{
organisationNameStore?: IOrganisationNameStore // MobX Store
}
#inject('organisationNameStore')
#observer
class App extends React.Component<IAppProps> {
constructor(props: IAppProps) {
super(props);
}
render() {
return (
<div className="App">
<input
type="text"
// value={this.props.organisationNameStore!.orgName.name}
name="name"
onChange={this.props.organisationNameStore!.handleTextChange}
/>
<div>
<label>{this.props.organisationNameStore!.orgName.name}</label>
</div>
</div>
);
}
}
export default App;
tsconfig.tsx :
{
"compilerOptions": {
"experimentalDecorators": true,
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"lib": [
"es6",
"dom",
"esnext.asynciterable"
],
"sourceMap": true,
"allowJs": true,
"jsx": "preserve",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"importHelpers": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"esModuleInterop": true,
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"exclude": [
"node_modules",
"build",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts"
],
"include": [
"src"
]
}
.babelrc :
{
"presets": ["#babel/preset-react"],
"plugins": [
"transform-runtime","transform-decorators-legacy"
// ["#babel/plugin-proposal-decorators", { "legacy": true }],
// ["#babel/plugin-proposal-class-properties", { "loose": true }]
]
}
There is no console errors.
Expected behavior - I wanted the label value to be updated when ever i type in the input in the App.tsx file.
Please correct me if i am wrong in implementing this?
as mweststrate said here:
https://stackoverflow.com/a/39959958/829154
MobX only converts plain objects automatically to observable objects when assigning them to e.g. an array, because for class instances it might interfere with the internals of that class otherwise.
See:
https://mobxjs.github.io/mobx/refguide/object.html, second bullet
so you should mark name as #observable in your OrganisationNameStore class.
So I have the Import sources within a group must be alphabetized. error in the following file:
login.view.tsx
import AbstractComponent from "../abstract-component";
import { LoginController } from "./login.view.controller";
import { UserService } from "../../services/user.service";
import { IBaseProps } from "../../App";
export interface ILoginProps extends IBaseProps {
userService?: UserService;
}
class Login extends AbstractComponent<ILoginProps, object> {
private loginController: LoginController;
constructor(public props: Readonly<ILoginProps>) {
super(props);
this.loginController = new LoginController(this, props);
}
}
My tsconfig.json is the following:
tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src"
},
"rules": {
"ordered-imports": [
false
]
},
"exclude": [
"node_modules",
"build",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts"
]
}
I tried to change the order of all the imports with results. Can anyone help me out?
This is not a typescript's error, but tslint's one. It wants you to rearrange the import statements in alphabetical order.
I think, like this (but I am not sure, so try different orders yourself):
import AbstractComponent from "../abstract-component";
import { IBaseProps } from "../../App";
import { LoginController } from "./login.view.controller";
import { UserService } from "../../services/user.service";
If nothing works, try tslint's auto fix command:
tslint --fix
If you use VSCode, then put the cursor on the red-underlined statements and press ctrl + ., it will suggest auto fix.