laravel+ReactでToDoアプリ※製作途中

はじめに

Laravel+Reactの練習にToDoアプリを作ってみます。

✅参考

Reactハンズオン[firestoreでTodoアプリ]

☝️React側のメインの参考

初めてのLaravel6.xとReact入門

☝️LaravelとReactの連携に

【laravel】laravelの命名規則

DB側セットアップ

環境構築は以下でやっています。

【Laravel+React】React側からAxiosでLaravelのAPI(MySQL)叩いてデータ取得

.envの編集

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3307
DB_DATABASE=todo
DB_USERNAME=root
DB_PASSWORD=

DB作成

mysql>create database todo;
mysql>use todo;

マイグレーションファイル作成

$ php artisan make:migration create_todos_table --create=todos
$ php artisan make:migration create_finished_todos_table --create=finished_todos

※テーブル名はスネークケースにすべしっぽい 参考

編集

※両方テーブル名以外同じ。

public function up()
{
    Schema::create('todos', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('name');
        $table->timestamps();
    });
}

モデル作成(Tinkerでの検証やDB取得にモデルクラスが必要)

$ php artisan make:model Todo
$ php artisan make:model FinishedTodo

※モデル名はアッパーキャメルにすること

テーブル作成実行

$ php artisan migrate

※以下MySQLコマンドで確認

MySQL [laravel_local]> show tables;
+-------------------------+
| Tables_in_laravel_local |
+-------------------------+
| failed_jobs |
| finished_todos |
| migrations |
| password_resets |
| todos |
| users |
+-------------------------+
6 rows in set (0.003 sec)

シーダー作成

$ php artisan make:seeder TodosTableSeeder
$ php artisan make:seeder FinishedTodosTableSeeder

※アッパーキャメルにすること、単数複数の指定はない。

DatabaseSeeder.php

public function run()
{
    $this->call(TodosTableSeeder::class);
    $this->call(FinishedTodosTableSeeder::class);
}

TodosTableSeeder.php

※同じ要領でFinishedTodosTableSeeder.phpも編集

public function run()
{
  for ($i = 1; $i <= 5; $i++) {
    DB::table('todos')->insert([ // finished_todos
      'id' => $i,
      'name' => "TODO".$i, // FINISHED TODO
    ]);
  }
}

仮データ反映

$ composer dump-autoload // ファイルの読み込み
$ php artisan db:seed
Seeding: TodosTableSeeder
Seeded:  TodosTableSeeder (0.09 seconds)
Seeding: FinishedTodosTableSeeder
Seeded:  FinishedTodosTableSeeder (0.02 seconds)
Database seeding completed successfully.
$ php artisan db:seed --class=TodosTableSeeder // 単発の場合

api.phpの編集

Route::get('/todos',function (Request $request) {
  $todos = App\Todo::all();
  return response()->json(['todos' => $todos]);
});

Route::get('/finished_todos',function (Request $request) {
  $finished_todos = App\FinishedTodo::all();
  return response()->json(['finished_todos' => $finished_todos]);
});

http://127.0.0.1:8000/api/todosにアクセスして確認

http://localhost/api/finished_todosにアクセス

React側セットアップ※DB表示まで

上記のJSONデータを取得できればOK

取得してみる。

import React, { useEffect, useState } from "react";
import axios from "axios";

function About() {
    const [todos, setTodos] = useState([]);
    const [finishedTodos, setFinishedTodos] = useState([]);

    useEffect(() => {
        getTodos();
        getFinishedTodos();
    }, []);

    const getTodos = async () => {
        const response = await axios.get("/api/todos");
        setTodos(response.data.todos);
    };

    const getFinishedTodos = async () => {
        const response = await axios.get("/api/finished_todos");
        setFinishedTodos(response.data.finished_todos);
    };

    return (
        <div>
            <h1>Aboutページ</h1>
            <h2>Todo</h2>
            <ul>
                {todos.map(todo => (
                    <li key="{todo.id}">{todo.name}</li>
                ))}
            </ul>
            <h2>FinishedTodo</h2>
            <ul>
                {finishedTodos.map(todo => (
                    <li key="{todo.id}">{todo.name}</li>
                ))}
            </ul>
        </div>
    );
}

export default About;

http://localhost/aboutで確認

DevToolsで検証

let response = await axios.get("/api/todos");
console.log(response.data);
let response = await axios.get("/api/finished_todos");
console.log(response.data);

※上記画像命名規則修正前で若干古いです。

デザイン修正

インストール

npm i styled-components

about.css作成

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background-color: #fff2cc;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
    monospace;
}

about.js修正

import React, { useEffect, useState } from "react";
import './about.css';
import axios from "axios";
import styled from "styled-components";

function About() {
    const [todos, setTodos] = useState([]);
    const [finishedTodos, setFinishedTodos] = useState([]);

    useEffect(() => {
       getTodos();
        getFinishedTodos();
    }, []);

    const getTodos = async () => {
        const response = await axios.get("/api/todos");
        setTodos(response.data.todos);
    };

    const getFinishedTodos = async () => {
        const response = await axios.get("/api/finishedtodos");
        setFinishedTodos(response.data.finishedtodos);
    };

    return (
        <div>
            <Title>Aboutページ</Title>
            <TodoContainer>
                <SubContainer>
                    <SubTitle>Todo</SubTitle>
                    <ul>
                        {todos.map(todo => (
                            <li key="{todo.id}">{todo.name}</li>
                        ))}
                    </ul>
                </SubContainer>
                <SubContainer>
                    <SubTitle>FinishedTodo</SubTitle>
                    <ul>
                        {finishedTodos.map(todo => (
                            <li key="{todo.id}">{todo.name}</li>
                        ))}
                    </ul>
                </SubContainer>
            </TodoContainer>
        </div>
    );
}

const Title = styled.p`
    font-size: 26px;
    color: #0097a7;
    letter-spacing: 2.8px;
    font-weight: 200;
`;

const SubTitle = styled.p`
    font-size: 22px;
    color: #5c5c5c;
`;

const SubContainer = styled.div`
    width: 400px;
`;

const TodoContainer = styled.div`
    display: flex;
    flex-direction: row;
    width: 80%;
    margin: 0 auto;
    justify-content: space-between;
`;

export default About;

確認

だいぶそれっぽく?なりました◎

CRUD実装

以前Vue.jsで使った実装を参考にしていきます。

【Axiosの実装】Laravel+Vue.jsでのDB連携

✅DB追加

React上で追加

const [input, setInput] = useState("");
const addTodo = () => {
  if (!!input) {
    setTodos([...todos,{
      id:todos.length +1,
      name:input
    }]);
    setInput("");
  }
};
----
// Titleタグ配下
<input onChange={e => setInput(e.target.value)} value={input} />
<button onClick={() => addTodo()}>追加</button>

確認

LaravelのDBに追加

api.phpにPOST送信用のルーティングを追加

Route::post('/todos',function (Request $request) {
  $todo = new App\Todo();
  $todo->name = $request->name;
  $todo->save();
  return response("OK", 200);
});

js側、メソッドの修正

const addTodo = () => {
  if (!!input) {
    const data = {
      name: input
    }
    axios.post("/api/todos",data)
    .then(()=>{
      console.log("DB追加");
      getTodos();
      setInput("");
    })
  }
};

✅削除

api.phpに追記

Route::delete('/todos/{id}',function (Request $request, $id) {
  App\Todo::find($id)->delete();
  return response("OK", 200);;
});

JS側

const deleteTodo = id => {
    axios.delete("/api/todos/" + id).then(() => {
      console.log("DB削除")
      getTodos();
    });
};

~~~~

{todos.map(todo => (
    <>
        <li id={todo.id}>{todo.name}</li>
        <button onClick={() => deleteTodo(todo.id)}>
            削除
        </button>
    </>
))}

※若干Warning出ているのが気になる。。。

Warning: Each child in a list should have a unique “key” prop.

✅状態の変更(更新)

ReactToDoのチュートリアルでは『完了済みにする』と『戻す』ボタンがある。

ここの処理をできるようにしたい。

 

備忘録

✅Laravelの命名規則

モデルをキャメルケースで書くとスネークケースに変換されるっぽい。

参考:【laravel】laravelの命名規則

finishedTodo.php

クラス名をfinishedtodoに変更。

コメントを残す