这是我第一次使用 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
它们都是非常简单的组件,我在这里只发布其中的一部分。
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>
);
}
状态/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;
}
状态/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 上下文来保存应用程序、用户和客户端等数据库信息,但我更喜欢设置 Mobx 状态树。我认为这是唯一的区别。
哦,我用的是 TypeScript(顺便说一句,client
的类型是什么?阅读指南我没看懂,好像是 MongoDB,但我需要从哪里导入它?)。
我的代码不起作用。 我什么也没得到,仍在加载:
我认为我的应用程序卡在 Home
组件中,在 getData()
函数中,因为 client
和 user
都是 null
,但在 {{1} }} 我创建了它们并保存在我的状态中,所以我不明白出了什么问题..
EDIT:有时,正如 Danila 所指出的,我也会收到此错误 App
。
我克隆了作者创建的 repo,它有效。我的代码有什么问题?我认为是 Promise 的问题,但我不确定,也不知道如何解决 :(
答案 0 :(得分:1)
您在 mobx-state-tree
中使用 DbModel.ts
和 types.frozen
。
这与 Realm.App
混淆,因为内部 MongoDB Realm 代码正在尝试更改 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 .....
}