React Native + SupabaseでTodoアプリ開発コピペ🙅

20251111

文字数[8217文字] この記事は10分16秒で読めます.

Logging

おはようございます.React Native + SupabaseでTodoアプリ開発を彼のソース・コードを参考にしてコードを書きました.コードの修正版をUPします.コード量がブログに記載するには多いので今回はコードだけをUPしますのでライブラリのインストールなどはご自身でインストールしてください.

import React, { useState } from 'react';
import { Alert, StyleSheet, View, TextInput, Button } from 'react-native';
import { supabase } from '../lib/supabase';

export default function Auth() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);

  async function signInWithEmail() {
    setLoading(true);
    const { error } = await supabase.auth.signInWithPassword({ email, password });
    if (error) Alert.alert(error.message);
    setLoading(false);
  }

  async function signUpWithEmail() {
    setLoading(true);
    const { error } = await supabase.auth.signUp({ email, password });
    if (error) Alert.alert(error.message);
    setLoading(false);
  }

  return (
    <View style={styles.container}>
      <TextInput style={styles.inputContainer} onChangeText={setEmail} value={email} placeholder="email@address.com" autoCapitalize="none" placeholderTextColor="#8B949E" />
      <TextInput style={styles.inputContainer} onChangeText={setPassword} value={password} secureTextEntry={true} placeholder="Password" placeholderTextColor="#8B949E" autoCapitalize="none" />
      <Button title="Sign in" disabled={loading} onPress={signInWithEmail} />
      <View style={{ marginTop: 10 }}>
        <Button title="Sign up" disabled={loading} onPress={signUpWithEmail} />
      </View>
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#7a7c80',
    padding: 20,
    paddingTop: 60,
  },
  inputContainer: {
    flexDirection: 'row',
    marginBottom: 20,
    backgroundColor: '#7a7c80',
    borderRadius: 12,
    paddingHorizontal: 10,
    paddingVertical: 8,
    alignItems: 'center',
    shadowColor: '#000',
    shadowOpacity: 0.2,
    shadowRadius: 6,
    elevation: 5,
    color:'#fff',
  },
});
import React, { useState, useEffect, createContext, useContext, PropsWithChildren } from 'react';
import { Session } from '@supabase/supabase-js';
import { supabase } from '../lib/supabase';

type AuthContextType = { session: Session | null; };

const AuthContext = createContext<AuthContextType>({ session: null });

export const AuthProvider = ({ children }: PropsWithChildren) => {
  const [session, setSession] = useState<Session | null>(null);

  useEffect(() => {
    supabase.auth.getSession().then(({ data: { session } }) => {
      setSession(session);
    });

    const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
      setSession(session);
    });

    return () => subscription.unsubscribe();
  }, []);

  return <AuthContext.Provider value={{ session }}>{children}</AuthContext.Provider>;
};

export const useAuth = () => useContext(AuthContext);
import React, { useState, useEffect } from 'react';
import { View, TextInput, Button, FlatList, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { supabase } from '../lib/supabase';
import { useAuth } from './AuthProvider';

type Todo = { id: number; task: string; is_complete: boolean; };

export default function TodoList() {
  const { session } = useAuth();
  const [todos, setTodos] = useState<Todo[]>([]);
  const [newTask, setNewTask] = useState('');

  useEffect(() => {
    fetchTodos();
  }, []);

  const fetchTodos = async () => {
    const { data, error } = await supabase.from('todos').select('*').order('id');
    if (error) console.log('error', error);
    else setTodos(data as Todo[]);
  };

  const addTodo = async () => {
    if (!newTask.trim()) return;
    const { data, error } = await supabase.from('todos').insert({ task: newTask, user_id: session!.user.id }).select();
    if (error) console.log('error', error);
    else setTodos([...todos, data[0]]);
    setNewTask('');
  };

  const toggleComplete = async (id: number, is_complete: boolean) => {
    const { error } = await supabase.from('todos').update({ is_complete: !is_complete }).match({ id });
    if (error) console.log('error', error);
    else setTodos(todos.map(todo => todo.id === id ? { ...todo, is_complete: !is_complete } : todo));
  };

  const deleteTodo = async (id: number) => {
    const { error } = await supabase.from('todos').delete().match({ id });
    if (error) console.log('error', error);
    else setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <View style={styles.container}>
      <View style={styles.inputContainer}>
        <TextInput style={styles.input} onChangeText={setNewTask} value={newTask} placeholder="Add a new task" placeholderTextColor="#8B949E" />
        <Button title="Add" onPress={addTodo} />
      </View>
      <FlatList
        data={todos}
        keyExtractor={(item) => item.id.toString()}
        renderItem={({ item }) => (
          <View style={styles.todoItem}>
            <TouchableOpacity onPress={() => toggleComplete(item.id, item.is_complete)}>
              <Text style={item.is_complete ? styles.completed : {color:'#fff'}}>{item.task}</Text>
            </TouchableOpacity>
            <Button title="Delete" onPress={() => deleteTodo(item.id)} color="red" />
          </View>
        )}
      />
      <Button title="Sign Out" onPress={() => supabase.auth.signOut()} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#7a7c80', 
    padding: 20,
    paddingTop: 60,
  },
  inputContainer: {
    flexDirection: 'row',
    marginBottom: 20,
    backgroundColor: '#7a7c80',
    borderRadius: 12,
    paddingHorizontal: 10,
    paddingVertical: 8,
    alignItems: 'center',
    shadowColor: '#000',
    shadowOpacity: 0.2,
    shadowRadius: 6,
    elevation: 5,
    color:'#fff',
  },
  input: {
    flex: 1,
    color: '#fff',
    fontSize: 16,
    marginRight: 8,
  },
  todoItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    backgroundColor: '#7a7c80',
    padding: 14,
    borderRadius: 10,
    marginBottom: 10,
    shadowColor: '#000',
    shadowOpacity: 0.15,
    shadowOffset: { width: 0, height: 2 },
    shadowRadius: 4,
    elevation: 3,
  },
  completed: {
    textDecorationLine: 'line-through',
    color: '#58A6FF',
    opacity: 0.6,
  },
});
import 'react-native-url-polyfill/auto';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createClient } from '@supabase/supabase-js';
import { SUPABASE_URL, SUPABASE_ANON_KEY } from '@env';

const supabaseUrl = SUPABASE_URL;
const supabaseAnonKey = SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
});
declare module '@env' {
  export const SUPABASE_URL: string;
  export const SUPABASE_ANON_KEY: string;
}
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: [
      [
        'module:react-native-dotenv',
        {
          moduleName: '@env',
          path: '.env',
          safe: false,
          allowUndefined: true,
        },
      ],
    ],
  };
};
SUPABASE_URL=先ほど控えたURL
SUPABASE_ANON_KEY=先ほど控えたanonキー

明日へ続く




3562番目の投稿です/1153 回表示されています.

中の人🏠️

AIによるおすすめ記事

著者名  @taoka_toshiaki

※この記事は著者が40代後半に書いたものです.

Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki

OFUSEで応援を送る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


タグ

Native, react, Supabase, Todo, UP, アプリ, インストール, コード, ソース, ブログ, ライブラリ, 今回, 修正, 参考, 明日, 自身, 記載, 開発,