Skip to content

Commit 9d4d240

Browse files
authored
Add Web code blocks (#517)
1 parent b9e5af2 commit 9d4d240

24 files changed

+289
-26
lines changed

source/includes/steps-task-tracker-web-apollo.yaml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ content: |
77
component with the following code to create an ``ApolloClient`` object that
88
connects to your app:
99
10-
.. literalinclude:: RealmApolloProvider.codeblock.realmApolloProvider.js
10+
.. literalinclude:: /tutorial/generated/code/final/RealmApolloProvider.codeblock.realmApolloProvider.js
1111
:caption: ``src/graphql/RealmApolloProvider.js``
1212
:emphasize-lines: 2-6
1313
---
@@ -20,12 +20,10 @@ content: |
2020
GraphQL request must include an Authorization header that specifies a valid
2121
user access token. The current client does not include any Authorization
2222
headers, so all requests it makes will fail.
23-
23+
2424
To fix this, update the ``createRealmApolloClient()`` function to include the
2525
current user's access token in an Authorization header with every request:
2626
27-
.. literalinclude:: RealmApolloProvider.codeblock.createRealmApolloClient.js
27+
.. literalinclude:: /tutorial/generated/code/final/RealmApolloProvider.codeblock.createRealmApolloClient.js
2828
:caption: ``src/graphql/RealmApolloProvider.js``
2929
:emphasize-lines: 4, 13
30-
31-
...

source/includes/steps-task-tracker-web-realmapp.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ content: |
55
The app client is the primary interface to your Realm app from the SDK. In
66
``src/App.js``, replace ``"TODO"`` with your Realm App ID:
77
8-
.. literalinclude:: App.codeblock.appID.js
8+
.. literalinclude:: /tutorial/generated/code/final/App.codeblock.appID.js
99
:caption: ``src/App.js``
10-
10+
:language: javascript
11+
1112
.. admonition:: Use Your App ID
1213
:class: note
1314
@@ -32,7 +33,6 @@ content: |
3233
currently use the app client you created. You need to update the functions to
3334
actually call the SDK authentication and registration methods.
3435
35-
.. literalinclude:: final/realmAppProvider.js
36-
:caption: ``src/RealmApp.tsx``
37-
:emphasize-lines: 10, 16, 19
38-
36+
.. literalinclude:: /tutorial/generated/code/final/RealmApp.codeblock.realmAppProvider.js
37+
:caption: ``src/RealmApp.js``
38+
:language: javascript
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const APP_ID = "<your Realm app ID here>";
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const RequireLoggedInUser = ({ children }) => {
2+
// Only render children if there is a logged in user.
3+
const app = useRealmApp();
4+
return app.currentUser ? children : <LoginScreen />;
5+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
function useTeamMembers() {
2+
const [teamMembers, setTeamMembers] = React.useState(null);
3+
const [newUserEmailError, setNewUserEmailError] = React.useState(null);
4+
const app = useRealmApp();
5+
const { addTeamMember, removeTeamMember, getMyTeamMembers } = app.functions;
6+
const updateTeamMembers = async () => {
7+
const team = await getMyTeamMembers();
8+
setTeamMembers(team);
9+
};
10+
/* eslint-disable react-hooks/exhaustive-deps */
11+
React.useEffect(() => {
12+
// display team members on load
13+
updateTeamMembers();
14+
}, []);
15+
/* eslint-enable react-hooks/exhaustive-deps */
16+
return {
17+
teamMembers,
18+
errorMessage: newUserEmailError,
19+
addTeamMember: async (email) => {
20+
const { error } = await addTeamMember(email);
21+
if (error) {
22+
setNewUserEmailError(error);
23+
return { error };
24+
} else {
25+
updateTeamMembers();
26+
}
27+
},
28+
removeTeamMember: async (email) => {
29+
await removeTeamMember(email);
30+
updateTeamMembers();
31+
},
32+
};
33+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const handleLogin = async () => {
2+
setIsLoggingIn(true);
3+
setError((e) => ({ ...e, password: null }));
4+
try {
5+
await app.logIn(Realm.Credentials.emailPassword(email, password));
6+
} catch (err) {
7+
handleAuthenticationError(err, setError);
8+
}
9+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const handleRegistrationAndLogin = async () => {
2+
const isValidEmailAddress = validator.isEmail(email);
3+
setError((e) => ({ ...e, password: null }));
4+
if (isValidEmailAddress) {
5+
try {
6+
// Register the user and, if successful, log them in
7+
await app.emailPasswordAuth.registerUser(email, password);
8+
return await handleLogin();
9+
} catch (err) {
10+
handleAuthenticationError(err, setError);
11+
}
12+
} else {
13+
setError((err) => ({ ...err, email: "Email is invalid." }));
14+
}
15+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const createRealmApolloClient = (app) => {
2+
const link = new HttpLink({
3+
// Realm apps use a standard GraphQL endpoint, identified by their App ID
4+
uri: `https://realm.mongodb.com/api/client/v2.0/app/${app.id}/graphql`,
5+
// A custom fetch handler adds the logged in user's access token to GraphQL requests
6+
fetch: async (uri, options) => {
7+
if (!app.currentUser) {
8+
throw new Error(`Must be logged in to use the GraphQL API`);
9+
}
10+
// Refreshing a user's custom data also refreshes their access token
11+
await app.currentUser.refreshCustomData();
12+
// The handler adds a bearer token Authorization header to the otherwise unchanged request
13+
options.headers.Authorization = `Bearer ${app.currentUser.accessToken}`;
14+
return fetch(uri, options);
15+
},
16+
});
17+
18+
const cache = new InMemoryCache();
19+
20+
return new ApolloClient({ link, cache });
21+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function RealmApolloProvider({ children }) {
2+
const app = useRealmApp();
3+
const [client, setClient] = React.useState(createRealmApolloClient(app));
4+
React.useEffect(() => {
5+
setClient(createRealmApolloClient(app));
6+
}, [app]);
7+
return <ApolloProvider client={client}>{children}</ApolloProvider>;
8+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export const RealmAppProvider = ({ appId, children }) => {
2+
const [app, setApp] = React.useState(new Realm.App(appId));
3+
React.useEffect(() => {
4+
setApp(new Realm.App(appId));
5+
}, [appId]);
6+
7+
// Wrap the Realm.App object's user state with React state
8+
const [currentUser, setCurrentUser] = React.useState(app.currentUser);
9+
async function logIn(credentials) {
10+
await app.logIn(credentials);
11+
// If successful, app.currentUser is the user that just logged in
12+
setCurrentUser(app.currentUser);
13+
}
14+
async function logOut() {
15+
// Log out the currently active user
16+
await app.currentUser?.logOut();
17+
// If another user was logged in too, they're now the current user.
18+
// Otherwise, app.currentUser is null.
19+
setCurrentUser(app.currentUser);
20+
}
21+
22+
const wrapped = { ...app, currentUser, logIn, logOut };
23+
24+
return (
25+
<RealmAppContext.Provider value={wrapped}>
26+
{children}
27+
</RealmAppContext.Provider>
28+
);
29+
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
let app = App(id: "tasktracker-qczfq")
1+
let app = App(id: "<your Realm app ID here>")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
return (
2+
<SidebarContainer>
3+
<Card>
4+
<SectionHeading>Projects</SectionHeading>
5+
<SectionList>
6+
{projects.map((project) => (
7+
<SectionListItem
8+
key={project.partition}
9+
onClick={() => setCurrentProject(project)}
10+
isSelected={project.partition === currentProject?.partition}
11+
>
12+
{project.name}
13+
</SectionListItem>
14+
))}
15+
</SectionList>
16+
<UserDetails
17+
user={app.currentUser}
18+
handleLogout={() => {
19+
app.logOut();
20+
}}
21+
handleEditPermissions={() => {
22+
setIsEditingPermissions(true);
23+
}}
24+
/>
25+
</Card>
26+
</SidebarContainer>
27+
);
28+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const projects = useProjects();
2+
const app = useRealmApp();

source/tutorial/generated/code/final/getRealmApp.codeblock.get-realm-app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Returns the shared instance of the Realm app.
22
export function getRealmApp() {
33
if (app === undefined) {
4-
const appId = "tasktracker-qczfq"; // Set Realm app ID here.
4+
const appId = "<your Realm app ID here>"; // Set Realm app ID here.
55
const appConfig = {
66
id: appId,
77
timeout: 10000,
@@ -13,4 +13,4 @@ export function getRealmApp() {
1313
app = new Realm.App(appConfig);
1414
}
1515
return app;
16-
}
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
static schema = {
2+
name: "Task",
3+
properties: {
4+
_id: "objectId",
5+
_partition: "string?",
6+
name: "string",
7+
status: "string",
8+
},
9+
primaryKey: "_id",
10+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function useProjects() {
2+
const app = useRealmApp();
3+
if (!app.currentUser) {
4+
throw new Error("Cannot list projects if there is no logged in user.");
5+
}
6+
const projects = app.currentUser.customData.memberOf;
7+
return projects;
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const AddTaskMutation = gql`
2+
mutation AddTask($task: TaskInsertInput!) {
3+
addedTask: insertOneTask(data: $task) {
4+
_id
5+
_partition
6+
name
7+
status
8+
}
9+
}
10+
`;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const DeleteTaskMutation = gql`
2+
mutation DeleteTask($taskId: ObjectId!) {
3+
deletedTask: deleteOneTask(query: { _id: taskId }) {
4+
_id
5+
_partition
6+
name
7+
status
8+
}
9+
}
10+
`;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const UpdateTaskMutation = gql`
2+
mutation UpdateTask($taskId: ObjectId!, $updates: TaskUpdateInput!) {
3+
updatedTask: updateOneTask(query: { _id: $taskId }, set: $updates) {
4+
_id
5+
_partition
6+
name
7+
status
8+
}
9+
}
10+
`;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
function useAddTask(project) {
2+
const [addTaskMutation] = useMutation(AddTaskMutation, {
3+
// Manually save added Tasks into the Apollo cache so that Task queries automatically update
4+
// For details, refer to https://www.apollographql.com/docs/react/data/mutations/#making-all-other-cache-updates
5+
update: (cache, { data: { addedTask } }) => {
6+
cache.modify({
7+
fields: {
8+
tasks: (existingTasks = []) => [
9+
...existingTasks,
10+
cache.writeFragment({
11+
data: addedTask,
12+
fragment: TaskFieldsFragment,
13+
}),
14+
],
15+
},
16+
});
17+
},
18+
});
19+
20+
const addTask = async (task) => {
21+
const { addedTask } = await addTaskMutation({
22+
variables: {
23+
task: {
24+
_id: new ObjectId(),
25+
_partition: project.partition,
26+
status: "Open",
27+
...task,
28+
},
29+
},
30+
});
31+
return addedTask;
32+
};
33+
34+
return addTask;
35+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function useDeleteTask(project) {
2+
const [deleteTaskMutation] = useMutation(DeleteTaskMutation);
3+
const deleteTask = async (task) => {
4+
const { deletedTask } = await deleteTaskMutation({
5+
variables: { taskId: task._id },
6+
});
7+
return deletedTask;
8+
};
9+
return deleteTask;
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function useUpdateTask(project) {
2+
const [updateTaskMutation] = useMutation(UpdateTaskMutation);
3+
const updateTask = async (task, updates) => {
4+
const { updatedTask } = await updateTaskMutation({
5+
variables: { taskId: task._id, updates },
6+
});
7+
return updatedTask;
8+
};
9+
return updateTask;
10+
}

0 commit comments

Comments
 (0)