尝试使用 Realm 连接到 MongoDB

Trying to connect to MongoDB using Realm

第一次使用Realm,MongoDB。 我以此 good tutorial 作为起点并创建了这个项目。

https://codesandbox.io/s/realm-forked-mrjex?file=/src/state/DbModel.ts

文件夹结构为:

src
|_ components
   |_ pages
      |_ Authentication.tsx
      |_ Home.tsx
      |_ Logout.tsx
   |_ App.tsx
   |_ Navigation.tsx
   |_ RestaurantCard.tsx
|_ lib
   |_ db-utils.ts
|_ state
   |_ index.ts
   |_ DbModel.ts

都是很简单的组件,我post这里只介绍其中的一部分。

App.tsx:

const serviceName = "mongodb-atlas";

export function App() {
  return (
    <Provider value={stateInstance}>
      <AppWithState />
    </Provider>
  );
}

function AppWithState() {
  const {
    db: { app, client, setClient, user, setUser }
  } = useMst();

  useEffect(() => {
    async function init() {
      if (!user) {
        const credentials = Realm.Credentials.anonymous();
        const newUser = app.currentUser
          ? app.currentUser
          : await app.logIn(credentials);
        setUser(newUser);
      }
      if (!client) {
        const newClient = app.currentUser.mongoClient(serviceName);
        setClient(newClient);
      }
    }
    init();
  }, [app, client, user]);

  return (
    <Router>
      <Navigation />
      <Switch>
        <Route path="/" component={Home} />
        ...
      </Switch>
    </Router>
  );
}

state/index.ts:

export const StateModel = t
  .model("StateModel", {
    db: t.optional(DbModel, {} as DbModelInstance)
  })
  .views((self) => ({}))
  .actions((self) => ({}));

export const stateInstance = StateModel.create();
export interface StateInstance extends Instance<typeof StateModel> {}

const RootStateContext = createContext<StateInstance | null>(null);
export const Provider = RootStateContext.Provider;

export function useMst() {
  const state = useContext(RootStateContext);
  if (state === null)
    throw new Error("State cannot be null, please add a context provider");
  return state;
}

state/DbModel.ts:

const appId = process.env.REACT_APP_REALM_APP_ID;
const appConfig: Realm.AppConfiguration = {
  id: appId
};
const app: Realm.App = new Realm.App(appConfig);

export const DbModel = t
  .model("DbModel", {
    app: t.optional(t.frozen<Realm.App>(), app),
    user: t.optional(t.frozen<Realm.User>(), null),
    client: t.optional(t.frozen<any>(), null)
  })
  .views((self) => ({
    get root() {
      return getRoot(self) as any;
    }
  }))
  .views((self) => ({}))
  .actions((self) => ({
    setApp(app: Realm.App) {
      self.app = app;
    },

    setUser(user: Realm.User) {
      self.user = user;
    },

    setClient(client: any) {
      self.client = client;
    }
  }))
  .actions((self) => ({}));

export interface DbModelInstance extends Instance<typeof DbModel> {}

Home.tsx:

export function Home() {
  const {
    db: { user, client }
  } = useMst();

  const [restaurants, setRestaurants] = useState([]);
  const isLoading = restaurants.length === 0;

  useEffect(() => {
    async function getData() {
      if (!client || !user) return;
      const rests = client.db("sample_restaurants").collection("restaurants");
      setRestaurants(await rests.find());
    }

    getData();
  }, [isLoading, client, user]);

  if (isLoading) {
    return <div>HOME Loading...</div>;
  }

  return (
    <div>
      {restaurants.map((restaurant) => (
        <RestaurantCard key={restaurant._id} restaurant={restaurant} />
      ))}
    </div>
  );
}

Authentication.tsx:

const userSchema = yup.object().shape({
  email: yup.string().email().required(),
  password: yup.string().required().min(8)
});

export function Authentication({ type = "login" }) {
  const {
    db: { app, user, setUser, client }
  } = useMst();

  const [isLoading, setIsLoading] = useState(false);
  const history = useHistory();

  useEffect(() => {
    if (!isAnon(user)) {
      history.push("/");
    }
  }, [history, user]);

  async function submitHandler(values: any) {
    setIsLoading(true);
    if (type === "create") {
      // @ts-ignore
      await app.emailPasswordAuth.registerUser(values.email, values.password);
    }
    // login user and redirect to home
    const credentials = Realm.Credentials.emailPassword(
      values.email,
      values.password
    );
    // @ts-ignore
    setUser(await app.logIn(credentials));
    setIsLoading(false);
  }

  return (
    <Formik
      initialValues={{
        email: "",
        password: ""
      }}
      validationSchema={userSchema}
      onSubmit={submitHandler}
    >
      {({ errors, touched, handleSubmit, values, handleChange }) => (
        <Form noValidate onSubmit={handleSubmit}>
          {isLoading && <div className="">AUTH Loading...</div>}

          <div>
            <h1>{type === "login" ? "Login" : "Sign Up"}</h1>
            <Form.Row>
              <Form.Label>Email</Form.Label>
              <Form.Control
                type="email"
                name="email"
                value={values.email}
                onChange={handleChange}
                isValid={touched.email && !errors.email}
              />
              <Form.Control.Feedback>{errors.email}</Form.Control.Feedback>
            </Form.Row>
            <Form.Row>
              <Form.Label>Password</Form.Label>
              <Form.Control
                type="password"
                name="password"
                value={values.password}
                onChange={handleChange}
                isValid={touched.password && !errors.password}
              />
              <Form.Control.Feedback>{errors.password}</Form.Control.Feedback>
            </Form.Row>

            <div className="text-center mt-2">
              <Button variant="primary" type="submit">
                Submit
              </Button>
            </div>
          </div>
        </Form>
      )}
    </Formik>
  );
}

function isAnon(user: Realm.User) {
  return !user || user.identities[0].providerType === "anon-user";
}

基本上我使用了餐馆的样本数据库。 在教程中,作者使用 React 上下文来保存 Db 信息,如应用程序、用户和客户端,但我更喜欢设置 Mobx 状态树。我认为这是唯一的区别。 哦,我使用 TypeScript(顺便说一句,client 的类型是什么?我没看懂指南,好像是 MongoDB 但我需要从哪里导入它?)。

我的代码不起作用。 我什么也没得到,仍在加载:

我认为我的应用卡在 Home 组件中,在 getData() 函数中,因为 clientuser 都是 null,但在 App 我创建了它们并保存在我的状态中,所以我不明白出了什么问题..

编辑:有时,正如 Danila 指出的那样,我也会遇到此错误 Cannot assign to read only property '_locationUrl' of object '#<App>'

我克隆了作者创建的 repo,它有效。我的代码有什么问题?我认为是 Promise 的问题,但我不确定,我不知道如何解决:(

您在 DbModel.ts 中使用 mobx-state-treetypes.frozen。 这与 Realm.App 混淆,因为在内部 MongoDB 领域代码正在尝试更改 Realm.App 实例,但由于您冻结了该实例,它将失败。

在您的应用程序代码中移动 Realm.App 创建应该可以解决这个问题。类似于:

function AppWithState() {
  const {
    db: {   client, setClient, user, setUser }
  } = useMst();
  const [app] = useState(new Realm.App(appConfig)) // <-- here is the fix, along with deleting the Realm.App instantiation code from DbModel.ts
 
  .... rest of your AppWithState code .....
}