LoginSignup
0
0

More than 1 year has passed since last update.

React( ReduxToolkit + Tailwindcss )の構成でアプリを作成をしました【CRUD Application 】

Last updated at Posted at 2022-07-13

環境の準備

①ターミナルでreactアプリケーションを作成する。

npx create-react-app <プロジェクト名>
cd <プロジェクト名>
yarn start

② 必要なパッケージをインストールする。

公式サイト: momentjs

yarn add react-router-dom
npm install @reduxjs/toolkit react-redux
yarn add uuid
yarn add moment

公式サイト: Tailwind CSS

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

コンポーネント・ファイル構成

  src
   ├─ components
        ├── Button.js
        ├── Login.js
        ├── Header.js
        └── TextField.js
   ├─ features
        ├── AddUser.js
        ├── EditUser.js 
        ├── UserList.js
        └── userSlice.js
   ├── App.js
   ├── index.css
   ├── index.js
   └── store.js
├── tailwind.config.ts
src/ components/ Button.js

const Button = ({ onClick, children }) => {
  return (
    <button
      className='px-6 py-2 my-2 text-white rounded bg-emerald-500 hover:bg-emerald-600'
      onClick={onClick}
    >
      {children}
    </button>
  );
};

export default Button;
src/ components/ Header.js
import { useNavigate } from 'react-router-dom';
import { Link } from 'react-router-dom';

const Header = () => {
  const navigate = useNavigate();
  const handleClick = () => {
    navigate('/user-auth');
  };

  return (
    <div className='w-full'>
      <nav className='flex flex-wrap items-center justify-between p-4 bg-emerald-500'>
        <div className='flex items-center flex-shrink-0 mr-6 text-white'>
          <span className='text-2xl font-semibold tracking-tight'>
            Front-end Blog
          </span>
        </div>
        <Link to='/user-auth'>
          <button
            onClick={handleClick}
            className='inline-block px-4 py-2 mt-4 text-sm leading-none text-white border border-white rounded hover:border-transparent hover:text-emerald-700 hover:bg-white lg:mt-0'
          >
            Login to admin
          </button>
        </Link>
      </nav>
    </div>
  );
};

export default Header;
src/ components/ Login.js

import { useContext, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';

const Login = () => {
  const { login } = useContext();
  const navigate = useNavigate();

  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    login(email, password, navigate);
  };

  return (
    <div className='relative flex flex-col justify-center min-h-screen overflow-hidden'>
      <div className='w-full p-6 m-auto bg-white rounded-md shadow-md lg:max-w-xl'>
        <h1 className='text-3xl font-semibold text-center underline text-emerald-500'>
          LogIn
        </h1>
        <form className='mt-6' onSubmit={handleSubmit}>
          <div className='mb-2'>
            <label
              for='email'
              className='block text-sm font-semibold text-gray-800'
            >
              Email
            </label>
            <input
              value={email}
              onChange={(e) => {
                setEmail(e.currentTarget.value);
              }}
              type='email'
              className='block w-full px-4 py-2 mt-2 bg-white border rounded-md text-emerald-600 focus:border-emerald-400 focus:ring-emerald-300 focus:outline-none focus:ring focus:ring-opacity-40'
            />
          </div>
          <div className='mb-2'>
            <label
              for='password'
              className='block text-sm font-semibold text-gray-800'
            >
              Password
            </label>
            <input
              value={password}
              onChange={(e) => {
                setPassword(e.currentTarget.value);
              }}
              type='password'
              className='block w-full px-4 py-2 mt-2 bg-white border rounded-md text-emerald-700 focus:border-emerald-400 focus:ring-emerald-300 focus:outline-none focus:ring focus:ring-opacity-40'
            />
          </div>
          <div className='mt-6'>
            <Link to='/'>
              <button className='w-full px-4 py-2 tracking-wide text-white transition-colors duration-200 transform rounded-md bg-emerald-500 hover:bg-emerald-600 focus:outline-none focus:bg-emerald-600'>
                LogIn
              </button>
            </Link>
          </div>
        </form>
      </div>
    </div>
  );
};

export default Login;
src/ components/ TextField.js
const TextField = ({ label, inputProps, onChange, value }) => {
  return (
    <div className='flex flex-col'>
      <label className='mb-2 text-base text-gray-800'>{label}</label>
      <input
        className='px-3 py-2 bg-gray-200 border-2 outline-none'
        {...inputProps}
        onChange={onChange}
        value={value}
      />
    </div>
  );
};

export default TextField;
features/ AddUser.js
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { v4 as uuidv4 } from 'uuid';
import Button from '../components/Button';
import TextField from '../components/TextField';
import { addUser } from './userSlice';

const AddUser = () => {
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const [values, setValues] = useState({
    name: '',
    email: '',
  });

  const handleAddUser = () => {
    setValues({ name: '', email: '' });
    dispatch(
      addUser({
        id: uuidv4(),
        name: values.name,
        email: values.email,
      })
    );
    navigate('/');
  };

  return (
    <div className='max-w-xl mx-auto mt-10'>
      <TextField
        label='Title'
        value={values.name}
        onChange={(e) => setValues({ ...values, name: e.target.value })}
        inputProps={{ type: 'title', placeholder: 'Enter a title' }}
      />
      <br />
      <TextField
        label='Content'
        value={values.email}
        onChange={(e) => setValues({ ...values, email: e.target.value })}
        inputProps={{ type: 'content', placeholder: 'Enter a content' }}
      />
      <Button onClick={handleAddUser}>Submit</Button>
    </div>
  );
};

export default AddUser;
features/ EditUser.js
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate, useParams } from 'react-router-dom';
import Button from '../components/Button';
import TextField from '../components/TextField';
import { editUser } from './userSlice';

const EditUser = () => {
  const params = useParams();
  const dispatch = useDispatch();
  const users = useSelector((store) => store.users);
  const navigate = useNavigate();
  const existingUser = users.filter((user) => user.id === params.id);
  const { name, email } = existingUser[0];
  const [values, setValues] = useState({
    name,
    email,
  });

  const handleEditUser = () => {
    setValues({ name: '', email: '' });
    dispatch(
      editUser({
        id: params.id,
        name: values.name,
        email: values.email,
      })
    );
    navigate('/');
  };

  return (
    <div className='max-w-xl mx-auto mt-10'>
      <TextField
        label='Name'
        value={values.name}
        onChange={(e) => setValues({ ...values, name: e.target.value })}
        inputProps={{ type: 'text', placeholder: 'Jhon Doe' }}
      />
      <br />
      <TextField
        label='Email'
        value={values.email}
        onChange={(e) => setValues({ ...values, email: e.target.value })}
        inputProps={{ type: 'email', placeholder: 'jhondoe@mail.com' }}
      />
      <Button onClick={handleEditUser}>Edit</Button>
    </div>
  );
};

export default EditUser;
features/ UserList.js
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import Button from '../components/Button';
import { deleteUser } from './userSlice';
import moment from 'moment';
import ReactMarkdown from 'react-markdown';

const markdown = `Just a link: https://reactjs.com.`;

const UserList = () => {
  const [showModal, setShowModal] = useState(false);

  const dispatch = useDispatch();
  const users = useSelector((store) => store.users);

  const handleRemoveUser = (id) => {
    dispatch(deleteUser({ id }));
  };

  const renderCard = () =>
    users.map((user) => (
      <div
        className='flex items-center justify-between p-6 bg-gray-100 shadow-lg'
        type='button'
        key={user.id}
        onClick={() => setShowModal(true)}
      >
        <div>
          <p className='font-normal text-gray-400'>
            {moment().format('MMMM Do YYYY, h:mm:ss a')}
          </p>
          <h3 className='text-lg font-bold text-gray-800'>{user.name}</h3>
          {/* <span className='font-normal text-gray-800'>{user.email}</span> */}
        </div>
        {showModal ? (
          <>
            <div className='fixed inset-0 z-50 flex items-center justify-center overflow-x-hidden overflow-y-auto shadow-lg outline-none focus:outline-none'>
              <div className='relative w-screen max-w-3xl mx-auto my-6'>
                <div className='relative flex flex-col w-full bg-white border-0 rounded-lg shadow-lg outline-none focus:outline-none'>
                  <div className='flex items-start justify-between p-8 border-b border-gray-300 border-solid rounded-t'>
                    <h3 className='text-3xl font-semibold'>{user.name}</h3>
                  </div>
                  <div className='relative flex-1 py-56'>
                    <span className='font-bold text-gray-800 markdown'>
                      <ReactMarkdown children={markdown}>
                        {user.email}
                      </ReactMarkdown>
                    </span>
                  </div>
                  <div className='flex items-center justify-end p-8 border-t border-solid rounded-b border-blueGray-200'>
                    <div className='flex gap-8'>
                      <Link to={`edit-user/${user.id}`}>
                        <button>
                          <svg
                            xmlns='http://www.w3.org/2000/svg'
                            className='w-6 h-6'
                            fill='none'
                            viewBox='0 0 24 24'
                            stroke='currentColor'
                            strokeWidth={2}
                          >
                            <path
                              strokeLinecap='round'
                              strokeLinejoin='round'
                              d='M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z'
                            />
                          </svg>
                        </button>
                      </Link>
                      <button onClick={() => handleRemoveUser(user.id)}>
                        <svg
                          xmlns='http://www.w3.org/2000/svg'
                          className='w-6 h-6'
                          fill='none'
                          viewBox='0 0 24 24'
                          stroke='currentColor'
                          strokeWidth={2}
                        >
                          <path
                            strokeLinecap='round'
                            strokeLinejoin='round'
                            d='M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16'
                          />
                        </svg>
                      </button>
                      {/* <Link to='/'> */}
                      <button
                        onClick={() => setShowModal(false)}
                        className='px-4 py-2 text-sm text-white rounded-md focus:outline-none bg-emerald-500 hover:bg-emerald-700 hover:shadow-lg'
                      >
                        Return
                      </button>
                      {/* </Link> */}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </>
        ) : null}
      </div>
    ));

  return (
    <div>
      <Link to='/add-user'>
        <Button>Add Post</Button>
      </Link>
      <div className='grid gap-2 md:grid-cols-1'>
        {users.length ? (
          renderCard()
        ) : (
          <p className='col-span-2 text-6xl font-bold text-center text-gray-800'>
            No Title
          </p>
        )}
      </div>
    </div>
  );
};

export default UserList;
features/ userSlice.js

import { createSlice } from '@reduxjs/toolkit';

const initialState = [];

const userSlice = createSlice({
  name: 'users',
  initialState,
  reducers: {
    addUser: (state, action) => {
      state.push(action.payload);
    },
    editUser: (state, action) => {
      const { id, name, email } = action.payload;
      const existingUser = state.find((user) => user.id === id);
      if (existingUser) {
        existingUser.name = name;
        existingUser.email = email;
      }
    },
    deleteUser: (state, action) => {
      const { id } = action.payload;
      const existingUser = state.find((user) => user.id === id);
      if (existingUser) {
        return state.filter((user) => user.id !== id);
      }
    },
  },
});

export const { addUser, editUser, deleteUser } = userSlice.actions;
export default userSlice.reducer;
src/ App.js
import { Route, Routes } from 'react-router-dom';
import Header from './components/Header';
import Login from './components/Login';
import AddUser from './features/AddUser';
import EditUser from './features/EditUser';
import UserList from './features/UserList';

function App() {
  return (
    <div className='flex flex-col h-full bg-white shadow-lg max-h-16'>
      <Header />
      <div className='container max-w-5xl px-2 pt-10 mx-auto md:pt-32'>
        <Routes>
          <Route path='/' element={<UserList />} />
          <Route path='/add-user' element={<AddUser />} />
          <Route path='/edit-user/:id' element={<EditUser />} />
          <Route path='/user-auth' element={<Login />} />
        </Routes>
      </div>
    </div>
  );
}

export default App;
src/ index.css
@tailwind base;
@tailwind components;
@tailwind utilities;

html {
  background-color: white;
}
src/ index.js
import React from 'react';
import * as ReactDOMClient from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { store } from './store';

const container = document.getElementById('root');
// Create a root.
const root = ReactDOMClient.createRoot(container);

root.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider store={store}>
        <App />
      </Provider>
    </BrowserRouter>
  </React.StrictMode>
);
src/ store.js
import { configureStore } from '@reduxjs/toolkit';
import usersReducer from './features/userSlice';

export const store = configureStore({
  reducer: {
    users: usersReducer,
  },
});

参考サイト

CRUD Operation With React and Redux Toolkit
マークダウンをtailwind cssでスタイリングする
Building a Modal Using ReactJS and TailwindCSS

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0