[react]第5週開発ログ
スパティンエンコーディングクラブ「フロントの花、反応」の5週間の講座を聞いた後に書いた文章です.
まず、非同期通信に必要なミドルウェアをインストールします.
yarn add redux-thunk redux-thunk:オブジェクトではなくアクション作成関数を作成できます. configStore.js Firestoreアプリケーション順序
1)ロード時にデータをインポートする(Firebaseと通信する関数を作成し減速機を修正する)
2)createにFirebaseを適用する(Firebaseと通信する関数を作成し、減速機を修正→読み込みと書き込み)
3)prestoreをupdateに適用する
4)delete にFirestoreを適用する bucket.js
インストール:糸add@material-ui/core@material-ui/アイコン material-ui Detailを使用jsページボタン を変更 Detail.js でリフレッシュすると、まずredoxに格納された偽データが表示されます.
→Firestoreのデータだけを正しく表示したい場合は、データを読み込む前にページを隠します. Spinner.js App.js 43(簡易ストレージサービス)スイート:イメージまたはファイルを格納するストレージサービス. 43のパケットを作成する設定した後、 を構築した後、構築フォルダ内のすべてのファイルをbuild bucketにアップロードします. ルーティング53に管理領域を作成した後、名前サーバをガビアに登録する(4つの順序/最後.登録を減算)
→レコード作成→マイドメインアドレス確認 へ
1)Firebase管理
修正する箇所:Spinner画面では自動スキップはできませんが、リフレッシュして次の画面に移動するには理由が分かりません.
Spinner.js redux/modules/rank.js Ranking.js モバイル結果画面
❗修正するポイント:
1)問題解決swifeにおける1回のswife挙動に2つの問題があるエラー
2)rankデータ値をFirebaseに正しく関連付ける
ReduxからFireStoreデータを交換する
まず、非同期通信に必要なミドルウェアをインストールします.
yarn add redux-thunk
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import bucket from "./modules/bucket";
import { createBrowserHistory } from "history";
export const history = createBrowserHistory();
const middlewares = [thunk];
const enhancer = applyMiddleware(...middlewares);
const rootReducer = combineReducers({ bucket });
const store = createStore(rootReducer, enhancer);
export default store;
1)ロード時にデータをインポートする(Firebaseと通信する関数を作成し減速機を修正する)
2)createにFirebaseを適用する(Firebaseと通信する関数を作成し、減速機を修正→読み込みと書き込み)
3)prestoreをupdateに適用する
4)delete
import { firestore } from "../../firebase";
const bucket_db = firestore.collection("bucket");
// Actions
const LOAD = "bucket/LOAD";
const CREATE = "bucket/CREATE";
const DELETE = "bucket/DELETE";
const UPDATE = "bucket/UPDATE";
const LOADED = "bucket/LOADED";
const initialState = {
list: [
{ text: "영화관 가기", completed: false },
{ text: "매일 책읽기", completed: false },
{ text: "기타 배우기", completed: false },
],
is_loaded: false,
};
// Action Creators
export const loadBucket = (bucket) => {
return { type: LOAD, bucket };
};
export const createBucket = (bucket) => {
// bucket = text
return { type: CREATE, bucket };
};
export const deleteBucket = (bucket) => {
return { type: DELETE, bucket };
};
export const updateBucket = (bucket) => {
return { type: UPDATE, bucket };
};
export const isLoaded = (loaded) => {
return { type: LOADED, loaded };
};
export const loadBucketFB = () => {
return function (dispatch) {
bucket_db.get().then((docs) => {
let bucket_data = [];
docs.forEach((doc) => {
if (doc.exists) {
bucket_data = [...bucket_data, { id: doc.id, ...doc.data() }];
}
});
console.log(bucket_data);
dispatch(loadBucket(bucket_data));
});
};
};
export const addBucketFB = (bucket) => {
return function (dispatch) {
let bucket_data = { text: bucket, completed: false };
dispatch(isLoaded(false));
bucket_db.add(bucket_data).then((docRef) => {
bucket_data = { ...bucket_data, id: docRef.id };
dispatch(createBucket(bucket_data));
dispatch(isLoaded(true));
});
};
};
export const updateBucketFB = (bucket) => {
return function (dispatch, getState) {
const _bucket_data = getState().bucket.list[bucket];
let bucket_data = { ..._bucket_data, completed: true };
if (!bucket_data.id) {
return;
}
bucket_db
.doc(bucket_data.id)
.update(bucket_data)
.then((docRef) => {
dispatch(updateBucket(bucket));
})
.catch((error) => {
console.log(error);
});
};
};
export const deleteBucketFB = (bucket) => {
return function (dispatch, getState) {
const _bucket_data = getState().bucket.list[bucket];
if (!_bucket_data.id) {
return;
}
bucket_db
.doc(_bucket_data.id)
.delete()
.then((docRef) => {
dispatch(deleteBucket(bucket));
})
.catch((error) => {
console.log(error);
});
};
};
// Reducer
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case "bucket/LOAD": {
if (action.bucket.length > 0) {
return { list: action.bucket, is_loaded: true };
}
return state;
}
case "bucket/CREATE": {
const new_bucket_list = [...state.list, action.bucket];
return { list: new_bucket_list };
}
case "bucket/DELETE": {
const bucket_list = state.list.filter((l, idx) => {
if (idx !== action.bucket) {
return l;
}
});
return { list: bucket_list };
}
case "bucket/UPDATE": {
const bucket_list = state.list.map((l, idx) => {
if (idx === action.bucket) {
return { ...l, completed: true };
} else {
return l;
}
});
return { list: bucket_list };
}
case "bucket/LOADED": {
return { ...state, is_loaded: action.loaded };
}
default:
return state;
}
}
material-uiの使用
インストール
import React from "react";
import Button from "@material-ui/core/Button";
import ButtonGroup from "@material-ui/core/ButtonGroup";
import { useSelector, useDispatch } from "react-redux";
import { updateBucketFB, deleteBucketFB } from "./redux/modules/bucket";
const Detail = (props) => {
const dispatch = useDispatch();
const bucket_list = useSelector((state) => state.bucket.list);
console.log(bucket_list, props);
const bucket_index = parseInt(props.match.params.index);
return (
<div>
<h1>{bucket_list[bucket_index].text}</h1>
<ButtonGroup>
<Button
color="secondary"
onClick={() => {
dispatch(deleteBucketFB(bucket_index));
props.history.goBack();
}}
>
삭제하기
</Button>
<Button
color="primary"
onClick={() => {
dispatch(updateBucketFB(bucket_index));
props.history.goBack();
}}
>
완료하기
</Button>
</ButtonGroup>
</div>
);
};
export default Detail;
Spinnerオフセット
→Firestoreのデータだけを正しく表示したい場合は、データを読み込む前にページを隠します.
import React from "react";
import styled from "styled-components";
import { Eco } from "@material-ui/icons";
const Spinner = (props) => {
return (
<Outter>
<Eco style={{ color: "#673ab7", fontSize: "150px" }} />
</Outter>
);
};
const Outter = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: #ede2ff;
`;
export default Spinner;
import React from "react";
import styled from "styled-components";
import { withRouter } from "react-router";
import { Route, Switch } from "react-router-dom";
import BucketList from "./BucketList";
import Detail from "./Detail";
import NotFound from "./NotFound";
import { connect } from "react-redux";
import { loadBucketFB, addBucketFB } from "./redux/modules/bucket";
import Progress from "./Progress";
import Spinner from "./Spinner";
import { firestore } from "./firebase";
const mapStateToProps = (state) => ({
bucket_list: state.bucket.list,
is_loaded: state.bucket.is_loaded,
});
const mapDispatchToProps = (dispatch) => {
return {
load: () => {
dispatch(loadBucketFB());
},
create: (new_item) => {
dispatch(addBucketFB(new_item));
},
};
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.text = React.createRef();
}
componentDidMount() {
this.props.load();
// const bucket = firestore.collection("bucket2");
// bucket.doc("bucket_item1").set({ text: "수영 배우기", completed: false });
// bucket
// .doc("bucket_item")
// .get()
// .then((doc) => {
// if (doc.exists) {
// console.log(doc);
// console.log(doc.data());
// console.log(doc.id);
// }
// console.log(doc.exists);
// });
// bucket.get().then((docs) => {
// let bucket_data = [];
// docs.forEach((doc) => {
// if (doc.exists) {
// bucket_data = [...bucket_data, { id: doc.id, ...doc.data() }];
// }
// });
// console.log(bucket_data);
// });
// // bucket.add({ text: "드럼 배우기", completed: false }).then((docRef) => {
// // console.log(docRef);
// // console.log(docRef.id);
// // });
// // bucket.doc("bucket_item").update({ text: "기타 배우기2" });
// bucket
// .doc("bucket_item2")
// .delete()
// .then((docRef) => {
// console.log("지웠어요!");
// });
}
addBucketList = () => {
const new_item = this.text.current.value;
this.props.create(new_item);
};
render() {
return (
<div className="App">
{!this.props.is_loaded ? (
<Spinner />
) : (
<React.Fragment>
<Container>
<Title>My BucketList</Title>
<Progress />
<Line />
<Switch>
<Route path="/" exact component={BucketList} />
<Route path="/detail/:index" component={Detail} />
<Route component={NotFound} />
</Switch>
</Container>
<Input>
<input type="text" ref={this.text} />
<button onClick={this.addBucketList}>추가하기</button>
</Input>
<button
onClick={() => {
window.scrollTo({ top: 0, left: 0, behavior: "smooth" });
}}
>
위로가기
</button>
</React.Fragment>
)}
</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;
align-items: center;
justify-content: center;
& > * {
padding: 10px;
}
& input {
border-radius: 5px;
margin-right: 10px;
border: 1px solid #5d5d5d;
width: 60%;
&:focus {
border: 1px solid #a673ff;
}
}
& button {
width: 25%;
color: #fff;
border-radius: 5px;
border: 1px solid #6799ff;
background-color: #6799ff;
}
`;
const Container = styled.div`
max-width: 350px;
min-height: 70vh;
background-color: #fff;
padding: 16px;
margin: 20px auto;
border-radius: 5px;
border: 1px solid #ddd;
`;
const Title = styled.h1`
color: #5587ed;
text-align: center;
`;
const Line = styled.hr`
margin: 16px 0px;
border: 1px dotted #ddd;
`;
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(App));
1.AWS 3パケットへの配備
yarn build
ドメイン接続
→レコード作成→マイドメインアドレス確認
2.Firebaseへの配備
1)Firebase管理
// cli 설치
yarn add global firebase-tools
// firebase에 로그인
yarn firebase login
// init 실행
yarn firebase init
2)ホスト管理を選択した後、// 빌드한 결과물 올리기
yarn firebase deploy
3)Firebaseダッシュボード-管理中にドメインに入り、画面を確認します.ドメインを移動した後の認証結果画面
修正する箇所:Spinner画面では自動スキップはできませんが、リフレッシュして次の画面に移動するには理由が分かりません.
[HW] FriendTest Project
import React from "react";
import styled from "styled-components";
import img from "./spinner.png";
const Spinner = (props) => {
return (
<Outter>
<img src={img} />
</Outter>
);
};
const Outter = styled.div`
background-color: #df402c88;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
& img {
width: 400px;
}
`;
export default Spinner;
import { firestore } from "../../firebase";
const rank_db = firestore.collection("rank");
// Actions
const ADD_USER_NAME = "rank/ADD_USER_NAME";
const ADD_USER_MESSAGE = "rank/ADD_USER_MESSAGE";
const ADD_RANK = "rank/ADD_RANK";
const GET_RANK = "rank/GET_RANK";
const IS_LOADED = "rank/IS_LOADED";
const initialState = {
user_name: "",
user_message: "",
user_score: "",
score_text: {
60: "우린 친구! 앞으로도 더 친하게 지내요!",
80: "우와! 우리는 엄청 가까운 사이!",
100: "우린 둘도 없는 단짝! :)",
},
ranking: [
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
{ score: 40, name: "최수빈", message: "안녕 포뇨!" },
],
is_loadad: false,
};
export const addUserName = (user_name) => {
return { type: ADD_USER_NAME, user_name };
};
export const addUserMessage = (user_message) => {
return { type: ADD_USER_MESSAGE, user_message };
};
export const addRank = (rank_info) => {
return { type: ADD_RANK, rank_info };
};
export const getRank = (rank_list) => {
return { type: GET_RANK, rank_list };
};
export const isLoaded = (loaded) => {
return { type: IS_LOADED, loaded };
};
export const addRankFB = (rank_info) => {
return function (dispatch) {
dispatch(isLoaded(false));
let rank_data = {
message: rank_info.message,
name: rank_info.name,
score: rank_info.score,
};
rank_db.add(rank_data).then((doc) => {
console.log(doc.id);
rank_data = { ...rank_data, id: doc.id, current: true };
dispatch(addRank(rank_data));
});
};
};
export const getRankFB = () => {
return function (dispatch) {
dispatch(isLoaded(false));
rank_db.get().then((docs) => {
let rank_data = [];
docs.forEach((doc) => {
console.log(doc.data());
rank_data = [...rank_data, { id: doc.id, ...doc.data() }];
});
dispatch(getRank(rank_data));
dispatch(isLoaded(true));
});
};
};
//Reducer
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case "rank/ADD_USER_NAME": {
return { ...state, user_name: action.user_name };
}
case "rank/ADD_USER_MESSAGE": {
return { ...state, user_message: action.user_message };
}
case "rank/ADD_RANK": {
return { ...state, ranking: [...state.ranking, action.rank_info] };
}
case "rank/GET_RANK": {
let ranking_data = [...state.ranking];
const rank_ids = state.ranking.map((r, idx) => {
return r.id;
});
console.log(rank_ids);
const rank_data_fb = action.rank_list.filter((r, idx) => {
if (rank_ids.indexOf(r.id) === -1) {
ranking_data = [...ranking_data, r];
}
});
console.log(ranking_data);
return { ...state, ranking: ranking_data };
}
case "rank/IS_LOADED": {
return { ...state, is_loaded: action.loaded };
}
default:
return state;
}
}
import React from "react";
import styled from "styled-components";
import { useSelector, useDispatch } from "react-redux";
import { resetAnswer } from "./redux/modules/quiz";
import { getRankFB } from "./redux/modules/rank";
import Spinner from "./Spinner";
const Ranking = (props) => {
const dispatch = useDispatch();
const _ranking = useSelector((state) => state.rank.ranking);
const is_loaded = useSelector((state) => state.rank.is_loaded);
const user_rank = React.useRef(null);
React.useEffect(() => {
dispatch(getRankFB());
if (!user_rank.current) {
return;
}
window.scrollTo({
top: user_rank.current.offsetTop,
left: 0,
behavior: "smooth",
});
}, []);
const ranking = _ranking.sort((a, b) => {
return b.score - a.score;
});
if (!is_loaded) {
return <Spinner />;
}
return (
<RankContainer>
<Topbar>
<p>
<span>{ranking.length}명</span>의 사람들 중 당신은?
</p>
</Topbar>
<RankWrap>
{ranking.map((r, idx) => {
if (r.current) {
return (
<RankItem key={idx} highlight={true} ref={user_rank}>
<RankNum>{idx + 1}등</RankNum>
<RankUser>
<p>
<b>{r.name}</b>
</p>
<p>{r.message}</p>
</RankUser>
</RankItem>
);
}
return (
<RankItem key={idx}>
<RankNum>{idx + 1}등</RankNum>
<RankUser>
<p>
<b>{r.name}</b>
</p>
<p>{r.message}</p>
</RankUser>
</RankItem>
);
})}
</RankWrap>
<Button
onClick={() => {
dispatch(resetAnswer());
window.location.href = "/";
}}
>
다시 하기
</Button>
</RankContainer>
);
};
const RankContainer = styled.div`
width: 100%;
padding-bottom: 100px;
`;
const Topbar = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100vw;
min-height: 50px;
border-bottom: 1px solid #ddd;
background-color: #fff;
& > p {
text-align: center;
}
& > p > span {
border-radius: 30px;
background-color: #fef5d4;
font-weight: 600;
padding: 4px 8px;
}
`;
const RankWrap = styled.div`
display: flex;
flex-direction: column;
width: 100%;
margin-top: 58px;
`;
const RankItem = styled.div`
width: 80vw;
margin: 8px auto;
display: flex;
border-radius: 5px;
border: 1px solid #ddd;
padding: 8px 16px;
align-items: center;
background-color: ${(props) => (props.highlight ? "#ffd6aa" : "#ffffff")};
`;
const RankNum = styled.div`
text-align: center;
font-size: 2em;
font-weight: 600;
padding: 0px 16px 0px 0px;
border-right: 1px solid #ddd;
`;
const RankUser = styled.div`
padding: 8px 16px;
text-align: left;
& > p {
&:first-child > b {
border-bottom: 2px solid #212121;
}
margin: 0px 0px 8px 0px;
}
`;
const Button = styled.button`
position: fixed;
bottom: 5vh;
left: 0;
padding: 8px 24px;
background-color: ${(props) => (props.outlined ? "#ffffff" : "#dadafc")};
border-radius: 30px;
margin: 0px 10vw;
border: 1px solid #dadafc;
width: 80vw;
`;
export default Ranking;
❗修正するポイント:
1)問題解決swifeにおける1回のswife挙動に2つの問題があるエラー
2)rankデータ値をFirebaseに正しく関連付ける
Reference
この問題について([react]第5週開発ログ), 我々は、より多くの情報をここで見つけました https://velog.io/@tnqls1211v/React-week5-devlogテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol