Search code examples
reactjsparent-childstyled-componentsreact-bootstrapbootstrap-5

With Styled Components how to style parent based on child having aria-current page or a particular className?


Trying to style a parent div for a child's Link if the Link has the applied aria-current="page" or an active class name.

code (stripped):

<NavBody>
  {items.map((subItem, key) => {
    const { title, link } = subItem
    return (
      <NavLinkContainer key={key}>
        <NavLink
          onClick={closeMobileMenu}
          to={`/docs${link}`}
          activeClassName="active"
        >
          {title}
        </NavLink>
      </NavLinkContainer>
    )
  })}
</NavBody>

Styled Components:

const NavBody = styled(Accordion.Body)`
  margin-left: ${({ theme }) => theme.spacings.small};
  padding: 0 !important;
`

const NavLinkContainer = styled.div`
  padding-top: ${({ theme }) => theme.spacings.xSmall};
  padding-bottom: ${({ theme }) => theme.spacings.xSmall};
  border-left: 1px solid ${({ theme }) => theme.colors.color7};
  padding-left: ${({ theme }) => theme.spacings.small} !important;
`

const NavLink = styled(Link)`
  color: ${({ theme }) => theme.colors.color3};
  text-decoration: none;
  padding-bottom: ${({ theme }) => theme.spacings.xxSmall};

  &.active {
    color: ${({ theme }) => theme.colors.color1};
  }

  &:hover {
    color: inherit;
    opacity: 0.7;
    text-decoration: none;
    border-bottom: 1px solid ${({ theme }) => theme.colors.color1};
  }
`

I tried:

const NavLinkContainer = styled.div`
  padding-top: ${({ theme }) => theme.spacings.xSmall};
  padding-bottom: ${({ theme }) => theme.spacings.xSmall};
  border-left: 1px solid ${({ theme }) => theme.colors.color7};
  padding-left: ${({ theme }) => theme.spacings.small} !important;

  &.active {
    border-left: 1px solid ${({ theme }) => theme.colors.color1};
  }
`

but that's incorrectly applied due to the padding-left. Another attempt:

<NavBody>
  {items.map((subItem, key) => {
    const { title, link } = subItem
    return (
      <NavLinkContainer key={key} activeClassName="active">
        <NavLink
          onClick={closeMobileMenu}
          to={`/docs${link}`}
          activeClassName="active"
        >
          {title}
        </NavLink>
      </NavLinkContainer>
    )
  })}
</NavBody>

the applied activeClassName doesn't render on the div and:

const NavLinkContainer = styled.div`
  padding-top: ${({ theme }) => theme.spacings.xSmall};
  padding-bottom: ${({ theme }) => theme.spacings.xSmall};
  border-left: 1px solid ${({ theme }) => theme.colors.color7};
  padding-left: ${({ theme }) => theme.spacings.small} !important;

  &[aria-current='page'] {
    border-left: 3px solid ${({ theme }) => theme.colors.color1} !important;
  }
`

doesn't render the color.

Research

In Styled Components is there a way to style the parent based wether the child has a className of active or aria-current="page"?


Solution

  • I figured out how to modify my parent NavLinkContainer. In Reach Router I have access to pathname from useLocation:

    const { pathname } = useLocation()
    

    using link from my map and useMemo I was able to find if the link existed by setting to a variable:

    const active = useMemo(() => {
        return pathname.toString().includes(link)
      }, [pathname, link])
    

    by doing so I could pass a boolean to NavLinkContainer:

    <NavLinkContainer border={active}>
      // Further code
    </NavLinkContainer>
    

    and in the Styled Component I could do:

    const NavLinkContainer = styled.div`
      border-color: ${({ border, theme }) =>
        border ? `${theme.colors.color1}` : `${theme.colors.color5}`};
    `
    

    which gives me the ability to style the parent div with a custom border color set within my theme.