css-loader css modules rules of nested component - reactjs

Lets say I use scss modules in the following way
A.jsx:
import styles from './A.module.scss';
import B from '../B/B.jsx';
const A = props => {
return <div className={styles.a}>
<B/>
</div>;
};
export default A;
B.jsx:
import './B.scss';
const B = props => {
return <div className="b">
</div>;
};
export default B;
and inside A.module.scss I override B's css rules as follows:
.a {
.b {
width: 500;
height: 500;
background-color: red;
}
}
then the styles I override for the B instance inside A are not passes to it, obviously because it's using css modules. How can I make the css rules of B applies only in A and not globally?
I thought maybe I have to pass custom className props for every component I want to achieve this, so for this instance pass custom className prop to B and pass it like so:
<B className={styles.b}/>
and in B.jsx just apply the custom class to the component.
Not my favourite solution, would rather not adding custom class but use just custom selector .a .b as this defines the relationship of every B which is under A I want to target, but I'm wondering if there's a simpler solution like I'm looking for?
Also I'm not sure the className prop is a viable solution for when B has several nodes nested in it and I'm trying to target a nested node inside it with my css rules. I can't just have for every component I have a className prop for every node in it.
Here's an example illustrating this situation:
B.jsx:
import './B.scss';
const B = props => {
return <div className="b">
<div className"b-inner1">
<div className"b-inner2">
<div className"b-inner3">
</div>
</div>
</div>
</div>;
};
export default B;
Here for example I wish to style the node with the className b-inner3 which might be just a node that is there for helping with the basic layout of the B's component and I do not wish to be able users of this component pass className to this node.

Each component (like any other function) has some abstractions over implementation details, it also has an API, this API is a contract between you and the consumer of your component.
If you are not exposing a className or style prop, that means you are basically saying "this component will not guaranty to always keep the same structure or public class names in the DOM". If anyone will override your classes, he/she are taking a risk and may face a breaking change.
If you do want to give the consumers the ability to override or add styling, you have 2 (or 3) main options as i see it:
Accept style and className props for each node you want to allow
styling:
const MyComponent = (props) => (
<div className={`${styles.myStyle1} ${props.publicStyle1}`}>
<div className={`${styles.myStyle2} ${props.publicStyle2}`}>
<button
className={`${styles.button}
${props.publicStyleButton}`}
>
Click Me
</button>
</div>
</div>
);
Yes, this can get messy and may look like a confusing API to
consume and maintain.
Expose hardcoded class names:
const MyComponent = (props) => (
<div className={`${styles.myStyle1} public-style-1`}>
<div className={`${styles.myStyle2} public-style-2`}>
<button
className={`${styles.button} public-style-button`}
>
Click Me
</button>
</div>
</div>
);
This way, your contract says you will always keep those public class names (or a breaking change version)
Another approach which is not directly related to styling, usually
referred as "Compound Components", you can mix it with one of
the approaches above:
const MyButton = ({ text, onClick, ...rest }) => (
<button
onClick={onClick}
className={`${styles.button}`}
{...rest}
>
{text}
</button>
)
const MyComponent = (props) => (
<div className={`${styles.myStyle1} public-style-1`}>
{children}
</div>
);
MyComponent.Button = MyButton;
/* usage... */
<MyComponent>
<MyComponent.Button
text="Click Me"
className="consumer-class"
style={{color: 'green'}}
/>
</MyComponent>
This will let you break your component into smaller pieces and give
more control to your consumers.

Related

Why do (some) CSS styles break when I defined a React functional component inside another functional component?

Why does defining a React functional component inside another functional component break CSS transitions?
function Doohick({isOpen}: {isOpen: boolean}) {
const style = {
transition: 'opacity 2s ease',
...(isOpen ? {opacity: 1} : {opacity: 0})
}
return (
<div style={style}>
Doohick!!!
</div>
)
}
function Parent() {
const [open, isOpen] = useState(false)
return (
<>
<button onClick={() => setIsOpen(!isOpen)}>Toggle Doohick</button>
<Doohick isOpen={isOpen} />
</>
)
}
If I define Doohick outside of Parent, as above, everything works great. If I move the definition inside Parent, with no other changes, my CSS transitions break. Other CSS properties are fine.
Why does defining a functional component inside another functional component break CSS transitions?
Complicated Explanation of Why I Want To Do This
I hear you asking: why would I want to do that? I'll tell you, but bear in mind you don't need to know any of this to understand the specific problem.
I want to encapsulate the Doohick state in a custom hook:
function useDoohick() {
const [isOpen, setIsOpen] = useState(false)
const ToggleButton =
<Button onClick={() => setIsOpen(!isOpen)}>Toggle Doohick</Button>
const Doohick = <MyDoohick show={isOpen}/>
return {ToggleButton, Doohick}
}
function Parent() {
const {Doohick, ToggleButton} = useDoohick()
return (
<>
{ToggleButton}
{Doohick}
</>
)
}
But I also want the Parent to be able to pass its own props into Doohick or ToggleButton. I can almost achieve that that like this:
function useDoohick() {
const [isOpen, setIsOpen] = useState(false)
const ToggleButton = ({text}) =>
<Button
onClick={() => setIsOpen(!isOpen)}
>
{text}
</Button>
const Doohick = () =>
<MyDoohick show={isOpen} />
return {ToggleButton, Doohick}
}
function Parent() {
return (
<>
<ToggleButton text='Burninate' />
<Doohick />
</>
)
}
This works as advertised: ToggleButton renders with the expected label and controls whether or not Doohick is shown. But this pattern breaks some CSS styles (specifically, transitions) I have defined on Doohick. Other styles are fine.
I can still call it like this:
function Parent() {
return (
<>
{ToggleButton({text: 'Burninate'})}
{Doohick()}
</>
)
}
...and the transitions work correctly. But I would much prefer the standard JSX syntax here:
<ToggleButton text='Burninate />
Clearly, <Doohick /> and Doohick() are different. But what is it about the former that breaks CSS transitions here?
The root of the problem boils down to defining the custom components inside the Parent. The hook itself is irrelevant. But this pattern of encapsulating state in a custom hook while returning a customizable component is really powerful and almost works, so I'm hoping there's a way it can be saved.
TL;DR
Why does defining a component within another component break my CSS transitions (and possibly other styles I haven't found yet)? How can I get around this while still calling my nested component with JSX-style syntax?
Defining a component inside another component will always result in issues like this. Every time the outer component renders, you create a brand new definition of the inner component. It may have the same text as the one from the previous render, but it's a different function in memory, so as far as react can tell it's a different type of component.
The component type is the main thing that react looks for when reconciling changes. Since the type changed, react is forced to unmount the old component and then mount the new one. So rather than having a <div> on the page who's style is changing, you have a div with some style, then it gets deleted and an unrelated div gets put onto the page. It may have a different style, but since this is a brand new div, the transition property won't do anything.

Having trouble with onMouseEnter event in React

I'm trying to build a navbar, using React and hooks, where each div will change to a specific color on an onMouseEnter and onMouseLeave. I can't figure out why they're all affected if i hover over one. I guess I'm asking how I could make them independent of one another.
Sorry if this is a really obvious mistake. Still really green. Thanks again!
Here is a link to the CodeSandbox: https://codesandbox.io/s/holy-snowflake-twojb?file=/src/navbar.js
I refactored your code.
You can check it in https://codesandbox.io/s/lucid-lake-u3oox?file=/src/navbar.js
You are using the function setBackground to set the background color in the hoverStyle CSS class which affects to all <div className="hoverStyle"> tags.
There are many options to do that. If you want to do it with React, one way of do it is creating a CSS class like this:
.active {
background-color: #ffac4e;
}
then, create a functional component
const Activable = ({ className, children, bgColor}) => {
const [active, setActive] = useState('#fff');
return (
<div className={`${ className } ${ active }`
onMouseEnter = {() => setActive( bgColor )}
onMouseLeave = {() => setActive('#fff')}
>
{ children }
</div>
)
}
then replace your ` with this new component like this:
<Activable className="hoverStyle" bgColor="#ffac4e">
<div style = {navChildLeft}>2020</div>
<div style = {navChildTop}>
Shy RL <br />
Digital - Album art
</div>
</Activable>
and repeat with the rest of <div>'s
With react, it is important to think about reusable components. How can you create something that you can reuse over and over again in different parts of your project.
Take a look at this sample of your project broken into components.
https://codesandbox.io/s/holy-brook-3jror?fontsize=14&hidenavigation=1&theme=dark
I would recommend reading this to help out: https://reactjs.org/docs/thinking-in-react.html

Dynamically style a React element

I am trying to dynamically style a React element: In the nav bar, I would like to make the font color of the <div> displaying the current page to be different from the other <div>s representing other pages. I have passed into my <NavBar> component's props the params representing the current page.
From this information, I have figured out a way to achieve what I am trying to do: I store the <div>s representing the page links in an object and wrap the selected page in another <div> carrying a 'selected'class name.
However, I could envision scenarios where this solution would disrupt styling in place since it adds a wrapper element and classes within the wrapper element would take precedence over the 'selected' class.
Does anyone know a better way? I have posted my solution and the context below:
export default class NavBar extends React.Component {
constructor(props) {
super(props);
}
render() {
const pages = {
my_subscribed_docs:
<div className='nav__row2-item'>Documents I'm Subscribed To</div>,
my_created_docs:
<div className='nav__row2-item'>Documents I've Created</div>,
new_doc:
<div className='nav__row2-item'>
<div className="plus-icon">+</div>
<div className='nav__row2-text_left-of-icon'>New Document</div>
</div>,
};
const currentPage = this.props.path.slice(1);
pages[currentPage] =
<div className='nav__row2-item--selected'>{pages[currentPage]}</div>;
debugger;
return (
<nav>
<div className="nav__row1">
<div className="nav__logo">Worksheet Generator</div>
<div style={{cursor: 'pointer'}} onClick={this.props.logout}>
Logout
</div>
</div>
<div className="nav__row2" >
{ Object.values(pages).map( page => (
<div key={shortid.generate()}>{page}</div>)
) }
</div>
</nav>
);
}
}
If you want to dynamically add a class to your component, I recommend using classnames library. This is pretty much the way to do it when using external stylesheets.

Preserve class name when wrapping element with styled-components

I'm leveraging the Bootstrap grid so I'm creating components that render elements that include a class:
const Row = props => (
<div className="row" {...props}>
{props.children}
</div>
);
Now, at times I want my row to be padded, so I attempted to wrap my Row component:
const PaddedRow = styled(Row)`
padding: 20px;
`;
This applies the padding, but when the div is rendered, it is without the class.
Chrome's React plugin shows the following:
<Styled(Row)>
<Row className=".sc-cHds hQgwd">
<div className=".sc-cHds hQgwd"> // No "row" class
Is it not possible to preserve the class name for the wrapped component?
What you're doing with {...props} is spreading the passed-in props. styled-components passes the generated className via these props, and because you apply the spread after your className property it overrides the existing class.
If you were to write out manually what your code does in the end this is what it'd look like:
<div className="row" className="sc-cHds">
This makes it apparent what the issue is: the second className declaration is overriding the first one! The solution if I were to stick strictly to your code would be to concatenate the passed in class manually:
<div {...props} className={`row ${props.className}`}>
This works, however it's not idiomatic usage of styled-components. Rather than wrapping the component with styled(Row) we recommend creating a styled component for that <div />:
const RowWrapper = styled.div`
padding: 20px;
`
const Row = props => (
<RowWrapper className="row">
{props.children}
</RowWrapper>
);
This is the way styled-components is intended to be used, it will also automatically concatenate your Bootstrap class with the automatically generated one.

is there a way to have templates in react

I have built an element which is kind of template. (e.g, thumbnail container with image at the top and something in the footer with dynamic content between them)
the dynamic content can be different types of DOM elements, based on the state.
I did it with adding logic in the render method which "injects" the dynamic part.
Does this make sense (having logic in the render method which returns different react components)?
Is there a better way for templating? (i'm not looking for projects that add this capability, wanted to know if there's a "react way" to do so.
Thanks!
edit: here's the code I was referring to (coffeescript):
internalContent: ->
switch #props.title
when "title1" then SomeReactFactory(props)
when "title2" then SomeOtherReactFactory(props)
render ->
...
DOM.div
className: 'panel'
#internalContent()
the internalContent() method is dynamically adding some React Component based on the prop
This is the React way.. And you should make use of it to target your specific domain.
For example a Button in React could be written like this:
const MyButton = ({ text, type = "normal", color = "blue", onClick }) => {
return (
<button
onClick={onClick}
style={{backgroundColor: color }}
className={"my-button my-button--type" + type}>
{text}
</button>);
};
Or a layout component:
const MyLayout = ({side, nav, main}) => {
return (
<div className="container">
<nav>{nave}</nav>
<aside>{side}</aside>
<div className="main">{main}</div>
</div>
)
}
Now you can composite it for example like this:
class App extends Component {
...
render() {
<MyLayout
nav={<MyNav/>}
side={<MySideBar items={...} />}
main={<MyButton onClick={this.onClick} text="Main Button"}
/>
}
}
Dont try to pack everything in a big Component which will do everything, the trick in React is to make small reusable Components and composite them.
You could also create a bunch of components which you can use across many projects.

Resources