尝试使用 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()
函数中,因为 client
和 user
都是 null
,但在 App
我创建了它们并保存在我的状态中,所以我不明白出了什么问题..
编辑:有时,正如 Danila 指出的那样,我也会遇到此错误 Cannot assign to read only property '_locationUrl' of object '#<App>'
。
我克隆了作者创建的 repo,它有效。我的代码有什么问题?我认为是 Promise 的问题,但我不确定,我不知道如何解决:(
您在 DbModel.ts
中使用 mobx-state-tree
和 types.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 .....
}
第一次使用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()
函数中,因为 client
和 user
都是 null
,但在 App
我创建了它们并保存在我的状态中,所以我不明白出了什么问题..
编辑:有时,正如 Danila 指出的那样,我也会遇到此错误 Cannot assign to read only property '_locationUrl' of object '#<App>'
。
我克隆了作者创建的 repo,它有效。我的代码有什么问题?我认为是 Promise 的问题,但我不确定,我不知道如何解决:(
您在 DbModel.ts
中使用 mobx-state-tree
和 types.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 .....
}