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

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)

Related

Style child component from parent component using styled-components

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 />
</>
}

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 detect if another component is present in the document?

I have a site built with React Static that has a Header component that is always present. Depending on if the current page has a hero component or not, the Header should be either light or dark.
The Header is rendered outside of the routes and the useEffect is triggered before the children is rendered. This is probably because of the routing.
This is the current code:
// App.js
import React, { useState, useEffect } from 'react'
import { Root, Routes } from 'react-static'
export default () => {
const [useDarkTheme, setUseDarkTheme] = useState(false);
useEffect(() => {
if (typeof document !== "undefined") {
const heroPresent = document.querySelectorAll(".o-hero").length > 0;
console.log("The hero is present: " + heroPresent);
setUseDarkTheme(!heroPresent);
}
})
return (
<Root>
<React.Suspense fallback={ <em>Loading...</em> }>
<Header useDarkTheme={ useDarkTheme } />
<Routes default />
</React.Suspense>
</Root>
);
}
What will be rendered at <Routes default /> is the static pages configured in React Static's static.config.js.
Below is an example of the Hero component:
// Hero.js
import React from "react";
export default () => {
console.log("This is the Hero rendering. If this exist, the Header should be dark.");
return (
<div className="o-hero">
<p>Hero!</p>
</div>
);
}
When I run the application and look at the logs this is what I get:
The hero is present: false
This is the Hero rendering. If this exist, the Header should be dark.
How could I somehow detect the presence of the Hero from the Header although the Hero is in a router and the Header is not? This feels like quite a common use case, but I could not find any info on the interwebs.
Thanks in advance!
So I ended up using useContext to provide all children with a getter and a setter for the Header's theme (dark or light). The solution is very much inspired from this answer. The solution looks like this:
// App.js
import React, { useState, useContext } from 'react'
import { Root, Routes } from 'react-static'
import { HeaderThemeContext } from "./context";
export default () => {
const { theme } = useContext(HeaderThemeContext);
const [headerTheme, setHeaderTheme] = useState(theme);
return (
<Root>
<React.Suspense fallback={ <em>Loading...</em> }>
<HeaderThemeContext.Provider value={ { theme: headerTheme, setTheme: setHeaderTheme } }>
<Header theme={ headerTheme } />
<Routes default />
</HeaderThemeContext.Provider>
</React.Suspense>
</Root>
);
}
// Hero.js
import React from "react";
import { headerThemes, setHeaderTheme } from "./context";
export default () => {
setHeaderTheme(headerThemes.DARK);
console.log("This is the Hero rendering. If this exist, the Header should be dark.");
return (
<div className="o-hero">
<p>Hero!</p>
</div>
);
}
// context.js
import React, { createContext, useContext } from "react";
export const headerThemes = {
LIGHT: "light",
DARK: "dark",
};
export const HeaderThemeContext = createContext({
theme: headerThemes.LIGHT,
setTheme: () => {}
});
// This is a hook and can only be used in a functional component with access to the HeaderThemeContext.
export const setHeaderTheme = theme => useContext(HeaderThemeContext).setTheme(theme);
This gives global access to set and get the header theme, which might not be optional, but it works for now and I think it's fine. Please let me know if there is a better way of doing this.

Props not being passed when using custom document for styled components

I'm trying to use styled components with next.js. I've added the babel plugin and added a custom _document.js. That all seems to be working fine.
However, when I try and use isomorphic-unfetch to getInitialProps into the page, the request returns data, but it doesn't make it into Props.
For _document.js I'm using the code from the next.js site here and have also tried a slightly different version from here which I also saw on the next.js docs site at some point.
My test page looks like this (also from the next.js docs):
import styled from 'styled-components';
import fetch from 'isomorphic-unfetch';
export default class MyPage extends React.Component {
static async getInitialProps({ req }) {
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
return { userAgent }
}
render() {
return (
<div>
Hello World {this.props.userAgent}
</div>
)
}
}
I also have an _app.js that looks like this:
import App, { Container } from 'next/app';
import Page from '../components/Page';
class MyApp extends App {
render() {
const { Component } = this.props;
return (
<Container>
<Page>
<Component />
</Page>
</Container>
);
}
}
export default MyApp;
And Page.js just has some styled components and a theme with a component that looks like this:
class Page extends Component {
render() {
return (
<ThemeProvider theme={theme}>
<StyledPage>
<GlobalStyle />
<Meta />
<Header />
{this.props.children}
</StyledPage>
</ThemeProvider>
);
}
}
export default Page;
I feel like it's something to do with the new _document.js or _app.js not passing the props down somehow.
Clearly I'm pretty new to next.js, so apologies if it's something stupid. In the meantime, I'll keep plugging away to get a better understanding of what's going on. I wanted to ask in parallel here since I'm under some time pressure for this project.
Many thanks for any thoughts you might have.
Not long after posting, I worked out the issue. I guess posting on SO helps to understand the issue.
Essentially it was _app.js which was the problem, not _document.js as I had initially expected.
I needed to add pageProps to _app.js so that they were propagated down to the page:
import App, { Container } from 'next/app';
import Page from '../components/Page';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Page>
<Component {...pageProps} />
</Page>
</Container>
);
}
}
export default MyApp;

React Router & Global Context

I'm building an e-commerce website with React (my first ever React project) and I'm using React router to manage my pages.
I've got the following component tree structure:
<Router>
<BrowserRouter>
<Router>
<withRouter(Base)>
<Route>
<Base>
<BaseProvider>
<Context.Provider>
<Header>
<PageContent>
The standard React Router structure basically, and withRouter I've got the following:
Base.js
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { Header } from './Header';
import { Footer } from './Footer';
import Provider from '../../BaseProvider';
class Base extends Component {
render() {
return (
<Provider>
<Header/>
<div className="container">{this.props.children}</div>
<Footer />
</Provider>
);
}
}
BaseProvider.js
import React, { Component, createContext } from 'react';
const Context = createContext();
const { Provider, Consumer } = Context;
class BaseProvider extends Component {
state = {
cart: [],
basketTotal: 0,
priceTotal: 0,
};
addProductToCart = product => {
const cart = { ...this.state.cart };
cart[product.id] = product;
this.setState({ cart, basketTotal: Object.keys(cart).length });
};
render() {
return (
<Provider
value={{ state: this.state, addProductToCart: this.addProductToCart }}
>
{this.props.children}
</Provider>
);
}
}
export { Consumer };
export default BaseProvider;
This gives me a template essentially, so I just the children pages without having to include Header and Footer each time.
If I want to use my global context I'm having to import it each time, and it seems like I've done something wrong as surely I should be able to use this on any page since it's exported in BaseProvider?
If I was to visit the About page, I'd get the same component structure, but no access to the consumer without using:
import { Consumer } from '../../BaseProvider';
Why do I have to do this for each file even though it's exported and at the top level of my BaseProvider? It just seems such a bad pattern that I'd have to import it into about 20 files...
Without importing it, I just get:
Line 67: 'Consumer' is not defined no-undef
I tried just adding the contextType to base but I get: Warning: withRouter(Base): Function components do not support contextType.
Base.contextType = Consumer;
I feel like I've just implemented this wrong as surely this pattern should work a lot better.
I'd recommend using a Higher Order Component - a component that wraps other components with additional state or functionality.
const CartConsumer = Component => {
return class extends React.Component {
render() {
return (
<MyContext.Consumer>
<Component />
</MyContext.Consumer>
)
}
}
}
Then in any component where you'd like to use it, simply wrap in the export statement:
export default CartConsumer(ComponentWithContext)
This does not avoid importing completely, but it's far more minimal than using the consumer directly.

Categories