개발자로 전향중

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

React

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

hovinee 2022. 1. 25. 15:33

My bucketlist!

설치 프로그램 styled.component, router,

 

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} from "./redux/modules/bucket"
import Progress from "./Progress";


function App() {

  // const [list, setList] = React.useState(["영화관 가기", "매일 책읽기", "수영 배우기"]);
  // 상태값이 동적으로 바뀔경우 hook usestate const [상태 값 저장 변수, 상태 값 갱신함수] = useState[상태초기값]
  const text = React.useRef(null);
  // const navigate = useNavigate();

  const dispatch = useDispatch();
  
  const addBucketList = () => {
    // 스프레드 문법! 기억하고 계신가요? :)
    // 원본 배열 list에 새로운 요소를 추가해주었습니다.
    // setList([...list, text.current.value]);

    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 } 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].text}</h1>
            <button onClick={() => {
                dispatch(updateBucket(bucket_index));
                navigate("/")
            }}>완료하기</button>
            <button onClick={() => {
                dispatch(deleteBucket(bucket_index));
                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)
    console.log(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

 

// 액션 타입을 정해줍니다.
const CREATE = "bucket/CREATE";
const DELETE = "bucket/DELETE";
const UPDATE = "bucket/UPDATE";

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

// 액션 생성 함수예요.
// 액션을 만들어줄 함수죠!
export function createBucket(bucket) {
  console.log("액션을 생성할거야!");
  return { type: CREATE, bucket: bucket };
}

export function updateBucket(bucket_index) {

  return { type: UPDATE, bucket_index };
}

export function deleteBucket(bucket_index) {
  console.log("지울 버킷 인덱스", bucket_index);
  return { type: DELETE, bucket_index };
}

// 리듀서예요.
// 실질적으로 store에 들어가 있는 데이터를 변경하는 곳이죠!
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case "bucket/CREATE": {
      console.log("이제 값을 바꿀거야!");
      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

 

//configStore.js
import { createStore, combineReducers } from "redux";
import bucket from "./modules/bucket";

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

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

export default store;