mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-10 04:22:18 +00:00
Add react native tutorial, fix some mistakes found along the way
This commit is contained in:
@@ -17,7 +17,7 @@ This tutorial will introduce the following concepts:
|
|||||||
2. Authentication
|
2. Authentication
|
||||||
3. Databases and collections
|
3. Databases and collections
|
||||||
4. Queries and pagination
|
4. Queries and pagination
|
||||||
5. Storage
|
|
||||||
|
|
||||||
# Prerequisites {% #prerequisites %}
|
# Prerequisites {% #prerequisites %}
|
||||||
1. Basic knowledge of Kotlin and Android development.
|
1. Basic knowledge of Kotlin and Android development.
|
||||||
|
|||||||
10
src/routes/docs/tutorials/react-native/+layout.svelte
Normal file
10
src/routes/docs/tutorials/react-native/+layout.svelte
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { globToTutorial } from '$lib/utils/tutorials.js';
|
||||||
|
import { setContext } from 'svelte';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
const tutorials = globToTutorial(data);
|
||||||
|
setContext('tutorials', tutorials);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
||||||
11
src/routes/docs/tutorials/react-native/+layout.ts
Normal file
11
src/routes/docs/tutorials/react-native/+layout.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import type { LayoutLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: LayoutLoad = ({ url }) => {
|
||||||
|
const tutorials = import.meta.glob('./**/*.markdoc', {
|
||||||
|
eager: true
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
tutorials,
|
||||||
|
pathname: url.pathname
|
||||||
|
};
|
||||||
|
};
|
||||||
6
src/routes/docs/tutorials/react-native/+page.ts
Normal file
6
src/routes/docs/tutorials/react-native/+page.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageLoad = async () => {
|
||||||
|
redirect(303, '/docs/tutorials/react/step-1');
|
||||||
|
};
|
||||||
28
src/routes/docs/tutorials/react-native/step-1/+page.markdoc
Normal file
28
src/routes/docs/tutorials/react-native/step-1/+page.markdoc
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
layout: tutorial
|
||||||
|
title: Build an ideas tracker with React Native
|
||||||
|
description: Learn to build a React Native app with no backend code using an Appwrite backend.
|
||||||
|
step: 1
|
||||||
|
difficulty: beginner
|
||||||
|
readtime: 10
|
||||||
|
category: Mobile and native
|
||||||
|
framework: React Native
|
||||||
|
---
|
||||||
|
|
||||||
|
**Idea tracker**: an app to track all the side project ideas that you'll start, but probably never finish.
|
||||||
|
In this tutorial, you will build Idea tracker with Appwrite and React Native.
|
||||||
|
|
||||||
|
# Concepts {% #concepts %}
|
||||||
|
|
||||||
|
This tutorial will introduce the following concepts:
|
||||||
|
|
||||||
|
1. Setting up your first project
|
||||||
|
2. Authentication
|
||||||
|
3. Databases and collections
|
||||||
|
4. Queries and pagination
|
||||||
|
|
||||||
|
# Prerequisites {% #prerequisites %}
|
||||||
|
|
||||||
|
1. Android, iOS simulators, or a physical device to run the app
|
||||||
|
2. Have [Node.js](https://nodejs.org/en) and [NPM](https://www.npmjs.com/) installed on your computer
|
||||||
|
3. Basic knowledge of React Native and Expo
|
||||||
43
src/routes/docs/tutorials/react-native/step-2/+page.markdoc
Normal file
43
src/routes/docs/tutorials/react-native/step-2/+page.markdoc
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
layout: tutorial
|
||||||
|
title: Create app
|
||||||
|
description: Create a React Native app project and integrate with Appwrite.
|
||||||
|
step: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create React Native project {% #create-react-project %}
|
||||||
|
|
||||||
|
Create a React Native app with the `npm create` command.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx create-expo-app ideas-tracker
|
||||||
|
cd ideas-tracker
|
||||||
|
```
|
||||||
|
|
||||||
|
# Add dependencies {% #add-dependencies %}
|
||||||
|
|
||||||
|
Install the React Native Appwrite SDK.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx expo install react-native-appwrite react-native-url-polyfill
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, install React Navigation to help implement simple navigation logic.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @react-navigation/native @react-navigation/native-stack
|
||||||
|
```
|
||||||
|
|
||||||
|
Install peer dependencies needed for React Navigation.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx expo install react-native-screens react-native-safe-area-context
|
||||||
|
```
|
||||||
|
|
||||||
|
For iOS with bare React Native project, make sure you have CocoaPods installed. Then install the pods to complete the installation:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd ios
|
||||||
|
pod install
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
60
src/routes/docs/tutorials/react-native/step-3/+page.markdoc
Normal file
60
src/routes/docs/tutorials/react-native/step-3/+page.markdoc
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
layout: tutorial
|
||||||
|
title: Set up Appwrite
|
||||||
|
description: Import and initialize Appwrite for your React Native application.
|
||||||
|
step: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create project {% #create-project %}
|
||||||
|
|
||||||
|
Head to the [Appwrite Console](https://cloud.appwrite.io/console).
|
||||||
|
|
||||||
|
{% only_dark %}
|
||||||
|

|
||||||
|
{% /only_dark %}
|
||||||
|
{% only_light %}
|
||||||
|

|
||||||
|
{% /only_light %}
|
||||||
|
|
||||||
|
If this is your first time using Appwrite, create an account and create your first project.
|
||||||
|
|
||||||
|
Then, under **Add a platform**, add a **Android** or **Apple** platform with the package/bundle ID `com.example.idea-tracker`.
|
||||||
|
|
||||||
|
{% only_dark %}
|
||||||
|

|
||||||
|
{% /only_dark %}
|
||||||
|
{% only_light %}
|
||||||
|

|
||||||
|
{% /only_light %}
|
||||||
|
|
||||||
|
You can skip optional steps.
|
||||||
|
|
||||||
|
# Initialize Appwrite SDK {% #init-sdk %}
|
||||||
|
|
||||||
|
To use Appwrite in our React Native app, you'll need to find our project ID.
|
||||||
|
Find your project's ID in the **Settings** page.
|
||||||
|
|
||||||
|
{% only_dark %}
|
||||||
|

|
||||||
|
{% /only_dark %}
|
||||||
|
{% only_light %}
|
||||||
|

|
||||||
|
{% /only_light %}
|
||||||
|
|
||||||
|
Create a new file `lib/appwrite.js` to hold our Appwrite related code.
|
||||||
|
Only one instance of the `Client()` class should be created per app.
|
||||||
|
Add the following code to it, replacing `<YOUR_PROJECT_ID>` with your project ID.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { Client, Databases, Account } from "react-native-appwrite";
|
||||||
|
|
||||||
|
const client = new Client();
|
||||||
|
client
|
||||||
|
.setEndpoint("https://cloud.appwrite.io/v1")
|
||||||
|
.setProject("<YOUR_PROJECT_ID>") // Replace with your project ID
|
||||||
|
.setPlatform('com.example.idea-tracker');
|
||||||
|
|
||||||
|
|
||||||
|
export const account = new Account(client);
|
||||||
|
export const databases = new Databases(client);
|
||||||
|
```
|
||||||
166
src/routes/docs/tutorials/react-native/step-4/+page.markdoc
Normal file
166
src/routes/docs/tutorials/react-native/step-4/+page.markdoc
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
---
|
||||||
|
layout: tutorial
|
||||||
|
title: Add authentication
|
||||||
|
description: Add authentication to your React Native application.
|
||||||
|
step: 4
|
||||||
|
---
|
||||||
|
|
||||||
|
# User context {% #user-context %}
|
||||||
|
|
||||||
|
In React Native, you can use [context](https://reactjs.org/docs/context.html) to share data between components.
|
||||||
|
You can use a context and a custom hook to manage our user's data.
|
||||||
|
|
||||||
|
Create a new file `contexts/UserContext.jsx` and add the following code to it.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { ID } from "react-native-appwrite";
|
||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
import { account } from "../lib/appwrite";
|
||||||
|
import { toast } from "../lib/toast";
|
||||||
|
|
||||||
|
|
||||||
|
const UserContext = createContext();
|
||||||
|
|
||||||
|
export function useUser() {
|
||||||
|
return useContext(UserContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UserProvider(props) {
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
|
|
||||||
|
async function login(email, password) {
|
||||||
|
const loggedIn = await account.createEmailSession(email, password);
|
||||||
|
setUser(loggedIn);
|
||||||
|
toast('Welcome back. You are logged in');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
await account.deleteSession("current");
|
||||||
|
setUser(null);
|
||||||
|
toast('Logged out');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function register(email, password) {
|
||||||
|
await account.create(ID.unique(), email, password);
|
||||||
|
await login(email, password);
|
||||||
|
toast('Account created');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
try {
|
||||||
|
const loggedIn = await account.get();
|
||||||
|
setUser(loggedIn);
|
||||||
|
toast('Welcome back. You are logged in');
|
||||||
|
} catch (err) {
|
||||||
|
setUser(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
init();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={{ current: user, login, logout, register, toast }}>
|
||||||
|
{props.children}
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, you can use the `useUser` hook to access the user's data from any component wrapped by this context's provider.
|
||||||
|
|
||||||
|
# Display toasts {% #display-toasts %}
|
||||||
|
|
||||||
|
For a better user experience, display toasts when the users perform an action, such as login, logout, create new ideas, etc.
|
||||||
|
|
||||||
|
We can do this by creating a new file `lib/toast.js` and adding the following code to it.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { ToastAndroid, Platform, AlertIOS } from 'react-native';
|
||||||
|
|
||||||
|
export function toast(msg) {
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
ToastAndroid.show(msg, ToastAndroid.SHORT)
|
||||||
|
} else {
|
||||||
|
AlertIOS.alert(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
# Login page {% #login-page %}
|
||||||
|
|
||||||
|
Create a new file `views/Login.jsx` and add the following code to it.
|
||||||
|
this page contains a basic form to allow the user to login or register.
|
||||||
|
Notice how this page consumes the `useUser` hook to access the user's data and perform login and register actions.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
|
||||||
|
import { useUser } from '../contexts/UserContext';
|
||||||
|
|
||||||
|
|
||||||
|
export default function LoginScreen() {
|
||||||
|
const user = useUser();
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.header}>Login or register</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Email"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Password"
|
||||||
|
value={password}
|
||||||
|
onChangeText={setPassword}
|
||||||
|
secureTextEntry
|
||||||
|
/>
|
||||||
|
<View style={styles.buttonContainer}>
|
||||||
|
<Button
|
||||||
|
title="Login"
|
||||||
|
onPress={
|
||||||
|
() => {
|
||||||
|
user.login(email, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
title="Register"
|
||||||
|
onPress={
|
||||||
|
() => {
|
||||||
|
user.register(email, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
fontSize: 24,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
height: 40,
|
||||||
|
borderColor: 'gray',
|
||||||
|
borderWidth: 1,
|
||||||
|
marginBottom: 10,
|
||||||
|
paddingLeft: 8,
|
||||||
|
},
|
||||||
|
buttonContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
106
src/routes/docs/tutorials/react-native/step-5/+page.markdoc
Normal file
106
src/routes/docs/tutorials/react-native/step-5/+page.markdoc
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
layout: tutorial
|
||||||
|
title: Add routing
|
||||||
|
description: Add routing to your React Native applicating using Appwrite.
|
||||||
|
step: 5
|
||||||
|
---
|
||||||
|
In this step, you'll add some basic routing to your app.
|
||||||
|
Based on the user's login status, you'll redirect them to the login page or the home page.
|
||||||
|
|
||||||
|
# Home page {% #home-page %}
|
||||||
|
|
||||||
|
Create a new file `views/Home.jsx` and add the following stub code to it.
|
||||||
|
We'll update this page later to display the ideas posted by other users and allow the user to post their ideas.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text } from 'react-native';
|
||||||
|
import { useUser } from '../contexts/UserContext';
|
||||||
|
|
||||||
|
|
||||||
|
export default function HomeScreen() {
|
||||||
|
const user = useUser();
|
||||||
|
|
||||||
|
console.log(user.current);
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Text>
|
||||||
|
Welcome, {user.current ? user.current.email : 'Please login'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Basic routing {% #basic-routing %}
|
||||||
|
To handle basic routing, you can use the `react-navigation` library.
|
||||||
|
This router also consumes the `UserContext` to determine if the user is logged in or not to redirect the user automatically.
|
||||||
|
|
||||||
|
Create a file `lib/Router.jsx` and add the following code to it.
|
||||||
|
```js
|
||||||
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
|
import LoginScreen from '../views/Login';
|
||||||
|
import HomeScreen from '../views/Home';
|
||||||
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
|
import { useUser } from '../contexts/UserContext';
|
||||||
|
|
||||||
|
const Stack = createNativeStackNavigator();
|
||||||
|
export function Router() {
|
||||||
|
const user = useUser();
|
||||||
|
return (
|
||||||
|
<NavigationContainer>
|
||||||
|
<Stack.Navigator>
|
||||||
|
{user.current == null ? (
|
||||||
|
<Stack.Screen
|
||||||
|
name="Login"
|
||||||
|
component={LoginScreen}
|
||||||
|
options={{ title: 'Login' }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Stack.Screen
|
||||||
|
name="Home"
|
||||||
|
component={HomeScreen}
|
||||||
|
options={{ title: 'Home' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Stack.Navigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We'll display this router in the `App.js` file.
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
import { UserProvider } from './contexts/UserContext';
|
||||||
|
import { Router } from './lib/Router';
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<UserProvider>
|
||||||
|
<Router />
|
||||||
|
</UserProvider >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
# Test the routing {% #test-routing %}
|
||||||
|
Now, if you run the app, you should see the login page.
|
||||||
|
If you create a new account, you should be logged in and redirected to the home page.
|
||||||
|
|
||||||
|
Run the app using the following command.
|
||||||
|
```sh
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
137
src/routes/docs/tutorials/react-native/step-6/+page.markdoc
Normal file
137
src/routes/docs/tutorials/react-native/step-6/+page.markdoc
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
---
|
||||||
|
layout: tutorial
|
||||||
|
title: Add database
|
||||||
|
description: Connect a database to your React Native application using Appwrite Web SDK.
|
||||||
|
step: 6
|
||||||
|
---
|
||||||
|
In this step, you'll set up a database to store ideas in Appwrite, configure permissions, then create a
|
||||||
|
context to manage ideas in your React Native app.
|
||||||
|
|
||||||
|
# Create collection {% #create-collection %}
|
||||||
|
In Appwrite, data is stored as a collection of documents. Create a collection in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas.
|
||||||
|
|
||||||
|
{% only_dark %}
|
||||||
|

|
||||||
|
{% /only_dark %}
|
||||||
|
{% only_light %}
|
||||||
|

|
||||||
|
{% /only_light %}
|
||||||
|
|
||||||
|
Create a new collection with the following attributes:
|
||||||
|
| Field | Type | Required |
|
||||||
|
|-------------|--------|----------|
|
||||||
|
| userId | String | Yes |
|
||||||
|
| title | String | Yes |
|
||||||
|
| description | String | No |
|
||||||
|
|
||||||
|
# Configure permissions {% #configure-permissions %}
|
||||||
|
{% only_dark %}
|
||||||
|

|
||||||
|
{% /only_dark %}
|
||||||
|
{% only_light %}
|
||||||
|

|
||||||
|
{% /only_light %}
|
||||||
|
|
||||||
|
Navigate to the **Settings** tab of your collection, add the role **Any** and check the **Read** box.
|
||||||
|
Next, add a **Users** role and give them access to **Create** by checking those boxes.
|
||||||
|
These permissions apply to all documents in your new collection.
|
||||||
|
|
||||||
|
Finally, enable **Document security** to allow further permissions to be set at the document level.
|
||||||
|
Remember to click the **Update** button to apply your changes.
|
||||||
|
|
||||||
|
# Ideas context {% #ideas-context %}
|
||||||
|
|
||||||
|
Now that you have a collection to hold ideas, we can read and write to it from our app.
|
||||||
|
Like you did with the user data, we will create a React context to hold our ideas.
|
||||||
|
Create a new file `contexts/IdeasContext.jsx` and add the following code to it.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { ID, Permission, Role, Query } from "react-native-appwrite";
|
||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
import { databases } from "../lib/appwrite";
|
||||||
|
import { toast } from "../lib/toast";
|
||||||
|
|
||||||
|
export const IDEAS_DATABASE_ID = "default"; // Replace with your database ID
|
||||||
|
export const IDEAS_COLLECTION_ID = "ideas-tracker"; // Replace with your collection ID
|
||||||
|
|
||||||
|
const IdeasContext = createContext();
|
||||||
|
|
||||||
|
export function useIdeas() {
|
||||||
|
return useContext(IdeasContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IdeasProvider(props) {
|
||||||
|
const [ideas, setIdeas] = useState([]);
|
||||||
|
|
||||||
|
async function add(idea) {
|
||||||
|
const response = await databases.createDocument(
|
||||||
|
IDEAS_DATABASE_ID,
|
||||||
|
IDEAS_COLLECTION_ID,
|
||||||
|
ID.unique(),
|
||||||
|
idea,
|
||||||
|
[Permission.write(Role.user(idea.userId))]
|
||||||
|
);
|
||||||
|
toast('Ideas added');
|
||||||
|
setIdeas((ideas) => [response, ...ideas].slice(0, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove(id) {
|
||||||
|
await databases.deleteDocument(IDEAS_DATABASE_ID, IDEAS_COLLECTION_ID, id);
|
||||||
|
toast('Idea removed');
|
||||||
|
setIdeas((ideas) => ideas.filter((idea) => idea.$id !== id));
|
||||||
|
await init(); // Refetch ideas to ensure we have 10 items
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
const response = await databases.listDocuments(
|
||||||
|
IDEAS_DATABASE_ID,
|
||||||
|
IDEAS_COLLECTION_ID,
|
||||||
|
[Query.orderDesc("$createdAt"), Query.limit(10)]
|
||||||
|
);
|
||||||
|
setIdeas(response.documents);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
init();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IdeasContext.Provider value={{ current: ideas, add, remove }}>
|
||||||
|
{props.children}
|
||||||
|
</IdeasContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that new ideas have the added permission `Permission.write(Role.user(idea.userId))`.
|
||||||
|
This permission ensures that only the user who created the idea can modify it.
|
||||||
|
|
||||||
|
Remeber to add the `IdeasProvider` to your `App.js` file.
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
import { UserProvider } from './contexts/UserContext';
|
||||||
|
import { IdeasProvider } from './contexts/IdeasContext'; // Add import
|
||||||
|
import { Router } from './lib/Router';
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<UserProvider>
|
||||||
|
<!-- Add the Ideas Provider -->
|
||||||
|
<IdeasProvider>
|
||||||
|
<Router />
|
||||||
|
</IdeasProvider>
|
||||||
|
</UserProvider >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
131
src/routes/docs/tutorials/react-native/step-7/+page.markdoc
Normal file
131
src/routes/docs/tutorials/react-native/step-7/+page.markdoc
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
---
|
||||||
|
layout: tutorial
|
||||||
|
title: Create ideas page
|
||||||
|
description: Add database queries and pagination using Appwrite in your React Native application.
|
||||||
|
step: 7
|
||||||
|
---
|
||||||
|
|
||||||
|
Using the `useIdeas` hook you can now display the ideas on the page and create a form to submit new ideas.
|
||||||
|
|
||||||
|
If an idea is submitted by the logged-in user, a remove button will be displayed to remove the idea.
|
||||||
|
While this check uses the user ID to determine the render logic, permissions set in step 6 will be used to enforce
|
||||||
|
that only the owner of the idea can remove it.
|
||||||
|
|
||||||
|
Overwrite the contents of `views/Home.jsx` with the following:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { View, Text, TextInput, Button, StyleSheet, ScrollView } from 'react-native';
|
||||||
|
import { useUser } from '../contexts/UserContext';
|
||||||
|
import { useIdeas } from '../contexts/IdeasContext';
|
||||||
|
|
||||||
|
|
||||||
|
export default function HomeScreen() {
|
||||||
|
const user = useUser();
|
||||||
|
const ideas = useIdeas();
|
||||||
|
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [description, setDescription] = useState("");
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
{user.current ? (
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.header}>Submit Idea</Text>
|
||||||
|
<View>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Title"
|
||||||
|
value={title}
|
||||||
|
onChangeText={(text) => setTitle(text)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Description"
|
||||||
|
value={description}
|
||||||
|
onChangeText={(text) => setDescription(text)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
title="Submit"
|
||||||
|
onPress={() =>
|
||||||
|
ideas.add({ userId: user.current.$id, title, description })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text>Please login to submit an idea.</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.header}>Latest Ideas</Text>
|
||||||
|
<View>
|
||||||
|
{ideas.current.map((idea) => (
|
||||||
|
<View key={idea.$id} style={styles.card}>
|
||||||
|
<Text style={styles.cardTitle}>{idea.title}</Text>
|
||||||
|
<Text style={styles.cardDescription}>{idea.description}</Text>
|
||||||
|
{/* Show the remove button to idea owner. */}
|
||||||
|
{user.current && user.current.$id === idea.userId && (
|
||||||
|
<Button
|
||||||
|
title="Remove"
|
||||||
|
onPress={() => ideas.remove(idea.$id)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
section: {
|
||||||
|
marginBottom: 20,
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
fontSize: 24,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
height: 40,
|
||||||
|
borderColor: 'gray',
|
||||||
|
borderWidth: 1,
|
||||||
|
marginBottom: 10,
|
||||||
|
paddingLeft: 8,
|
||||||
|
},
|
||||||
|
buttonContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
borderRadius: 8,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: 2,
|
||||||
|
},
|
||||||
|
shadowOpacity: 0.25,
|
||||||
|
shadowRadius: 3.84,
|
||||||
|
elevation: 5,
|
||||||
|
},
|
||||||
|
cardTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
cardDescription: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
14
src/routes/docs/tutorials/react-native/step-8/+page.markdoc
Normal file
14
src/routes/docs/tutorials/react-native/step-8/+page.markdoc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
layout: tutorial
|
||||||
|
title: Next steps
|
||||||
|
description: Run your React Native project built with Appwrite
|
||||||
|
step: 8
|
||||||
|
---
|
||||||
|
|
||||||
|
# Test your project {% #test-project %}
|
||||||
|
You can run your projects with `npm run start`. This will start the Metro bundler and open the Expo Go app on your device.
|
||||||
|
You can also run your project on an emulator or simulator by pressing `i` for iOS and `a` for Android.
|
||||||
|
|
||||||
|
# Bundling for production {% #bundle-production %}
|
||||||
|
Appwrite's React Native SDK is designed to work with the Expo Metro bundler.
|
||||||
|
When you are ready to build your app for production, you can learn more in the [Expo documentation](https://docs.expo.dev/).
|
||||||
@@ -27,7 +27,7 @@ This tutorial will introduce the following concepts:
|
|||||||
2. Authentication
|
2. Authentication
|
||||||
3. Databases and collections
|
3. Databases and collections
|
||||||
4. Queries and pagination
|
4. Queries and pagination
|
||||||
5. Storage
|
|
||||||
|
|
||||||
|
|
||||||
# Prerequisites {% #prerequisites %}
|
# Prerequisites {% #prerequisites %}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function IdeasProvider(props) {
|
|||||||
ID.unique(),
|
ID.unique(),
|
||||||
idea
|
idea
|
||||||
);
|
);
|
||||||
setIdeas((ideas) => [response.documents, ...ideas].slice(0, 10));
|
setIdeas((ideas) => [response, ...ideas].slice(0, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function remove(id) {
|
async function remove(id) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ This tutorial will introduce the following concepts:
|
|||||||
2. Authentication
|
2. Authentication
|
||||||
3. Databases and collections
|
3. Databases and collections
|
||||||
4. Queries and pagination
|
4. Queries and pagination
|
||||||
5. Storage
|
|
||||||
|
|
||||||
|
|
||||||
# Prerequisites {% #prerequisites %}
|
# Prerequisites {% #prerequisites %}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ This tutorial will introduce the following concepts:
|
|||||||
2. Authentication
|
2. Authentication
|
||||||
3. Databases and collections
|
3. Databases and collections
|
||||||
4. Queries and pagination
|
4. Queries and pagination
|
||||||
5. Storage
|
|
||||||
|
|
||||||
# Prerequisites {% #prerequisites %}
|
# Prerequisites {% #prerequisites %}
|
||||||
1. Basic knowledge of JavaScript and Svelte.
|
1. Basic knowledge of JavaScript and Svelte.
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ This tutorial will introduce the following concepts:
|
|||||||
2. Authentication
|
2. Authentication
|
||||||
3. Databases and collections
|
3. Databases and collections
|
||||||
4. Queries and pagination
|
4. Queries and pagination
|
||||||
5. Storage
|
|
||||||
|
|
||||||
|
|
||||||
# Prerequisites {% #prerequisites %}
|
# Prerequisites {% #prerequisites %}
|
||||||
|
|||||||
Reference in New Issue
Block a user