MobX React Not re-rendering - reactjs

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.

Related

RTL Error cannot find module #assets/logo-filled.png from src/component/index.tsx

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/*"],
}

Can't get property dependency injection to work in React project

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

TypeScript & React: compose higher-order components

I'm new to TypeScript with React and I have (already) two HOCs in React and I would like to compose them because it is likely that there will be more.
import { getIsAuthenticated } from 'features/user-authentication/user-authentication-reducer';
import { connect, ConnectedProps } from 'react-redux';
import { RootState } from 'redux/root-reducer';
import { withTranslation } from '../../../i18n';
import HomePageComponent from './home-page-component';
const mapStateToProps = (state: RootState) => ({
isAuthenticated: getIsAuthenticated(state),
});
const connector = connect(mapStateToProps);
export type PropsFromRedux = ConnectedProps<typeof connector>;
export default withTranslation()(connector(HomePageComponent));
That is how it currently looks (and works / compiles). The component is:
// ...
import { TFunction } from 'next-i18next';
// ...
import { PropsFromRedux } from './home-page-container';
type Props = {
readonly t: TFunction;
} & PropsFromRedux;
const HomePageComponent = ({ isAuthenticated, t }: Props) => (
// ...
In JavaScript I usually do:
export default compose(withTranslation(), connector)(HomePageComponent);
where compose is either from Ramda or from Redux itself.
Using Ramda's compose throws two errors.
connector: No overload matches this call.
HomePageComponent: Expected 0 arguments, but got 1.
Using Redux' compose compiles here, but breaks my tests.
import { render, screen } from 'tests/test-helpers';
import HomePageContainer from './home-page-container';
describe('Home Page Container', () => {
it('should render a greeting', () => {
render(<HomePageContainer />);
expect(screen.getByText(/hello world/i)).toBeInTheDocument();
});
});
In the test file, HomePageContainer throws:
JSX element type 'HomePageContainer' does not have any construct or call signatures.
How can I use compose for higher-order components and get it to work with TypeScript?
PS: I found this other question and answer in SO, but the answer doesn't work in the latest TS versions and its only for two HOCs, I need to compose more.
My tsconfig.json:
{
"compilerOptions": {
"allowJs": true,
"baseUrl": "./src",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "preserve",
"lib": ["dom", "dom.iterable", "esnext"],
"module": "esnext",
"moduleResolution": "node",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "es5"
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}
Minimal example in CodeSandbox: https://codesandbox.io/s/hoc-breaking-example-mtp3m
If you go to rambda compose typing you'll see that it's generic, so you can define what would be the resulting function, typing it like this removes the error
export default compose<React.FunctionComponent>(
withTitle("Hello StackOverflow!"),
connector
)(App);

configure storybook docs addon to display theme.dark

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

Import sources within a group must be alphabetized. Typescript

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.

Resources