// dict.js
// Action Creators
export const loadDictFB = () => {
return function (dispatch) {
dict_db.get().then((docs) => {
let dict_data = [];
docs.forEach((doc) => {
if (doc.exists) {
dict_data = [...dict_data, {id: doc.id, created_at: doc._delegate._document.version.timestamp.seconds, ...doc.data()}]
// 파이어스토어의 특성 상 data id값에 의해 정렬되는데 나는 등록된 시간 순서에 따라 정렬하고 싶었음.
dict_data.sort(function(a, b) {
return a.created_at - b.created_at;
});
// data를 리덕스 스토어에 넣고 created_at에 의해 정렬될 수 있게 한다.
}
})
dispatch(loadDict(dict_data));
})
}
};
// List.js
...
const scrollTarget = useRef();
...
if (shouldScroll && scrollTarget.current) {
// scrollTarget.current 를 두번(리덕스 데이터 불러올때, 파이어스토어 데이터 불러올때) 불러오는데
// 리덕스 데이터때에는 scrollTarget.current 값이 없으므로 scrollTarget.current가 있을때만 scroll되도록 한다.
scrollTarget.current.scrollIntoView({behavior: 'smooth', block: 'end'})
}
<ListWrap ref={scrollTarget}></ListWrap>
// App.js의 Container 태그가 아니라 List.js의 ListWrap 태그에 ref를 걸어줘야 스크롤이 동작한다...
//
case "dict/DELETE": {
const new_dict_list = state.list.filter((l, idx) => {
return action.id !== l.id
})
return {...state, list: new_dict_list}
// 여기서 list의 value로는 spread 할당을 해주면 안됨. return {...state, list: [...state.list, ...new_dict_list]} 이런 식으로...
// 기존 list와 new_dict_list의 차이는 지워야할 객체가 있냐없냐인데
// ...state.list를 해버리면 지워야할 객체를 함께 담아버리는꼴임.
}
무한스크롤 동작에 관여된 파일은 FetchMore.js와 List.js이다.
// FetchMore.js
import React, { useRef, useEffect } from "react";
import "./FetchMore.css";
const FetchMore = ({ loading, setPage }) => {
const fetchMoreTrigger = useRef(null);
const fetchMoreObserver = new IntersectionObserver(([{ isIntersecting }]) => {
if (isIntersecting) setPage((page) => page + 5);
});
useEffect(() => {
const fetchMoreTrigger_current = fetchMoreTrigger.current;
fetchMoreObserver.observe(fetchMoreTrigger.current);
return () => {
fetchMoreObserver.unobserve(fetchMoreTrigger_current);
// 컴포넌트가 사라졌을때, observe를 떼어주는 동작인데, /write 페이지로 넘어가면 에러가 난다.
// 그냥 fetchMoreTrigger.current를 return에 사용하면 값이 바뀔 수 있다는 경고문구가 떴다.
// 그래서 useEffect 안에서 변수로 한번 할당해두고 그 변수를 적용하면 해결!
};
}, []);
return (
<div
id="fetchMore"
className={loading ? "loading" : ""}
ref={fetchMoreTrigger}
/>
);
};
export default FetchMore;
// List.js
...
import FetchMore from "./FetchMore";
...
const [page, setPage] = useState(0);
const [loading, setLoading] = useState(false);
useEffect(async () => {
setLoading(true);
dispatch(loadDictFB(page));
// page 변수가 변경되었음을 감지하고 콜백함수를 실행시킨다.
// 여기서는 loadDictFB(page)를 실행하여 원하는 구간의 데이터를 추가시킨다.
setLoading(false);
}, [page]);
...
<FetchMore loading={page !== 0 && loading} setPage={setPage} />
...
최초 로딩 시엔 리스트에 아무것도 없으므로, 저절로 FetchMore를 감지하고 page에 +5가 됨. 따라서 마운트 시 loadDictFB는 안써도 됨.
// App.js
const mapDispatchToProps = (dispatch) => ({
load: () => {
// dispatch(loadDictFB());
},
});
...
componentDidMount() {
// this.props.load(); // 컴포넌트 최초 렌더링 시 db 데이터를 받아옴
}
심화과정에서 해결할수 있다고하니 일단은 새로고침을 넣는다...
//Write.js
...
useEffect(() => {
if (props.match.path === "/write") {
dispatch(reload(true));
}
return;
}, []);
...
if (props.isEdit) {
...
} else {
...
dispatch(reload(false)); // 수정하기 기능에서는 roload가 true가 되면 쓸데없이 새로고침 되므로.
}
useEffect(() => {
if (reload) {
window.location.reload(); // /write 페이지로 갔다가 다시 돌아왔을때 렌더링에 문제가 있어서 임시로 새로고침 처리함.
dispatch(reload(false));
}
}, []);
야호~!