Can I select all react components of a type, without assigning a class to each one?

I have a Playground here: https://codesandbox.io/s/736v9vjzw1

const Something = ({ classes, children, variant }) => {
  return (
    <div className={classes.someThing}>
      <p> I'm some thing </p>

      <SomeOtherThing />

      <SomeOtherThing> I have some children </SomeOtherThing>

      <SomeOtherThing> I have some children </SomeOtherThing>

      <SomeOtherThing> I have some children </SomeOtherThing>
    </div>
  );
};

const styles = {
  someThing: {
    color: "green",
    border: "solid 2px black",
    margin: 30,

    "& $someOtherThing": {
      backgroundColor: "pink" // Doesn't work
    },

    "& p": {
      fontWeight: "bold" //This works but is too broad
    }
  }
};

I have a situation here, where I want to style all the SomeOtherThings inside my SomeThing.

I can use & p selector to select the p element - but I don't like this. It would style any random ps I have around - and I don't want to have to look inside the component definition to find what it's top level element is.

How can I do this? Something like & SomeOtherElement.

The real world application of this, is that in some places I want have SomeOtherElement be displayed block and other places inline-block.

Answers:

Answer

I would extend the SomeOtherThing component to accept a className and add it to the div if present. This will also work on a production setup, where the class names is minified to e.g. .t-0-root.

Here is a forked playground: https://codesandbox.io/s/zlzx277zzm which shows how to use it.

const SomeOtherThing = ({ classes, children, className }) => {
  return (
    <p className={`${classes.someOtherThing} ${className && className}`}>
      I'm another thing {children}
    </p>
  );
};

I would most likely use the package classnames to conditionally render the class name instead of string interpolation.

const Something = ({ classes, children, variant }) => {
  return (
    <div className={classes.someThing}>
      <p> I'm some thing </p>

      <SomeOtherThing />

      <SomeOtherThing className={classes.someOtherThing}>
        I have some children
      </SomeOtherThing>

      <SomeOtherThing className={classes.someOtherThing}>
        I have some children
      </SomeOtherThing>

      <SomeOtherThing className={classes.someOtherThing}>
        I have some children
      </SomeOtherThing>
    </div>
  );
};

const styles = {
  someThing: {
    color: "green",
    border: "solid 2px black",
    margin: 30
  },
  someOtherThing: {
    backgroundColor: "pink"
  }
};
Answer

It's so simple, in your someThing CSS codes select the p elements with class someOtherThing class name and use not() CSS operation for p in top level, see following code:

const styles = {
  someThing: {
    color: "green",
    border: "solid 2px black",
    margin: 30,

    "& [class*='SomeOtherThing']": {
      backgroundColor: "pink" // 
    },

    "& :not([class*='SomeOtherThing'])": {
      fontWeight: "bold" // Just for top level p
    }
  }
};

and

const SomeOtherThing = ({ classes, children }) => {
  return (
    <p className={classes.root}> I'm another thing {children} </p>
  );
};

CodeSandBox

The way that this works, is that by giving SomeOtherThing any jss class, it's going to render the dom element as something like:

 <p class="SomeOtherThing-root-0-1-2"> I'm another thing  I have some children  </p>

which the [class*='SomeOtherThing'] attribute selector will match on.

You should note that this selector will apply to any deeper nested SomeOtherThings as well.

Answer

One problem with the "cascading" aspect of CSS is that it sort of breaks React's component model. But in React you can always create a wrapper or higher-order component that returns another with some predefined props, kind of like a factory function:

const Something = props => {
  return (
    <div className={props.className}>
      // ...
    </div>
  )
}

const SomethingInline = props => {
  return <Something className='display-inline' {...props} />
}

const SomethingBlock = props => {
  return <Something className='display-block' {...props} />
}

const App = () => {
  return (
    <div>
      <SomethingInline />
      <SomethingBlock />
      <SomethingBlock> I have children </SomethingBlock>
    </div>
  )
}

Instead of using the & selector to define your style, create a class that only applies to these specific versions of your component. In this way, the global scope of CSS is avoided and you can create these sort of declarative components that describe their own style, but don't require you to explicitly pass a class name.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.