1
votes

How would I go about applying conditional styles to a Styled Component based on its parent component's props?

A high-level sample setup of my current code is below:

// File 1: GrandParent.js

import React, { useState } from 'react';
import Parent from './parent';

export default function GrandParent() {
    const [ isExpanded, setIsExpanded ] = useState(false);

    render (
        <>
            <Parent isExpanded={isExpanded} />
            ...other Stuff
        </>
    );
}
// File 2: Parent.js

import React from 'react';
import styled from 'styled-components';
import mediaQueries from './settings';

export default function Parent(props) {
    render (
        <div className={props.isExpanded ? `expanded` : ``}>
            ... other stuff
            <Child>I'm a child.</Child>
        </>
    );
}

const Child = styled.div`
    background-color: "red";

    .expanded & {
        background-color: "blue";

        ${mediaQueries.desktop`
            background-color: "navy";
        `}
    }
`;

Essentially, what I'd like is for my <Child> styled component to have awareness of it's <Parent>'s props so that I can style it conditionally. Currently, I am just conditionally applying a class name to a div just inside the <Parent>, and then I'm using that class name to apply styling to the <Child>. But doesn't feel like a very Styled Components or React way of doing things.

What I'd like to be able to do is something more like this:

const Child = styled.div`
    background-color: ${props => props.isExpanded ? 'blue' : 'red'};
`;

But is there a way for me to do this without having to pass in 'isExpanded' as a prop on the <Child>? In my actual code, there are many more children of <Parent> who will also need to know the status of 'isExpanded' for styling purposes, and I really don't want to have to pass that prop to all of them individually.

I think Context API might be the key here, but I'm already using a context several levels higher up above GrandParent in the React tree for global style settings, and I'm afraid that if I try to nest a 2nd context (perhaps wrapping it around <Parent> to let <Parent> and all of its children know about the status of 'isExpanded'), that then both <Parent> and <Child> might lose access to the outer context... or is that not how context nesting works?

I just know that I still need both <Parent> and <Child> to have access to some theme settings from the outer context. And I don't know if it's overkill to introduce a 2nd context at the <Parent> level just for keeping track of the status of that single 'isExpanded' prop.

The other requirement here is that I need <Child> to be able to handle media queries within its conditional styling as well (eg. only applying certain styles if 'isExpanded' is true and if it's a desktop viewport).

What's the proper way to set this up?

2

2 Answers

1
votes

You are correct. Context is the way to go.

Context is designed to be a lightweight cross-component data store and using 2 contexts in this case is definitely not overkill; In fact, this use case is almost exactly what Context was created for. (Anecdotally, at work we use dozens of nested contexts).

export const ExpandedContext = createContext();
export default function GrandParent() {
    const [ isExpanded, setIsExpanded ] = useState(false);
    
    render (
        <ExpandedContext.Provider value={{ isExpanded }}>
            <Parent />
            ...other Stuff
        <ExpandedContext.Provider/>
    );
}

Unfortunately, there is no way to access context inside of a styled-component, so you'll have to pass in the value via props instead.

export default function Parent(props) {
    const { isExpanded } = useContext(ExpandedContext);
    render (
        <div>
            ... other stuff
            <Child isExpanded={isExpanded}>I'm a child.</Child>
        </div>
    );
}
1
votes

You could make the parent's container div a styled component and refer to Child. Something like:

export default function Parent(props) {
    render (
        <ParentContainer isExpanded={props.isExpanded}>
            ... other stuff
            <Child>I'm a child.</Child>
        </ParentContainer>
    );
}

const Child = styled.div`
    // ...
`;

const ParentContainer = styled.div`
  > ${Child} {
    background-color: ${props => props.isExpanded ? 'blue' : 'red'};
  }
`;