[React] CSS Styled Components: fixing first-child last-child problem

Problem

When your last-child, first-child css condition doesn't work, we need to check html tags inside React code!

For example, my original code (like below) was causing such problem.

ํŠน์ • ์ปดํฌ๋„ŒํŠธ ๋ฆฌ์ŠคํŠธ์˜ first-child, last-child ํŠน์ • css ์กฐ๊ฑด์ด ๋จนํžˆ์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๋ฆฌ์•กํŠธ ์ฝ”๋“œ์˜ html tag ๋“ค์„ ๊ฒ€ํ† ํ•ด๋ณด๋Š” ๊ฒŒ ์ข‹๋‹ค. ์•„๋ž˜๋Š” ์˜ˆ์‹œ์ฝ”๋“œ์ด๋‹ค.

Code Example

import styled from 'styled-components'
import React from 'react'

const Name = styled.div`
  margin-bottom: 10px;

  &: last-child {
    margin-bottom: 0;
  }
`

<ExampleTable>
  <>
  {/* item: { data: ['annie', 'bianca', 'cecilia'] }   */}
    {item.data.length && item.data.map((value, index) => {
      return (
        <>
          <Name>
            {value}
          </Name>
        </>
      )
    })}
  </>
</ExampleTable>

Analysis

  • I wanted all <Name> components to have bottom margin 10px, but wanted to give 0 bottom margin to <Name> tag's last-child.
  • But my current code (above) does not have wrapper tag for several <Name> components. Fyi, <> </> doesn't really do anything.
  • This means your html and css cannot recognize which one is the first-child or last-child of <Name> component list.

[๋ฒˆ์—ญ]

  • <Name> ์ปดํฌ๋„ŒํŠธ ๋ฆฌ์ŠคํŠธ์˜ ๋งˆ์ง€๋ง‰ ์ปดํฌ๋„ŒํŠธ์—๋งŒ ๋งˆ์ง„์„ ๋‹ค๋ฅด๊ฒŒ ์ฃผ๊ณ  ์‹ถ์—ˆ์œผ๋‚˜, ์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋“ค์„ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด์ฃผ๋Š” html tag ๊ฐ€ ์—†๋‹ค. <> </> ์ด๋ ‡๊ฒŒ ๋นˆ ํƒœ๊ทธ๋Š” wrapper ์—ญํ• ์„ ํ•ด์ฃผ์ง€ ๋ชปํ•œ๋‹ค.
  • ์ด๋Ÿฐ ์ƒํ™ฉ์ด๋ฉด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋‹จ๋…์ ์œผ๋กœ ๊ตฌ๋ณ„๋˜์ง€ ์•Š์•„์„œ, ์–ด๋–ค ๊ฒƒ์ด ์ฒซ๋ฒˆ์งธ์ด๊ณ  ๋งˆ์ง€๋ง‰์ธ์ง€ html css๊ฐ€ ์•Œ์ง€ ๋ชปํ•œ๋‹ค.

Solution 1: make last-child, first-child css conditions work | css ์กฐ๊ฑด ์‚ด๋ฆฌ๊ธฐ

<ExampleTable>
  <div> {/* change this!! */}
    {item.data.length && item.data.map((value, index) => {
      return (
        <> {/* do not make this as a div */}
          <Name>
            {value}
          </Name>
        </>
      )
    })}
  </div>
</ExampleTable>
  • Make a wrapper div for list of <Name> components.
  • Do not make <> </> as a div tag inside return. This will make html like this.

<div><Name /></div>
<div><Name /></div>
  • This causes problem because each <Name> component becomes both first-child & last-child inside the div. This is not what you want.
  • ๋งŒ์•ฝ return ์•ˆ์˜ ๋นˆ ํƒœ๊ทธ๋ฅผ div๋กœ ๋ฐ”๊ฟ”๋ฒ„๋ฆฌ๋ฉด, ์œ„์˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ๊ฐ <Name> ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•˜๋‚˜์˜ ๋ฆฌ์ŠคํŠธ ์•ˆ์— ์žˆ์ง€ ์•Š๊ฒŒ ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด first-child, last-child ๊ตฌ๋ถ„์„ ํ•  ์ˆ˜๊ฐ€ ์—†๋‹ค. ๋ชจ๋“  ์•„์ด๋“ค์ด first ์ด์ž last ๊ฐ€ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

Solution 2: add key index | ์ปดํฌ๋„ŒํŠธ์— ํ‚ค ๊ฐ’, ์ธ๋ฑ์Šค ๊ฐ’ ์ถ”๊ฐ€ํ•˜๊ธฐ

On top of solution 1, you can add one more thing.

In the example code, it creates the list of components because we are using array of data. To make your code clean, you might want to create key or index for iterating items inside return code.

Because you will see annoying warnings like this in a console.

react js console warning

But you don't want to make outer tag of <Name> component as div tag. We already talked above that it will mess up your css.

As alternatives, we have two possible solutions.

  1. We can just delete empty tag <> </>.

Just delete empty tag, and give key to <Name> component.

// ...
  return (
    <Name key={index}>
      {value}
    </Name>
  )
// ...
  1. For some reason, if you need to keep it, we can use React.Fragment.
A common pattern in React is for a component to return multiple elements. 
Fragments let you group a list of children without adding extra nodes to the DOM.

- quote from official REACT Docs.

Here is example code of React.Fragment.

// ...
  return (
    <React.Fragment key={index}> {/* change this!! */}
      <Name>
        {value}
      </Name>
    </React.Fragment>
  )
// ...

This way, you wouldn't mess up first-child or last-child css conditions AND have a key for each child component.

๋ฆฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ์ด๊ธฐ์— ์–ด๋ ˆ์ด๋ฅผ ๋Œ๊ฒŒ ๋˜๋ฉด ๊ฐ ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ์— key, index ๊ฐ’์„ ์ฃผ๋ผ๋Š” ์ฝ˜์†” ์›Œ๋‹์ด ๋œฐ ๊ฒƒ์ด๋‹ค. ์ด ์›Œ๋‹์„ ์—†์• ๋ ค๋ฉด 1๋ฒˆ์ฒ˜๋Ÿผ ๋นˆ ํƒœ๊ทธ <></>๋ฅผ ์•„์˜ˆ ์‚ญ์ œํ•˜๊ณ  <Name> ์ปดํฌ๋„ŒํŠธ์— ํ‚ค ๊ฐ’์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค.

ํ˜น์‹œ ์–ด๋–ค ๋‹ค๋ฅธ ์กฐ๊ฑด๋•Œ๋ฌธ์— <Name>์„ ๊ฐ์‹ธ๋Š” ํƒœ๊ทธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด, 2๋ฒˆ์ฒ˜๋Ÿผ React.Fragment ๋กœ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค. ์œ„์—์„œ ์„ค๋ช…ํ–ˆ๋“ฏ์ด, ์—ฌ๊ธฐ์— div ํƒœ๊ทธ๋ฅผ ์จ์„œ ์ถ”๊ฐ€์ ์ธ ๋…ธ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํ”ผํ•ด์•ผ ํ•œ๋‹ค.

Conclusion

Let's not forget to wrap same components inside a single div tag to distinguish first-child and last-child of css. Hope this helped you solve a problem.

๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ ์• ๋“ค๋ผ๋ฆฌ ํ•œ div ์•ˆ์— ์ž˜ ๋ฌถ์—ฌ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ ์žŠ์ง€ ์•Š๊ธฐ!

๏ฟผ

Reference