-
Notifications
You must be signed in to change notification settings - Fork 1
[feat] add infinte scroll #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8b3a94f
622a3ea
c503a16
8ce5c9f
1c444d6
c0b7e0d
d87aa26
7f4250a
d5addda
cabd90c
48cf5e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,70 @@ | ||
| import React from 'react'; | ||
| import React, { useRef, useEffect, useState } from 'react'; | ||
| import { Droppable } from 'react-beautiful-dnd'; | ||
| import { ColumnData } from '@/types/dashboardView'; | ||
| import { Columns } from '@/types/dashboardView'; | ||
| import Button from '@/components/Button'; | ||
| import Image from 'next/image'; | ||
| import Card from './Card'; | ||
| import useModalStore from '@/store/modalStore'; | ||
| import CreateTaskModal from './CreateTaskModal'; | ||
| import styles from './Column.module.css'; | ||
|
|
||
| function Column({ color, title, totalCount, id, items }: ColumnData) { | ||
| function Column({ | ||
| color, | ||
| title, | ||
| totalCount, | ||
| id, | ||
| items, | ||
| loadMoreData, | ||
| }: Columns) { | ||
| const observerRef = useRef<IntersectionObserver | null>(null); | ||
| const loadMoreRef = useRef<HTMLDivElement | null>(null); | ||
| const columnRef = useRef<HTMLDivElement | null>(null); | ||
| const [minHeight, setMinHeight] = useState<string>('auto'); | ||
| const { openModal } = useModalStore(); | ||
|
|
||
| useEffect(() => { | ||
| observerRef.current = new IntersectionObserver( | ||
| (entries) => { | ||
| const entry = entries[0]; | ||
| if (entry.isIntersecting) { | ||
| loadMoreData(id); | ||
| } | ||
| }, | ||
| { | ||
| root: document.querySelector('.scrollContext'), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. μ€.. μ΄κ±° styles.scrollContext μ΄λ κ² ν΄λμ€λͺ
λ£μ΄λ²λ¦¬λ©΄ λ λλ§λ html보면 ν΄μκ° μ΄μ©κ³ λ‘ λ°λλλ°
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. root: ~λ DOM λ
Έλλ‘ μ€μ νλ©΄ λλ€κ³ ν΄μ μ λ κ² λ°λ‘ λ£μκ³ , |
||
| threshold: 0.4, | ||
| } | ||
| ); | ||
|
|
||
| const loadMoreElement = loadMoreRef.current; | ||
|
|
||
| if (totalCount === 0) return; | ||
|
|
||
| if (loadMoreElement && observerRef.current && totalCount >= 10) { | ||
| observerRef.current.observe(loadMoreElement); | ||
| } | ||
|
|
||
| return () => { | ||
| if (loadMoreElement && observerRef.current) { | ||
| observerRef.current.unobserve(loadMoreElement); | ||
| } | ||
| }; | ||
| }, [id, loadMoreData]); | ||
|
Comment on lines
+51
to
+52
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. totalCountκ° λλ½λ κ² κ°μλ° μλ νμ 건κ°μ?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ν μ¬μμ± λ§κ³ , μ΄κ±° μΆκ°ν΄μ λΆκ° 쑰건λ μΆκ°ν΄λ³Όκ²μ. |
||
|
|
||
| useEffect(() => { | ||
| if (items.length === 0 || items.length <= 5) { | ||
| setMinHeight('866px'); | ||
| } else { | ||
| setMinHeight('auto'); | ||
| } | ||
| }, [items]); | ||
|
|
||
| const handleCreateTask = () => { | ||
| openModal(<CreateTaskModal />); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className={styles.column}> | ||
| <div className={styles.column} ref={columnRef}> | ||
| <div className={styles.header}> | ||
| <div className={styles.status}> | ||
| <div style={{ background: color }} className={styles.dot}></div> | ||
|
|
@@ -52,26 +100,32 @@ function Column({ color, title, totalCount, id, items }: ColumnData) { | |
| </Button> | ||
| </div> | ||
|
|
||
| <Droppable | ||
| droppableId={`${id}`} | ||
| isDropDisabled={false} | ||
| isCombineEnabled={false} | ||
| ignoreContainerClipping={true} | ||
| direction="vertical" | ||
| > | ||
| {(provided) => ( | ||
| <div | ||
| ref={provided.innerRef} | ||
| {...provided.droppableProps} | ||
| className={styles.dropContext} | ||
| > | ||
| {items.map((item, index) => | ||
| item ? <Card key={item.id} item={item} index={index} /> : null | ||
| )} | ||
| {provided.placeholder} | ||
| </div> | ||
| )} | ||
| </Droppable> | ||
| <div className={styles.scrollContext}> | ||
| <Droppable | ||
| droppableId={`${id}`} | ||
| isDropDisabled={false} | ||
| isCombineEnabled={false} | ||
| ignoreContainerClipping={true} | ||
| direction="vertical" | ||
| > | ||
| {(provided) => ( | ||
| <div | ||
| className={styles.dropContext} | ||
| ref={provided.innerRef} | ||
| {...provided.droppableProps} | ||
| style={{ minHeight }} | ||
| > | ||
| {items.map((item, index) => | ||
| item ? <Card key={item.id} item={item} index={index} /> : null | ||
| )} | ||
|
|
||
| {provided.placeholder} | ||
|
|
||
| <div ref={loadMoreRef} style={{ height: '1px' }} /> | ||
| </div> | ||
| )} | ||
| </Droppable> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,14 +2,14 @@ import { useState, useEffect, useCallback } from 'react'; | |
| import debounce from 'lodash/debounce'; | ||
| import axiosInstance from '@/lib/axiosInstance'; | ||
| import { DropResult } from 'react-beautiful-dnd'; | ||
| import { ColumnData, CardData } from '@/types/dashboardView'; | ||
| import { Columns, Cards } from '@/types/dashboardView'; | ||
| import { COLUMN_URL, CARD_URL } from '@/constants/urls'; | ||
|
|
||
| export default function useDashBoardView(dashboardId: string | undefined) { | ||
| const [columns, setColumns] = useState<ColumnData[]>([]); | ||
| const [columns, setColumns] = useState<Columns[]>([]); | ||
| const [loading, setLoading] = useState<boolean>(false); | ||
| const [error, setError] = useState<string | null>(null); | ||
| const [cursor, setCursor] = useState<string | null>(null); | ||
| const [cursors, setCursors] = useState<Record<number, number | null>>({}); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. μ½λ λλ΅μ μΌλ‘ λ³Έ κ² κ°μλ° columsλ cursors κ°μ΄ λ°μ νκ² κ΄λ ¨μλ μ λ€μ |
||
|
|
||
| const fetchData = useCallback(async () => { | ||
| setLoading(true); | ||
|
|
@@ -19,67 +19,58 @@ export default function useDashBoardView(dashboardId: string | undefined) { | |
| ); | ||
|
|
||
| const columns = columnData.data; | ||
|
|
||
| const columnIds: number[] = columns.map( | ||
| (column: ColumnData) => column.id | ||
| ); | ||
| const columnIds: number[] = columns.map((column: Columns) => column.id); | ||
|
|
||
| const cardRequests = columnIds.map((columnId) => | ||
| axiosInstance.get( | ||
| `${CARD_URL}?size=10&columnId=${columnId}&cursor=${cursor || ''}` | ||
| ) | ||
| axiosInstance.get(`${CARD_URL}?size=10&columnId=${columnId}`) | ||
| ); | ||
|
|
||
| const cardResponses = await Promise.all(cardRequests); | ||
| console.log(cardResponses[0].data.totalCount); | ||
|
|
||
| const updatedColumns = columns.map( | ||
| (column: ColumnData, index: number) => { | ||
| const cardData = cardResponses[index].data.cards; | ||
| const totalCount = cardResponses[index].data.totalCount; | ||
| return { | ||
| ...column, | ||
| items: cardData || [], | ||
| totalCount: totalCount || 0, | ||
| }; | ||
| } | ||
|
|
||
| const updatedColumns = columns.map((column: Columns, index: number) => { | ||
| const cardData = cardResponses[index].data.cards; | ||
| const totalCount = cardResponses[index].data.totalCount; | ||
|
|
||
| return { | ||
| ...column, | ||
| items: cardData || [], | ||
| totalCount: totalCount || 0, | ||
| }; | ||
| }); | ||
|
|
||
| const initialCursors = updatedColumns.reduce( | ||
| (acc: Record<number, number | null>, column: Columns) => { | ||
| const lastCard = column.items[column.items.length - 1]; | ||
| acc[column.id] = lastCard ? lastCard.id : null; | ||
| return acc; | ||
| }, | ||
| {} as Record<number, number | null> | ||
| ); | ||
|
|
||
| setColumns(updatedColumns); | ||
| setCursor(cardResponses[0].data.cursor); | ||
| } catch (err: unknown) { | ||
| setCursors(initialCursors); | ||
| } catch (err) { | ||
| if (err instanceof Error) setError(err.message); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }, [dashboardId, cursor]); | ||
| //μ²΄ν¬ | ||
| }, [dashboardId]); | ||
|
|
||
| useEffect(() => { | ||
| console.log('λ λλ§ μ²΄ν¬'); | ||
| if (!dashboardId) return; | ||
|
|
||
| fetchData(); | ||
| }, [fetchData, dashboardId, cursor]); | ||
| //μ²΄ν¬ | ||
| }, [fetchData, dashboardId]); | ||
|
|
||
| const sendCardUpdateRequest = useCallback( | ||
| debounce(async (cardId: string, updatedCardData: CardData) => { | ||
| debounce(async (cardId: string, updatedCardData: Cards) => { | ||
| try { | ||
| const response = await axiosInstance.put( | ||
| `${CARD_URL}/${cardId}`, | ||
| updatedCardData | ||
| ); | ||
| console.log('μ λμΉ΄λλ°μ΄ν°', updatedCardData); | ||
| if (response.status === 200 || response.status === 204) { | ||
| console.log('μΉ΄λκ° μ±κ³΅μ μΌλ‘ μ λ°μ΄νΈλμμ΅λλ€.'); | ||
| } | ||
| } catch (err: unknown) { | ||
| await axiosInstance.put(`${CARD_URL}/${cardId}`, updatedCardData); | ||
| } catch (err) { | ||
| if (err instanceof Error) setError(err.message); | ||
| } | ||
| }, 200), | ||
| [] | ||
| ); | ||
| //μ²΄ν¬ | ||
|
|
||
| const handleOnDragEnd = useCallback( | ||
| async (result: DropResult) => { | ||
|
|
@@ -126,24 +117,74 @@ export default function useDashBoardView(dashboardId: string | undefined) { | |
| updatedColumns[sourceColumnIndex].totalCount -= 1; | ||
| updatedColumns[destinationColumnIndex].totalCount += 1; | ||
|
|
||
| console.log(updatedColumns[sourceColumnIndex].totalCount); | ||
| try { | ||
| await sendCardUpdateRequest(`${removed.id}`, { | ||
| ...removed, | ||
| columnId: removed.columnId, | ||
| }); | ||
| } catch (err) { | ||
| if (err instanceof Error) setError(err.message); | ||
| } | ||
| } | ||
|
|
||
| const updatedCursors = updatedColumns.reduce( | ||
| (acc, column) => { | ||
| const lastCard = column.items[column.items.length - 1]; | ||
| acc[column.id] = lastCard ? lastCard.id : null; | ||
| return acc; | ||
| }, | ||
| {} as Record<number, number | null> | ||
| ); | ||
|
|
||
| setColumns(updatedColumns); | ||
| setCursors(updatedCursors); | ||
| }, | ||
| [columns, sendCardUpdateRequest] | ||
| ); | ||
|
|
||
| const loadMoreData = useCallback( | ||
| debounce(async (columnId: number) => { | ||
| try { | ||
| await sendCardUpdateRequest(`${removed.id}`, { | ||
| ...removed, | ||
| columnId: removed.columnId, | ||
| }); | ||
| } catch (err: unknown) { | ||
| const currentCursor = cursors[columnId]; | ||
|
|
||
| if (currentCursor === null) return; | ||
|
|
||
| const { data: cardResponses } = await axiosInstance.get( | ||
| `${CARD_URL}?size=10&cursorId=${currentCursor}&columnId=${columnId}` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. size κ°λ μμλ‘ λΉΌμ κ΄λ¦¬νλ©΄ μ’μκ²κ°μμ |
||
| ); | ||
|
|
||
| const newCards = cardResponses.cards; | ||
| const newCursorId = cardResponses.cursorId; | ||
|
|
||
| setColumns((prevColumns) => | ||
| prevColumns.map((column) => | ||
| column.id === columnId | ||
| ? { | ||
| ...column, | ||
| items: [ | ||
| ...column.items, | ||
| ...newCards.filter( | ||
| (newCard: Cards) => | ||
| !column.items.some( | ||
| (prevCard) => prevCard.id === newCard.id | ||
| ) | ||
| ), | ||
| ], | ||
| } | ||
| : column | ||
| ) | ||
| ); | ||
|
|
||
| setCursors((prevCursors) => ({ | ||
| ...prevCursors, | ||
| [columnId]: newCursorId || null, | ||
| })); | ||
| } catch (err) { | ||
| if (err instanceof Error) setError(err.message); | ||
| } | ||
| }, | ||
| [columns, sendCardUpdateRequest] | ||
| }, 200), | ||
| [cursors] | ||
| ); | ||
|
|
||
| //μ²΄ν¬ | ||
|
|
||
| return { columns, loading, error, handleOnDragEnd }; | ||
| return { columns, loading, error, handleOnDragEnd, loadMoreData }; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
μ»΄ν¬λνΈκ° λ λλ§ λ λλ§λ€ IntersectionObserver κ°μ²΄κ° μλ‘ μμ±λλκ² λ§λμ?
μλ λκ² μλλΌλ©΄
μ΄λ°μμΌλ‘ λ§μ μ£Όλκ²μ μΆμ²λλ €μ!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
μλ‘ μμ±λλ κ² λ§μ΅λλ€ !
μκ°ν΄λ³΄λ©΄ μ»¬λΌ μμ²΄κ° λ¦¬λ λλ§μ΄ μ¦μ κ±° κ°μΌλ μ§μλ λ§μμ΄ λ§λ κ² κ°μμ! μμ ν κ²μ!