본문 바로가기

Web

[React.js + Firebase] react-router-dom NavLink 사용법 - 초간단 사진 게시판 만들기(4)

💡 이번 포스트에서는 styled-components와 react-router-dom의 NavLink를 활용해서 firestore의 documents들을 메뉴처럼 접근 할 수 있도록 하겠습니다.

이전 포스트를 참고 하시면 코드와 프로젝트에 대한 진행상황을 파악하시기 수월합니다.

 

[React.js + Firebase] react-router-dom 동적 라우팅 - 초간단 사진 게시판 만들기(3)

💡이번 포스트에서는 React에서 동적 라우팅을 사용하여 firebase의 데이터들을 원하는 url에 띄우는 방법을 알아 보도록 하겠습니다. 이전 포스트를 참고 하시면 코드와 프로젝트에 대한 진행상황

kmuhan-study.tistory.com


react-router-dom(v6)에서 NavLink를 사용하는 법은 다음과 같습니다.

<NavLink to="/path" />

기본적으로 Link와 사용법이 동일하며, to 뒤에 절대 경로로 작성해서 페이지를 리다이렉션합니다. NavLink와 Link와의 차이점은 다음과 같습니다.

<NavLink to="/path" className={({ isActive, isPending }) =>
    isPending ? "pending" : isActive ? "active" : ""
  } end >

isActive라는 속성을 가지며(설정하지 않으면 기본적으로 active라는 classname을 가지고 css로 접근 가능), className 속성으로 활성화 상태에 따라서 컴포넌트를 다양하게 조절할 수 있습니다.

isPending이라는 속성을 가지며(설정하지 않으면 접근 불가능), className 속성으로 Transitions(로딩 화면 같은 개념입니다.)의 작동 상태에 따라서 컴포넌트를 조절 할 수 있습니다. 
end는 to로 설정한 경로에 대해서 뒤에 파라미터가 추가로 붙으면, 정확하게 일치하지 않는 것으로 간주하고, isActive를 false로 처리합니다.

Link URL isActive
<NavLink to="/tasks" /> /tasks/123 true
<NavLink to="/tasks" end /> /tasks true
<NavLink to="/tasks" end /> /tasks/123 false
<NavLink to="/tasks" /> /tasks true

 

아래의 예시로 살펴보도록 하겠습니다.

Firestore Database

파이어베이스에 저장되어있는 데이터 구조입니다.

Category.js

import { NavLink, useParams } from "react-router-dom"
import { db } from "../firebase"
import { collection, getDocs, query } from "firebase/firestore"
import { useEffect, useState } from "react"
import styled from 'styled-components'

const Wrapper = styled.div`
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
`

const StyledNavLink = styled(NavLink)`
    margin: 10px;
    font-size: 30px;
    transition: 0.5s;
    
    &:hover {
        background-color: gray;
    }
`

function Category() {
    const { categoryId } = useParams()
    const [ categoryItems, setCategoryItems ] = useState()
    
    async function getDocuments(categoryId) {
        const categoryRef = query(collection(db, categoryId))
        const queryCategory = await getDocs(categoryRef)
        setCategoryItems(queryCategory.docs)
    }

    useEffect(() => {
        getDocuments(categoryId)
    }, [categoryId])

    return (
        <Wrapper>
            {categoryItems && categoryItems.map((doc) => (
                <StyledNavLink
                    to={`/${categoryId}/${doc.id}`}
                    key={doc.id}
                >
                    {doc.id}
                </StyledNavLink>
            ))}
        </Wrapper>
    )
}

export default Category

StyledNavLink는 NavLink에 styled-components를 적용시킨 컴포넌트입니다.

현재 categoryId = country로 접속해 있을 때, Korea를 클릭하면 /country/Korea로 다이렉션됩니다.

<StyledNavLink to={`/${categoryId}/${doc.id}`} key={doc.id}>
	{doc.id}
</StyledNavLink>

key = {doc.id} 🔦 State에 저장된 데이터를 map을 사용해서 return 안에서 뿌려주었기 때문에, 매핑되는 StyledNavLink마다 고유한 key값을 지정해 주지 않으면, react에서 경고문을 받게 됩니다.

링크를 클릭하면 상세 페이지로 넘어갈 수 있습니다.

 

하지만 /country/Korea로 다이렉션 되어도 해당 path에 Route 설정을 해주지 않는다면 원하는 페이지를 띄울 수 없을 것이고, 실제 프로덕션 되는 환경에서는 404Error를 받게 되겠죠?

 

Router.js

const Country = React.lazy(() => import("./pages/Country"))
<Route path="/country/:id" element={<Country/>} />

Country.js

import { useParams } from "react-router-dom"
import { db } from "../firebase"
import { doc, getDoc } from "firebase/firestore"
import { useEffect, useState } from "react"
import styled from 'styled-components'

const Wrapper = styled.div`
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
`
const Container = styled.div`
    display: flex;
    flex-direction: column;
`
const P = styled.p`
    font-size: ${props => props.fontSize || '20px'};
    margin: 10px;
`

function Country() {
    const { id } = useParams()
    const [ countryData, setCountryData ] = useState()
    
    async function getDocument(countryId) {
        const docRef = doc(db, "country", countryId)
        const docSnap = await getDoc(docRef)
        setCountryData(docSnap.data())
    }

    useEffect(() => {
        getDocument(id)
    }, [id])
    
    return (
        <Wrapper>
            {countryData !== undefined &&
            <Container>
                <P fontSize="30px">{id}</P>
                <P>Continet: {countryData.continent}</P>
                <P>Capital: {countryData.capital}</P>
                <P>Language: {countryData.language}</P>
            </Container>
            }
        </Wrapper>
    )
}

export default Country

Country.js 페이지를 추가 해 줍니다.

Animal.js 페이지도 Country.js와 동일한 구조로 추가 해 줍니다.

⏰ 주의 할 점
Country에 있는 속성들인 continent, capital, language
Animal에 있는 속성들인 species, sexual, type으로 바꿔주고,
firestore에서 쿼리 해 올 때의 참조도, country에서 animals로 바꿔줍니다.

Animal.js

import { useParams } from "react-router-dom"
import { db } from "../firebase"
import { doc, getDoc } from "firebase/firestore"
import { useEffect, useState } from "react"
import styled from 'styled-components'

const Wrapper = styled.div`
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
`
const Container = styled.div`
    display: flex;
    flex-direction: column;
`
const P = styled.p`
    font-size: ${props => props.fontSize || '20px'};
    margin: 10px;
`

function Animal() {
    const { id } = useParams()
    const [ countryData, setCountryData ] = useState()
    
    async function getDocument(countryId) {
        const docRef = doc(db, "animals", countryId)
        const docSnap = await getDoc(docRef)
        setCountryData(docSnap.data())
    }

    useEffect(() => {
        getDocument(id)
    }, [id])
    
    return (
        <Wrapper>
            {countryData !== undefined &&
            <Container>
                <P fontSize="30px">{id}</P>
                <P>Species: {countryData.species}</P>
                <P>Sexual: {countryData.sexual}</P>
                <P>Type: {countryData.type}</P>
            </Container>
            }
        </Wrapper>
    )
}

export default Animal

animal 페이지까지 확인할 수 있습니다.


지금까지 게시판의 목록들에 링크를 걸어서 동적 라우팅으로 문서에 대한 페이지를 생성 해 보았습니다. 포스팅에서는 firebase storage를 활용하여 각 문서에 대한 사진들을 업로드 하고, 사진을 띄우는 방법을 알아 보겠습니다.