Home > database >  MobX changing but not rerendering components
MobX changing but not rerendering components

Time:06-10

I would like to open and close an antd Modal component using a MobX store. I have the following code Here's a link to codesandbox https://codesandbox.io/s/nifty-dijkstra-g0kzs6?file=/src/App.js

import AppNavigation from "./components/Menu";
import ContactPopUp from "./components/Contact";
export default function App() {
  return (
    <div className="App">
      <AppNavigation />
      <ContactPopUp />
    </div>
  );
}

File with the MobXstore

import { createContext, useContext } from "react";
import AppStore from "./appStore";

interface Store {
  appStore: AppStore;
}

export const store: Store = {
  appStore: new AppStore()
};

export const StoreContext = createContext(store);

export function useStore() {
  return useContext(StoreContext);
}

Separate file where I declare the store

import { makeAutoObservable } from "mobx";

export default class AppStore {
  contactFormOpen = false;

  constructor() {
    makeAutoObservable(this);
  }

  setContactFormOpen = (isOpen: boolean) => {
    console.log("Changed contact form to ", isOpen);
    this.contactFormOpen = isOpen;
  };
}

The Menu.tsx

import React from "react";
import { Menu, MenuProps } from "antd";
import { useStore } from "../store/store";

const AppNavigation = () => {
  const { appStore } = useStore();
  const menuItems: MenuProps["items"] = [
    {
      label: <a onClick={(e) => handleOpenContactForm(e)}>Contact</a>,
      key: "Contact"
    }
  ];

  const handleOpenContactForm = (e: any) => {
    e.preventDefault();
    e.stopPropagation();
    appStore.setContactFormOpen(true);
    console.log("Open contact pop up", appStore.contactFormOpen);
  };

  return (
    <Menu
      items={menuItems}
      theme="dark"
      overflowedIndicator={""}
      className="header__menu award-menu header__menu--md"
    />
  );
};

export default AppNavigation;

ContactPopUp.tsx

import { Modal } from "antd";
import React, { useEffect, useState } from "react";
import { useStore } from "../store/store";

const ContactPopUp = () => {
  const { appStore } = useStore();
  const [visible, setVisible] = useState(appStore.contactFormOpen);

  useEffect(() => {
    setVisible(appStore.contactFormOpen);
  }, [appStore.contactFormOpen]);

  const handleCancel = () => {
    appStore.setContactFormOpen(false);
    console.log("Close contact from", appStore.contactFormOpen);
  };

  return (
    <Modal title="Contact us" visible={visible} onCancel={handleCancel}>
      <h2>Modal Open</h2>
    </Modal>
  );
}; 

export default ContactPopUp;

The mobx contactFormOpen clearly changes but the modal state does not. I really don't understand why... UseEffect also doesn't trigger a re render.

CodePudding user response:

You just forgot most crucial part - every component that uses any observable value needs to be wrapped with observer decorator! Like that:

const ContactPopUp = () => {
  const { appStore } = useStore();

  const handleCancel = () => {
    appStore.setContactFormOpen(false);
    console.log('Close contact from', appStore.contactFormOpen);
  };

  return (
    <Modal
      title="Contact us"
      visible={appStore.contactFormOpen}
      onCancel={handleCancel}
    >
      <h2>Modal Open</h2>
    </Modal>
  );
};

// Here I've added `observer` decorator/HOC
export default observer(ContactPopUp);

And you don't need useEffect or anything like that now.

Codesandbox

  • Related