Style child component from parent component using styled-components - javascript

I have a parent component and a child component. I import child component into parent and use it there. Below is the child component.
import styled from "styled-components";
const HeaderContainer = styled.h1``;
const Header = () => <HeaderContainer>This is Header</HeaderContainer>;
export default Header;
As you can see it is a simple component all it does it render text.
The parent component is below.
import "./styles.css";
import Header from "./header";
import styled from "styled-components";
const Main = styled.div`
${Header} {
background-color: "red";
}
`;
export default function App() {
return (
<Main className="App">
<Header />
</Main>
);
}
I am importing Header and using it inside JSX. What I want is to style Header component from parent component. I tried below styles using styled-components but it does not work somehow.
const Main = styled.div`
${Header} {
background-color: "red";
}
`;
Here is the codesandbox.
https://codesandbox.io/s/dreamy-brown-w3bbs?file=/src/App.js:0-277
How can I make this work or if you have a better idea then please share.

You don't need any global class names or selecting DOM elements which are both obvious bad practices. Instead, just use "styled" on the child component inside its parent. So, e.g., in your case:
import Header from "./header";
import styled from "styled-components";
const StyledHeader = styled(Header)`
background-color: "red";
`;
export default function App() {
return (
<Main className="App">
<StyledHeader />
</Main>
);
}
Also it's important to notice that: Some CSS-in-JS tools will require you to manually add a className prop inside the child component's props in order for this to work. So, e.g., your Header component must become:
const Header = ({ className }) => <HeaderContainer className={className}>This is Header</HeaderContainer>;
You don't need to pass this prop yourself from the parent component, your CSS-in-JS tool will pass this className automatically.

Hopefully I'm following what you want to do but in your Main styles you could just target the h1 for the header?
const Main = styled.div`
h1 {
background-color: red;
}
`;
Or if you didn't want to target all H1's then add a className to your header, I've added a few ways to do this here: https://codesandbox.io/s/clever-stallman-bdwfk?file=/src/App.js

In your code, the part where you tried to style the header, it was wrong. Instead what you can and should try is:
inline styles:
import "./styles.css";
import Header from "./header";
import styled from "styled-components";
export default function App() {
return (
<Main className="App">
<Header style={{background-color: "red"}}/>
</Main>
);
}
External CSS
You can give a class name to your elements inside the components and style them in the parent file like the following
The css file, let's name it style.css
.header-heading {
font-size: 4rem;
color: "red";
}
The child component
const Header = function () {
return (
<h1 className="header-heading">This is the heading</h1>
);
}
export default Header;
Then you can import both of them in the parent file and you'll see that the child component is styled now.
The parent component
import React from "react";
import Header from "./Header";
import "./style.css";
const App = function () {
<>
<h1>Below is the h1 component rendered</h1>
<Header />
</>
}

Related

Change navigation styles depending on current path in Gatsby.js

With require(), I can get the CSS switching from page A to page B ... but when returning to page A, it keeps the stylesheet from page B active, even though both pages require() their own specific stylesheet. The same happens when I require() stylesheets by comparing to the current URL (location.pathname). What is the easiest way to accomplish this in Gatsby?
What I've tried:
import React from 'react'
import Layout from '../components/Layout'
import Header from '../components/Home/Header'
import style from "../templates/module.main.css";
const IndexPage = () => {
return (
<Layout className={style}>
<Header />
</Layout>
)
}
export default IndexPage
import React from 'react'
import Layout from '../components/Layout'
import Header from '../components/Home/Header'
import style from "../templates/module.secondary.css";
const IndexPageTwo = () => {
return (
<Layout className={style}>
<Header />
</Layout>
)
}
export default IndexPageTwo
Use css module, it will link the style to the pages/components.
Example with scss:
styleA.module.scss:
.page {
background: red
}
PageA.tsx:
import style from "./styleA.module.scss";
export default () => <div className={style.page}>Test</div>
styleB.module.scss:
.page {
background: blue
}
PageB.tsx:
import style from "./PageB.module.scss";
export default () => <div className={style.page}>Test</div>
Okay, I think the best solution for me was using react helmet and targeting the class like that:
<Helmet bodyAttributes={{ class: 'pageTwo' }} />
and then
.pageTwo .navbar {
color: black;
}

How to use styled-components that extends from an already styled component?

I'm trying to extract all my component styling using styled-component to a single file.
However, I am running into an error where if I extract the styling and the component which the styling is reliant into a separate file, the themes stop working.
Button.jsx
import {ButtonWrapper} from './Button.styled';
import {Button as MUButton} from '#material-ui/core/Button';
export const Button = () => {
return <ButtonWrapper>
<Button/>
</ButtonWrapper>
}
SmallButton.jsx
import {StyledSmallButton} from './Button.styled';
// import {Button} from './Button'
// const StyledSmallButton = styled(Button)` // This works.
// width: 50%
// `
export const SmallButton = () => {
return <StyledSmallButton/>
}
Button.styled.jsx
import {Button} from './Button';
export const ButtonWrapper = styled.div`
width: 100%
`;
export const StyledSmallButton = styled(Button)` //Doesn't work.
width: 50%
`;
Error
Uncaught Error: Cannot create styled-component for component: undefined
I believe there is a cyclic dependency problem here i.e. SmallButton requires Button which is themed by ButtonWrapper.
How can I solve this?
I think you've imported the MUButton wrong. Should the <Button /> component actually be <MUButton />?
import {ButtonWrapper} from './Button.styled';
import {Button as MUButton} from '#material-ui/core/Button';
// ^ this is the alias
export const Button = ({ className }) => {
return <ButtonWrapper>
<MUButton className={className} />
// ^ This should be MUButton
</ButtonWrapper>
}
Edit: passed the className prop down as mentioned by Andy int he comments

styled-components won't work with multiple ReactDOM.render

So I'm building a RubyOnRails application with React in the frontend. I have several microfrontends and I render each of them separately. Like this:
index.html.erb
<div id="app-header" />
<div id="app-content" />
Header.jsx
import React from 'react';
import styled from 'styled-components';
import ReactDOM from 'react-dom';
const StyledHeader = styled.div`
// ...
`;
const Header = () => {
return <StyledHeader>...</StyledHeader>;
}
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(
<Header />,
document.getElementById('app-header'));
});
App.jsx.jsx
import React from 'react';
import styled from 'styled-components';
import ReactDOM from 'react-dom';
const StyledApp = styled.div`
// ...
`;
const App = () => {
return <StyledApp>...</StyledApp>;
}
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(
<App />,
document.getElementById('app-content'));
});
The problem is that it looks like styled-components one renders the last styles from ReactDOM.render.
So in this case only app-content would be styled. If I remove it, than app-header is styled. I inspected the elements and it looks like the styles from app-header are not in the DOM when app-content is present. It's like it gets overridden.
I can't make the content and the header under the same root component because the content is not always React.
Is there a way of making the styles work without breaking this structure?

How to switch between CSS styles of React components when changing routes?

I have a React app which now has two routes (the main landing page and career page).
I made one reusable JumbotronResponsive component and i am rendering that single component as child under JumbotronLanding component (for landing page) and as child under JumbotronCareer component (for career page).
Like this
import React, { PureComponent } from 'react'
import { ResponsiveJumbotron } from '../../HOC';
import './jumbotronCareer.css';
export default class JumbotronCareer extends PureComponent {
render() {
return (
<>
<ResponsiveJumbotron/>
</>
)
}
}
and this
import React, { PureComponent } from 'react';
import { ResponsiveJumbotron } from '../../HOC';
import './jumbotronLanding.css';
export default class JumbotronLanding extends PureComponent {
render() {
return (
<>
<ResponsiveJumbotron/>
</>
)
}
}
As you can see those parent components have styles in which i want to style some ResponsiveJumbotron's parts.
For example i wish font-size of jumbotron in landing page be 70px and in career page be 45px.
But when i switch between routes those styles are like in one CSS file.
The question is 'how to separate them from each other?'.
You can add a className property and in your ResponsiveJumbotron use it like
const ResponsiveJumbotron = props => <div className={props.className} />;
[...]
export default class JumbotronLanding extends PureComponent {
render() {
return (
<>
<ResponsiveJumbotron className="landing-page" />
</>
)
}
}
and you can use that css class in your stylesheet

How can I add style to the body element with JSS?

I'm building an application using react-jss to style my components and wanted to know if it is possible to add styles to top-level elements (like html or body).
To illustrate, I have this simple NotFound component that I'm styling with react-jss. The style works fine, but the problem is the body elements has a default margin that I wanted to remove.
NotFound.js
import React from 'react';
import injectSheet from 'react-jss';
const styles = {
notFound: {
fontFamily: 'Roboto',
backgroundColor: 'blue',
color: 'white'
}
}
function NotFound({ classes }) {
return (
<div className={classes.notFound}>
NOT FOUND
</div>
)
}
export default injectSheet(styles)(NotFound);
Does anyone know if its possible to remove this margin using css-in-js? (I wanted to avoid css)
You can use the syntax introduced by jss-plugin-global
'#global': {
body: {...}
}
Also recommend creating a separate component for this and wrap your component with it. Otherwise your specific component becomes less reusable.
Just to elaborate Oleg's response, that's what I did:
1.Create my own JssProvider. Note: Had to also add jss-plugin-camel-case otherwise it wouldn't parse my properties from camelCase to lisp-case:
import App from './App';
import { create } from 'jss';
import { JssProvider } from 'react-jss';
import globalPlugin from 'jss-global';
import camelCase from 'jss-plugin-camel-case';
const jss = create();
jss.use(globalPlugin(), camelCase());
render(
<JssProvider jss={jss}>
<Router>
<App />
</Router>
</JssProvider>
document.getElementById("app")
);
2.Added my global property to my top level component
import React from "react";
import Main from "./common/ui/components/Main";
import NotFound from "./components/NotFound";
import injectSheet from 'react-jss';
const style = {
'#global': {
body: {
margin: 0
}
}
};
const App = () => {
return (
<Main>
<Switch>
<Route component={NotFound}/>
</Switch>
</Main>
);
};
export default injectSheet(style)(App);
And that was it! Worked like a charm.
EDIT: After some more research I found that I don't need step 1. Just adding the #global style to my App component did the job. I guess this jss-global plugin must be default in react-jss. (someone correct me if I'm wrong)

Categories