개발자로 전향중

리액트 3주차 버킷리스트 만들기 연습 2022-01-26 본문

React

리액트 3주차 버킷리스트 만들기 연습 2022-01-26

hovinee 2022. 1. 26. 19:43

db에 연결!

My bucketlist!

설치 프로그램

 

App.js

import { Route, Routes } from "react-router-dom";
import React from "react";
// BucketList 컴포넌트를 import 해옵니다.
// import [컴포넌트 명] from [컴포넌트가 있는 파일경로];
import BucketList from "./BucketList";
import styled from "styled-components";
import Detail from "./detail";
import {useDispatch} from "react-redux";
import {createBucket, loadBucketFB, addBucketFB} from "./redux/modules/bucket"
import Progress from "./Progress";
// import {db} from "./firebase"


function App() {
  
  
  const text = React.useRef(null);
  
  const dispatch = useDispatch();
  
  React.useEffect(() => {
    dispatch(loadBucketFB());
  }, [])

  const addBucketList = () => {
    dispatch(addBucketFB({text: text.current.value, completed: false}));
    // dispatch(createBucket({text: text.current.value, completed: false}));
  };
  
  return (
    <div className="App">
      <Container>
        <Title>내 버킷리스트</Title>
        <Progress/>
        <Line />
        {/* 컴포넌트를 넣어줍니다. */}
        {/* <컴포넌트 명 [props 명]={넘겨줄 것(리스트, 문자열, 숫자, ...)}/> */}

        <Routes>
          <Route path="/" element={<BucketList />} />
          <Route path="/detail:index" element={<Detail />} />
        </Routes>

      </Container>
      {/* 인풋박스와 추가하기 버튼을 넣어줬어요. */}
      <Input>
        <input type="text" ref={text} />
        <button onClick={addBucketList}>추가하기</button>
      </Input>
      <button onClick={() => {
        window.scrollTo({top:0, left:0, behavior: "smooth"});
      }}>위로 가기</button>
    </div>
  );
}

const Input = styled.div`
  max-width: 350px;
  min-height: 10vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
  display: flex;
  & > * {
    padding : 5px;
  }

  & input{
    border: 1px solid #888;
    width: 70%;
    margin-right: 10px;
  }

  & input:focus {
    border: 1px solid #a673ff;
    outline: none;
  }
  
  & button {
    width: 25%;
    color: #fff;
    border: #a673ff;
    background: #a673ff;
  }
`;

const Container = styled.div`
  max-width: 350px;
  min-height: 60vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
`;

const Title = styled.h1`
  color: slateblue;
  text-align: center;
`;

const Line = styled.hr`
  margin: 16px 0px;
  border: 1px dotted #ddd;
`;

export default App;

 

BucketList.js

 

import React from "react";
import styled from "styled-components";
import { useNavigate } from "react-router-dom";
import {useSelector} from "react-redux"


const BucketList = (props) => {
    let navigate = useNavigate();
    // const my_lists = props.list;
    const my_lists = useSelector((state) => state.bucket.list);
    
    return (
        <ListStyle>
            {my_lists.map((list, index) => {
                return (
                    <ItemStyle
                        completed={list.completed}
                        className="list_item"
                        key={index}
                        onClick={() => {
                            navigate("/detail"+index);
                        }}
                    >
                        {list.text}
                    </ItemStyle>
                );
            })}
        </ListStyle>
    );
};

const ListStyle = styled.div`
    display: flex;
    flex-direction: column;
    height: 50vh;
    overflow-x: hidden;
    overflow-y: auto;
    max-height: 50vh;
`;

const ItemStyle = styled.div`
    padding: 16px;
    margin: 8px;
    color: ${(props) => props.completed? "#fff": "#333"};
    background-color: ${(props) => (props.completed ? "#673ab7" : "aliceblue")};
    `;

export default BucketList;

 

detail.js

 

import React from "react";
import { useNavigate } from "react-router-dom";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux"
// 데이터가져올때
import { useDispatch } from "react-redux";
import { deleteBucket, updateBucket, updateBucketFB, deleteBucketFB} from "./redux/modules/bucket";

const Detail = (props) => {
    const dispatch = useDispatch();
    let navigate = useNavigate();
    const params = useParams();
    const bucket_index = params.index;
    const bucket_list = useSelector((state) => state.bucket.list);

    return (
        <div>
            <h1 onClick={() => {
                navigate("/")
            }}>{bucket_list[bucket_index] ? bucket_list[bucket_index].text : ""}</h1>
            <button onClick={() => {
                // dispatch(updateBucket(bucket_index));
                dispatch(updateBucketFB(bucket_list[bucket_index].id))
                navigate("/")
            }}>완료하기</button>
            <button onClick={() => {
                // dispatch(deleteBucket(bucket_index));
                dispatch(deleteBucketFB(bucket_list[bucket_index].id));
                navigate("/")
            }}>삭제하기</button>
        </div>
    )


};

export default Detail;

 

projess.js

 

import React from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";

const Progress = () => {
    const bucket_list = useSelector((state) => state.bucket.list)

    let count = 0;
    bucket_list.map((b, idx) => {
        if (b.completed) {
            count++;
        }
    })

    return (
        <ProgressBar>
            <HighLight width={(count/bucket_list.length) * 100 + "%"} />
            <Dot/>
        </ProgressBar>

    )
}

const ProgressBar = styled.div`
    background: #eee;
    width: 100%;
    height: 20px;
    display: flex;
    align-items: center;
    border-radius: 10px;
`;
// display는 하이라이라트와 닷을 겹치게 하기위해
const HighLight = styled.div`
    background: #673ab7;
    transition: 1s;
    width: ${(props) => props.width};
    height: 20px;
    border-radius: 10px;
`;
const Dot = styled.div`
    width: 40px;
    height: 40px;
    background: #fff;
    border: 5px solid #673ab7;
    border-radius: 40px;
    margin-left: -20px;
`;
export default Progress;

 

.redux/modules/bucket.js

 

import {db} from "../../firebase"
import {
  collection,
  doc,
  getDoc,
  getDocs,
  addDOc,
  updateDoc,
  deleteDoc,
  addDoc,
} from "firebase/firestore"
import { async } from "@firebase/util";

// Actions
const LOAD = "bucket/LOAD";
const CREATE = "bucket/CREATE";
const DELETE = "bucket/DELETE";
const UPDATE = "bucket/UPDATE";

// 초기 상태값을 만들어줍니다.
const initialState = {
  list: [],
  // list: ["영화관 가기", "매일 책읽기", "수영 배우기"],

};


// 액션 생성 함수예요.
// 액션을 만들어줄 함수죠!

export function loadBucket(bucket_list) {
  return { type: LOAD, bucket_list };
};

export function createBucket(bucket) {
  return { type: CREATE, bucket: bucket };
}

export function updateBucket(bucket_index) {
  return { type: UPDATE, bucket_index };
}

export function deleteBucket(bucket_index) {
  return { type: DELETE, bucket_index };
}

// middlewares
export const loadBucketFB = () => {
  return async function (dispatch) {
    const bucket_data = await getDocs(collection(db, "bucket"));
    let bucket_list = [];
    bucket_data.forEach((b) => {
        bucket_list.push({id:b.id, ...b.data()});
    });
    dispatch(loadBucket(bucket_list));
  }
}

export const addBucketFB = (bucket) => {
  return async function (dispatch) {
    const docRef = await addDoc(collection(db, "bucket"), bucket);
    const bucket_data = {id: docRef.id, ...bucket};

    dispatch(createBucket(bucket_data));
  }
}

export const updateBucketFB = (bucket_id) => {
  return async function (dispatch, getState) {
    const docRef = doc(db, "bucket", bucket_id);
    await updateDoc(docRef, {completed: true});
    
    const _bucket_list = getState().bucket.list;
    const bucket_index = _bucket_list.findIndex((b) => {
      return b.id === bucket_id;
    })
    
    dispatch(updateBucket(bucket_index));
  }
}

export const deleteBucketFB = (bucket_id) => {
  return async function (dispatch, getState) {
    if(!bucket_id){
      window.alert("아이디가 없네요")
      return;
    }
    const docRef = doc(db, "bucket", bucket_id);
    await deleteDoc(docRef);

    const _bucket_list = getState().bucket.list;
    const bucket_index = _bucket_list.findIndex((b) => {
      return b.id === bucket_id;
    })

    dispatch(deleteBucket(bucket_index));
  }
}

// 리듀서예요.
// 실질적으로 store에 들어가 있는 데이터를 변경하는 곳이죠!
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    
    case "bucket/LOAD":
      return { list: action.bucket_list };
   
    case "bucket/CREATE": {
      const new_bucket_list = [...state.list, action.bucket];
      return { list: new_bucket_list };
    }

    case "bucket/UPDATE": {
   
      const new_bucket_list = state.list.map((l, idx) => {
        if(parseInt(action.bucket_index) === idx) {
          return {...l, completed: true};
        } else {
          return l
        }
      })
      return {list: new_bucket_list}
    }

    case "bucket/DELETE": {
      const new_bucket_list = state.list.filter((l, idx) => {
        return parseInt(action.bucket_index) !== idx;
      });

      return { list: new_bucket_list };
    }
    default:
      return state;
  }
}

 

.redux/configStore.js

 

import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import bucket from "./modules/bucket";
import thunk from "redux-thunk"

const middlewares = [thunk];
// root 리듀서를 만들어줍니다.
// 나중에 리듀서를 여러개 만들게 되면 여기에 하나씩 추가해주는 거예요!
const rootReducer = combineReducers({ bucket });
const enhancer = applyMiddleware(...middlewares)

// 스토어를 만듭니다.
const store = createStore(rootReducer, enhancer);

export default store;
 
 
 
firebase.js
 
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "AIzaSyCZDU77aDlBAOJd77H7VMbkTQ59hZrfRno",
  authDomain: "sparta-bucket-cdb0b.firebaseapp.com",
  projectId: "sparta-bucket-cdb0b",
  storageBucket: "sparta-bucket-cdb0b.appspot.com",
  messagingSenderId: "921157462850",
  appId: "1:921157462850:web:2a41da30dbbfb4466fb6db",
  measurementId: "G-GQS1WDBR2W"
};

initializeApp(firebaseConfig);
// Initialize Firebase
// const app = initializeApp(firebaseConfig);


export const db = getFirestore();