How to inject css to next.js page on ssr? - css

I am new to next js.
I have _app.tsx page:
import '../styles/antd.css'
import '../styles/globals.css'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
</>
)
}
export default MyApp
Problem is that styles - import '../styles/antd.css' are loaded as a external request in production run /_next/static/css/fffc165f54adb267.css it mean that there is a one second blink lag during the styles loading. Is there any better way how to load styles in my base page? Without external request for styles?

Next.JS's docs when talking about importing CSS states
In production, all CSS files will be automatically concatenated into a single minified .css file.
With SSR, the mark-up is sent to the user's DOM, which will have a link tag to your styles, the user will need to fetch, and parse styles before they render, which is what causes that flash you're talking about.
Use Styled-JSX to avoid this.
Styled-JSX will add the CSS render on the server with the rest of the content, and when served load with everything else instantly* Here's an example from their docs
function HelloWorld() {
return (
<div>
Hello world
<p>scoped!</p>
<style jsx>{`
p {
color: blue;
}
div {
background: red;
}
#media (max-width: 600px) {
div {
background: blue;
}
}
`}</style>
<style global jsx>{`
body {
background: black;
}
`}</style>
</div>
)
}
export default HelloWorld

Related

How to test CSS properties defined inside a class with react testing library

I am trying to test CSS properties that i have defined inside a class in css, wing the react testing library. However I am unable to do so.
Adding the simplified snippets.
import React from "react";
import { render, screen } from "#testing-library/react";
import '#testing-library/jest-dom/extend-expect';
import styled from "styled-components";
const Title = styled.span`
display: none;
background: red;
`
test("testRender", () => {
render(
<div>
<Title>Test</Title>
</div>
)
const spanElement = screen.getByText("Test");
const elementStyle = window.getComputedStyle(spanElement);
expect(elementStyle.display).toBe('none');
});
The test fails at the expect statement. I have tried refactoring to traditional css, there also the test fails. In both cases, I have tested it manually and the styles are taking effect.
I also understand that we should not directly test CSS properties, but I have tried testing the visibility with toBeVisible(), but that only works if the display: none is directly entered as a style, and not as part of a class.
This should be a very simple thing, that works out of the box, but I have been at it for some time now, without any luck.
Any help is appreciated.
I agree with #ourmaninamsterdam answer.
In addition, for checking appearance or disappearance of any element, you can also use .not.toBeInTheDocument like so:
expect(screen.queryByText("Test")).not.toBeInTheDocument();
NOTE: You must use queryByText instead of getByText in this case since queryByText wont throw an error if it doesn't find the element (it will return null).
Official docs Reference - https://testing-library.com/docs/guide-disappearance#nottobeinthedocument
You can use expect(screen.getByText("Test")).not.toBeVisible();
import React from "react";
import { render, screen } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import styled from "styled-components";
it("does display", () => {
const Title = styled.span`
display: block;
background: red;
`;
render(
<div>
<Title>Test</Title>
</div>
);
expect(screen.getByText("Test")).toBeVisible();
});
it("doesn't display", () => {
const Title = styled.span`
display: none;
background: red;
`;
render(
<div>
<Title>Test</Title>
</div>
);
expect(screen.getByText("Test")).not.toBeVisible();
});
...see the sandbox: https://codesandbox.io/s/blazing-river-l6rn6?file=/App.test.js

How to set path to a component image using style jsx tag in nextjs

I am using jsx to define styles for the NextJs components and i need to define a background image for some elements.
The only paths that seems to accept is the relative path, to the current page, or absolute path.
But how to pass a relative path to the component itself?
Here a simple component function for testing:
import React from 'react'
const TestComponent = (props) => {
return (
<div>
<h1 className="test">See my background</h1>
<style jsx>{`
.test {
background-image: url("someImageFile.jpg");
}
`}</style>
</div>
)
}
export default TestComponent
This will return the 404 error for url "localhost:3000/current page/someImageFile.jpg"
For a NextJS application, you should have a public folder at the root of the directory of your app where you store all the static files: such as images.
Here is a link to the official documentation where they explain it very clearly:
https://nextjs.org/docs/basic-features/static-file-serving
The trick is to use $ to be able to call require function:
<style jsx>{`
.test{
background-image: url(${require("./someImageFile.jpg")});
}
`}</style>
Reference documentation here.

Applying global styles in Next.js

I am using Next.js with Typescript. The margin of the body is default 8px and I want to get it to 0px. When I try to add an external style sheet to my index.tsx file it throws an error that you can only add external stylesheet to _app.tsx. However, even when I try to import in my _app.tsx, it doesn't change the global style of the body. I am using Emotion css for the styling part. Is there a different way to change the style of the body in the index file using global style? Here is my index.tsx code and I have tried adding the global styles using Emotion CSS as well but it doesn't work.
class Index extends React.Component {
render() {
return (
<div className='body'>
<Container>
<style jsx global>{`
.body:global() {
margin: 0px;
}
`}</style>
<NavBar />
</Container>
</div>
);
}
}
You need some global styles (<style jsx global>) to style the body element or provide css resets.
Example:
import Link from "next/link";
export default () => (
<div>
<style jsx global>{`
body {
background-color: red;
}
`}</style>
Hello, One!
<Link href="/two">
<a>Go to two</a>
</Link>
</div>
);
Code Sandbox
You can have global styles using emotion with Next.js
In your _app.tsx file, you must to
import { Global, css } from '#emotion/core'
return (
<>
<Global styles={css` /* styles */ `}/>
<Component {...pageProps} />
</>
You can see how to implement it, here
https://github.com/pabloobandodev/social-media/blob/master/pages/_app.tsx
According to the official docs:
Global CSS cannot be used in files other than your Custom <App> due to its side-effects and ordering problems.
Possible Ways to Fix It
Relocate all Global CSS imports to your pages/_app.js file.
How to do this in your case?
Well, the best way is to use a CSS base, lets take normalize.css for example.
Run yarn add normalize.css or npm i normalize.css, depending on whichever you are using.
Add import 'normalize.css'; in each of the page you want to use the base on. Official Docs.
Well this could seem redundant if you want to use the base in all of your pages. If so, you can, alternatively, create a file page/_app.tsx (any of the extension .js,.jsx,.ts,.tsx will work) and put this in it:
import 'normalize.css';
export { default } from 'next/app';
Note : If your app is running and you just added a custom App, you'll need to restart the development server. Only required if pages/_app.tsx didn't exist before.
No need to worry about other caveats mentioned in the docs as we are simply re-exporting App without any modification.
There are many CSS bases available choose any that seems best for you.
If you want to add custom global styles, then follow this:
Create a file styles/globals.css (.scss,.sass,etc. will also work if you have configured Next.js properly) and put your styles in that file.
Now add an import in pages/_app.tsx.
import '../styles/globals.css'; // change extension from `.css` to
// whatever you created above
export { default } from 'next/app';
If you have already created a module path alias for ../styles, then you might wanna change the styles import statement (probably to something like import '#styles/globals.css').
Also, if you are using less/sass/scss and want to use a base at the same time along with your custom global styles you simply need to use an import statement in your stylesheet (no need to import the base in _app.tsx if imported in the global stylesheet). An example:
// file: styles/globals.scss
#import '../node_modules/normalize.css/normalize.css';
// your styles...
body {
color: red;
}
// file: pages/_app.tsx
import '#styles/globals.scss';
export { default } from 'next/app';
Moreover, in your case it has not worked most probably because you were styling .body instead of body. It is likely that margin was present in the body, not your div.body.
This is how your _app.js, _app.tsx should look like; styles.css may have your CSS to reset the default browser properties, you can try adding other stylesheets here.
import '../styles/styles.css'
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}

CSS not changing when using React Router to route to another component

When I route my app to another component by using react-router-dom, the CSS doesn't change.
This is a minimalistic version of the code to demonstrate
App.js
import React from 'react';
import Home from './Home';
function App() {
return (
<div>
<Home></Home>
</div>
);
}
export default App;
Home.js
import React from 'react';
import './Home.css';
const Home = () => {
return (
<h1>Home</h1>
);
}
export default Home;
Home.css
body {
background-color: blue;
}
Dashboard.js
import React from 'react';
import './Dashboard.css';
import React from 'react';
import './Dashboard.css';
const Dashboard = () => {
return (
<div className='content'>
<h1>Dashboard</h1>
</div>
);
}
export default Dashboard;
Dashboard.css
.content {
display: flex;
align-content: center;
align-items: center;
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import Dashboard from './Dashboard';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter as Router, Route } from 'react-router-dom';
ReactDOM.render(
<Router>
<div>
<Route exact path='/' component={App} />
<Route path='/dashboard' component={Dashboard} />
</div>
</Router>, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: ...
serviceWorker.unregister();
When I do /dashboard, it loads the Dashboard component, but it keeps the previous CSS that was loaded from the Home component that resides the App component. The background stays blue. I want that when I route to another component because I changed the URL, it loads whatever CSS that new component has attached to it and gets rid of whatever CSS was before. Is that possible?
Edit: I have made an example in CodeSandbox to illustrate. It's a little different from the code above due to the limitations of the playground, but the functionality is the same.
From what can be seen, importing as a module ends up importing it globally. If we comment the line import Home from "./Home"; the blue background disappears. Just importing the component, imports the whole CSS despite the CSS being imported in a modular way. I'm not sure if I am missing something.
Edit 2:
Here are the different solutions I tried:
CSS Modules, but the body style was still globally loaded.
Styled components don't let me modify the body or html selectors CSS. They require me to create a <div> element and
then have that element span the whole body which I would style
as if it was the body. Which is a workaround I don't want to use because for that I rather use CSS Modules for the whole body spanning .
Inline styling also doesn't let me modify the body or html selectors CSS. I would also need to use a workaround like a body spanning <div> as in Styled components.
The problem
When you import a css like you're doing here
import './Home.css';
you're importing it in a global scope, which means it will not disappear once imported.
The solutions
CSS Modules
What you want is either CSS Modules, which is used like this:
import styles from './Home.css';
<a className={styles.myStyleClass}>Hello</a>
Styled components
or a CSS-in-js framework such as styled components which is used like this:
import styled from 'styled-components';
const MyStyledElement = styled.a`
color: blue;
`;
<MyStyledElement>Hello</MyStyledElement>
Regular objects / inline styling
or just "regular" CSS-in-js like:
const myStyle = {
color: blue;
}
<a style={myStyle}>Hello</a>
There are plenty of options when it comes to styling, these alternatives are popular ones that I encourage you to explore and see which you enjoy.
After doing some more tests I have concluded that as of now it is not possible to change whatever CSS styles have been applied to a <body> or <html> selector in an React SPA when a CSS file is already loaded and one uses React Router to render other components. I still appreciate the answers and the time taken to help me find a solution. They are still valid answers if we are not talking about the <body> or <html> node in an HTML document. From them I learned about other ways to use CSS in React. I modified the original post with the solutions I tried.
What ended working was modifying the DOM styles with JavaScript whithin the component itself.
Home.js
import React from "react";
const Home = () => {
// Modify the DOM Styles with JavaScript
document.body.style.backgroundColor = "blue";
// Or uncomment below to modify the
// document root background color
// which in this case would be <html>
//document.bgColor = "blue";
// Or modify the root tag style of the document instead of the
// <body> (<html> in this case)
//document.documentElement.setAttribute('style', 'background-color: green');
return (
<div>
<h1>Home</h1>
<form action="/dashboard">
<input type="submit" value="Go to Dashboard" />
</form>
</div>
);
};
export default Home;
Here is a working example:
Where my app wasn't loading style sheets and the like. However, I was importing my assets directly into my index.html entry point.
By replacing the links with absolute paths as per this documentation, my problem was resolved.
For me, this meant changing
<head>
<link rel="stylesheet" href="./style.css" ></link>
</head>
to this:
<head>
<link rel="stylesheet" href="/style.css" ></link>
</head>
I'm not sure if the same thing would work for your import statements, but it is worth a shot.
More info: styles-not-working-with-react-router

What is the best way to switch between web app's themes with React & Redux?

I want to implement an option to switch between two themes in my React and Redux web app.
I have 2 different CSS files, one for each theme. Right now in order to apply a CSS file's stylings, I simply import it at the head of my App.js.
import '../styles/theme1.css';
const App = (props) => {
return (
<div>
<Header />
<Container/>
</div>
);
};
const mapStateToProps = (state) => ({
theme: state.settings.theme // can return 'theme1' / 'theme2'
});
export default withRouter(connect(mapStateToProps)(App));
So I want to import stylings according to the app's state ('theme1' or 'theme2').
I could think of some messy ways and found some ready-made packages for that, but I want to know if there is a conventional way to do so, efficiently and as clean as possible.
this is a create-react-app kit.
Thank you.
One possibility depending on how your taskrunner configuration is set up would be to introduce React-Helmet, which would allow you to change the CSS reference in your <head> depending on the theme.
import React from "react";
import {Helmet} from "react-helmet";
function StyleSwitch (props) {
return (
<Helmet>
<link rel="stylesheet" href={props.stylesheet} />
</Helmet>
);
};
Try using scss/sass - I think it could simplify the whole process, because it allows you to nest classes/elements.
For example, lets assume you switch between two states, theme1 and theme2:
<div className={this.state.theme}>
<h2>Some text</h2>
</div>
And your .scss file should look like
.theme1{
background-color: black;
h2 {
color: white;
}
}
.theme2{
background-color: white;
h2{
color: black;
}
}

Resources