1

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:

  1. Click the login button without entering the email
  2. 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.
Sathish
  • 87
  • 1
  • 1
  • 6

0 Answers0