I have a store file named Authstore.js as below
import { observable, computed, action, makeObservable } from 'mobx';
export default class AuthStore {
constructor(stores) {
makeObservable(this);
this.stores = stores;
this.stop();
}
@observable isAuthed = false;
@observable authIsPending = true;
@observable user = null;
@observable loginError = null;
@observable
currentOrgNameHeader = '';
@action
stop = () => {
if (this.userRef) {
this.userRef.off('value', this.userCallback);
this.userRef = null;
this.userCallback = null;
}
this.isAuthed = false;
this.authIsPending = true;
this.user = null;
// this.loginError = null; // need to keep error after login failure
};
@computed
get loginErrorMessage() {
return this.loginError
? authErrors[this.loginError] || 'Unexpected error'
: '';
}
@computed
get isWrongPassword() {
return this.loginError === 'auth/wrong-password';
}
@computed
get isPasswordResetExpired() {
return this.loginError === 'auth/invalid-action-code';
}
@computed
get settings() {
return this.profile.settings || {};
}
initialise = () => {
if (this.isInitialised) {
return;
}
this.isInitialised = true;
checkAccountExists = email => {
if (!email) {
this.loginError = 'cw/no-email';
return Promise.reject('cw/no-email');
}
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
this.loginError = 'cw/invalid-email';
return Promise.reject('cw/invalid-email');
}
this.loginError = null;
return auth
.fetchSignInMethodsForEmail(email)
.then(res => res.includes('password'))
.catch(this.processLoginError);
};
signIn = (email, password) => {
if (!email || !password) {
this.loginError = 'cw/no-email-password';
return Promise.reject('cw/no-email-password');
}
this.loginError = null;
return auth
.signInWithEmailAndPassword(email, password)
.catch(this.processLoginError);
};
processLoginError = error => {
this.loginError = error.code || 'cw/unexpected';
return Promise.reject(error);
};
signOut = () => {
return auth.signOut();
};
}
And then I have login component in this I have multiple child components
Login.js
import React, { Component } from 'react';
import theme from '../../styles/theme';
import { observer, inject } from 'mobx-react';
@inject('authStore','uiStore')
@observer
export default class Login extends Component {
constructor(){
super();
this.state = {
redirect: null,
authForm:'email',
email: ''
}
}
componentWillMount() {
const { uiStore, authStore, location } = this.props;
const { setTitle, setLogoIndent } = uiStore;
setTitle('Login');
setLogoIndent('ready');
document.body.style.backgroundColor = 'black';
document.body.style.color = 'white';
document.body.style.backgroundImage = 'none';
authStore.signOut();
// const { mode } = queryString.parse(location.search);
// this.isPasswordReset = mode === 'resetPassword';
// const from = location.state && location.state.from;
// if (from && from.pathname !== '/') {
// this.setState({ redirect: from });
// }
}
render() {
const { email, authForm} = this.state;
const { authStore } = this.props;
console.log(authStore ,'authStore.isAuthed')
console.log("loginErrorMessage",this.props.authStore.loginErrorMessage);
if (authStore.isAuthed) {
const to = { pathname: `/o/` };
return <Navigate to={{ ...to, state: 'login' }} />;
}
return (
<Wrapper>
<ScrollToTopOnMount />
<Logo shade="light" height="150px" width="220px" />
{authForm === 'email' && (
<EmailForm
authStore={authStore}
email={email}
onSubmit={(accountExists, email) => {
const authForm = accountExists ? 'login' : 'register';
this.setState({ authForm, email });
}}
/>
)}
<Footer />
<Shape>
<Asterisk animation="rotate" color={theme.color.yellow} />
</Shape>
</Wrapper>
);
}
}
Child component EmailForm.js
import React, { Component } from 'react';
import bowser from 'bowser';
import { observer, inject } from 'mobx-react';
import { Form, Field, Icon, Input, Title, MainButton } from './styles';
import theme from '../../../styles/theme/index';
import { Error } from '../../../components/CommonComponents/Text/Text';
@inject('authStore')
@observer
export default class LoginForm extends Component {
constructor(props){
super(props);
this.state = {
isBusy: false
};
this.Email = React.createRef();
}
componentDidMount() {
this.Email.current.value = this.props.email;
if (!bowser.msie) {
this.Email.current.focus();
}
}
handleSubmit = event => {
event.preventDefault();
if (this.state.isBusy) return;
this.setState({ isBusy: true });
const email = ( this.Email.current.value || '').trim();
this.props.authStore
.checkAccountExists(email)
.then(exists => this.props.onSubmit(exists, email))
.catch(error => this.setState({ isBusy: false }));
};
render() {
const { loginErrorMessage, clearLoginError } = this.props.authStore;
console.log("loginErrorMessageES",loginErrorMessage);
return (
<Form onSubmit={this.handleSubmit}>
<Title>Let's get ready!</Title>
<Field mt="35px" mb="45px">
<Icon icon="user" />
<Input
ref={this.Email}
placeholder="Email"
autoCorrect="off"
autoCapitalize="none"
onKeyDown={() => clearLoginError()}
/>
</Field>
<MainButton type="submit">Continue</MainButton>
{loginErrorMessage && (
<Error mt="30px" color={theme.color.red} textColor={theme.color.red}>
{loginErrorMessage}
</Error>
)}
</Form>
);
}
}
When login form was submitted without email the loginErrorMessage need to be displayed using observer but the child component is not re-rendering on the observable change. Please let me know if I'm missing anything.
package.json
{
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.18.6",
"bowser": "^2.11.0",
"create-react-app": "^5.0.1",
"customize-cra": "^1.0.0",
"firebase": "7.24.0",
"mobx": "6.3.0",
"mobx-react": "6.3.0",
"mobx-react-lite": "3.0.0",
"moment": "^2.29.3",
"polished": "^4.2.2",
"query-string": "^7.1.1",
"re-base": "^4.0.0",
"react": "^18.2.0",
"react-app-rewired": "^2.2.1",
"react-datetime": "^3.1.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0",
"react-scripts": "^5.0.1",
"react-select": "^5.3.2",
"react-textarea-autosize": "^8.3.4",
"styled-components": "^5.3.5",
"styled-map": "^3.3.0",
"styled-system": "^5.1.5"
},
"babel": {
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"@babel/plugin-proposal-class-properties",
{
"loose": false
}
]
]
},
"scripts": {
"start": "react-app-rewired start",
"start:windows": "react-app-rewired start",
"build": "react-app-rewired build"
}
}
But if I remove the inject and observer from the child component class and passed the authstore as props to the child component then everything is working fine.
Please find the code sandbox link to reproduce the issue - https://codesandbox.io/s/sathish-7m3174?file=/src/routes/Login/Login.js
Steps to reproduce the issue:
- Click the login button without entering the email
- At this time the loginErrorMessage @compute will be updated in Mobx(AuthStore.js) and it will trigger the child component to re-render to show the error message but it is not happening somehow. Please check and advise on this.