0

I have created a Container component with component styles in next.js. When I declare use of this container component throughout my site, I would like to add a className to it and subjectively style it depending on the context of its use.

Here is an example of my Container component:

    const Container = (props) => (
    <>
        <div className="container">
            {props.children}
        </div>

        <style jsx>{`
            .container{
                width: 100%;
                max-width: 1200px;
                margin: 0 auto;
            }
        `}</style>
    </>
)

export default Container;

So, I want to give it a maximum width of 1200px, and centre it with auto margins. No problem, I have done that.

Now, I am planning to use this component in the header of my site. But in the case of the header, I would like the Container component to be a flexbox:

import Container from './Container'

const Header = (props) => (
    <>
        <header>
            <Container className="header-col">
                <div>
                    <h1>{props.title}</h1>
                </div>
                <nav>
                    <ul>
                        {/* Navigation items */}
                    </ul>
                </nav>
            </Container>
        </header>

        <style jsx>{`
            .header-col{
                display: flex;
                justify-content: space-between;
            }
        `}</style>
    </>
)

export default Header;

When I view the site, I noticed the flexbox style I specified for the Container component in the header is not present.

I was expecting the className to take effect, and allow me to add additional styles.

I believe this has something to do with it thinking that className is not a class name, rather props. But, I want to keep my code dry by creating style inheritance on components.

How could I do this?

Thanks for the help!

brc-dd
  • 10,788
  • 3
  • 47
  • 67
Jack Wright
  • 113
  • 2
  • 10

2 Answers2

0

This is a job for styled components:

import React from "react";
import styled from "styled-components";

export default function App() {
  return (
    <>
      <Container custom={"header"}>
        <h1>Very fancy h1 with flex display</h1>
      </Container>
      <Container custom={"regular"}>
        <h1>Non-fancy h1 with no flex display</h1>
      </Container>
    </>
  );
}

const Container = styled.div`
  display: ${(props) => (props.custom === "header" ? "flex" : "block")};
  /* you can call your prop ^^^^^^ whatever you want, 
  just change it on the container element too*/

  & h1 {
    /* you can apply css to children like so */

    font-family: ${(props) =>
      props.custom === "header"
        ? '"Courier New", Courier, monospace'
        : '"Arial Black", Gadget, sans-serif'};
  }
`;

In the above, I've created a custom styled component that receives the custom prop and then conditionally changes the values you are looking to adjust. To make the changes more visible to the eye, I've also styled the font so you can see the immediate difference between the two <Container> elements.


For a solution that is more scalable (e.g., for different themes), use ThemeProvider:

import React from "react";
import styled, { ThemeProvider } from "styled-components";

export default function App() {
  return (
    <>
      <ThemeProvider theme={ContainerHeader}>
        <Container>
          <h1>Very fancy h1 with flex display</h1>
        </Container>
      </ThemeProvider>
      <Container theme={"regular"}>
        <h1>Non-fancy h1 with no flex display</h1>
      </Container>
    </>
  );
}

const Container = styled.div`
  display: ${(props) => props.theme.display};
  & h1 {
    font-family: ${(props) => props.theme.h1Font};
  }
`;

Container.defaultProps = {
  theme: {
    display: "block",
    h1Font: '"Arial Black", Gadget, sans-serif'
  }
};

const ContainerHeader = {
  display: "flex",
  h1Font: '"Courier New", Courier, monospace'
};

CodeSandbox: https://codesandbox.io/s/stack-conditional-styled-components-vmnpn?file=/src/App.js:0-773

Harley Lang
  • 2,063
  • 2
  • 10
  • 26
  • In my opinion, there is a problem here. This is very declarative, in that if I now choose to include a third display option like grid, I would need to modify the logic of the component. At this scale, that isn't a huge deal but I could imagine in larger projects this may make the component logic look ugly. – Jack Wright Sep 11 '20 at 22:43
  • @JackWright see my updated answer and let me know if `ThemeProviders` meets your use case. – Harley Lang Sep 11 '20 at 22:59
  • Oh I have never used Theme provider before... So this will, in a sense, "inject" the CSS into the component and hide all of the logic of adding multiple styles? I'm going to quickly test this! – Jack Wright Sep 11 '20 at 23:06
  • I have tried it, and it doesn't feel right. I am now mixing two methods of component styling and it's starting to look messy. In next.js, I love how you can add a local – Jack Wright Sep 11 '20 at 23:22
  • @JackWright sounds like a good solution for local style. If salability is what you are looking for, then you can do it with vanilla css the old-fashioned way or you may want to give `styled-components` a second chance and try abstracting the styles away to their own files/folder to increase readability. – Harley Lang Sep 11 '20 at 23:33
  • 1
    Definitely seems like I may need to rethink how I style my components! That's a shame. I haven't given ```styled-components``` enough time so I will look into that and maybe it'll grow on me. Thanks for the help! I'll leave this question open for a few more days to see what other people have to say. – Jack Wright Sep 11 '20 at 23:54
  • Totally fair---do what you feel most comfortable with for now. If you do change your mind and have additional questions, I'm happy to answer them! – Harley Lang Sep 12 '20 at 00:12
0

I believe I have found the answer to my own question (I will still leave this question open for a couple more days just in case it can be improved).

In order to pass styles, you can add the "global" flag to the styled JSX and append additional classnames in the component with props.className.

Parent Container component using props.className:

const Container = (props) => (
    <>
        <div className={`container ${props.className}`}>
            {props.children}
        </div>

        <style jsx>{`
            .container{
                width: 100%;
                max-width: 1200px;
                margin: 0 auto;
            }
        `}</style>
    </>
)

export default Container;

Then, when you want to use that component, you can add additional styles with the global flag in the <style jsx>:

Container being used and styled even more in the header:

import Container from './Container';

const Header = (props) => (
    <>
        <header>
            <Container className="header-col">

                <div>
                    <h1>{props.title}</h1>
                </div>


                <nav>
                    <ul>
                        <li>Hello</li>
                        <li>There</li>
                    </ul>
                </nav>
            </Container>
        </header>

        <style jsx global>{`
            .header-col{
                display: flex;
                justify-content: space-between;
            }
        `}</style>
    </>
)

export default Header;

This is not 100% perfect though (but in my opinion it is still pretty good):

  • The global flag makes your styles global. So other components can use these styles (I believe)
  • You need to make sure your components take in props.className to append additional classnames for this global style
Jack Wright
  • 113
  • 2
  • 10