I am quite new to react and react-router-dom. The problem is, I am trying to get different layouts for different routes in my application. But instead all of the css files for different routes just imported in one huge conflicting piece.
App looks like this:
<Router>
< >
<Navigation />
<UpButton />
<Switch>
<Route path="/" exact component={Home} />
<Route path="/research" component={Research} />
<Route path="/publications" component={Publications} />
<Route path="/student" component={Student} />
<Route path="/about" component={About} />
</Switch>
</>
</Router>
Each component I am rendering looks like this:
import React from 'react';
import './home.scss';
// Utility
import Utility from 'components/utility/Utility';
class Home extends React.Component {
render() {
return (
< >
<Utility />
</>
);
}
}
export default Home;
Is there a way to import only the specific css file with specific route? Like home.css for '/' route, about.css for '/about' route specifically?
It might be the simple misunderstanding from me, but I really can't find any solution for now.
I recommend to use the following file structure and pattern:
- components
- Main.js
- Utility
- index.js
- styles.scss
- pages
- HomePage
- index.js
- styles.scss
Then specify the component related styles within the component's stylesheet, and the page-realated styles within the page's stylesheet. And use pages for routes, not components.
Finally, wrap every component into a specific class selector, and add wrap the belonging sass file into that selector (like .home-page).
For this reason, please, use sass instead of css, because you can embed style definitions within each other and use the top one as a "namespace". So the styles will not affect to each other.
// Main.js
import HomePage from '../pages/HomePage';
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/research" component={ResearchPage} />
// HomePage/index.js
import Utility from './components/Utility';
import './styles.scss';
...
render(){
<div className="home-page">
<Utility />
</div>
}
// HomePage/styles.scss
.home-page {
...
}
// Utility/index.js
import './styles.scss';
...
render(){
return (
<div className="utility-comp">
...
</div>
}
// Utility/styles.scss
.utility-comp {
...
}
React made a SPA which mean: single page application.. all the resources are loaded on app startup, if other resource are loaded in a second moment this resources became available in the entire applications.
I think that to avoid conflict you need to work on better target your stile, for example you can wrap your page in a container with the pagename as class, for example:
class Home extends React.Component {
render() {
return (
<div classname="home">
<Utility />
</div>
);
}
}
class Contact extends React.Component {
render() {
return (
<div classname="contact">
/*...*/
</div>
);
}
}
and then target your css using the page class:
.home h1{
color:red;
}
.contact h1{
color:blue;
}
Related
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
I am using the latest ReactJS with Create React App + Typescript, and I am using CSS modules. The App architecture would be:
/components
SignIn/
SignIn.module.scss
SignIn.tsx
ResetPassword/
ResetPassword.module.scss
ResetPassword.tsx
Inside each FC component, I include the CSS like this:
SignIn Component
import React from 'react';
import styles from './SignIn.module.scss';
const SignIn: React.FC = props => {
...
return {
<div className={styles.container} />
}
}
...
ResetPassword Component
import React from 'react';
import styles from './ResetPassword.module.scss';
const ResetPassword: React.FC = props => {
...
return {
<div className={styles.card} />
}
}
...
I define react-router-dom like this...
...
<BrowserRouter>
<Switch>
<Route path='/sing-in' component={SignIn} />
<Route path='/reset-password' component={ResetPassword} />
</Switch>
</BrowserRouter>
...
The issue:
React inserts the <style> of each component regardless if I am in the Sign-in page or in the reset-password page.
Expected behaviour:
React inserts only the CSS for the component displaying in the current page.
I have tried to add the prop exact in each Route but it didn't work.
I have built react.js site from create-react-app.
But in production mode, there is FOUC because styles are loaded after html is rendered.
Is there any way to resolve this? I have been searching google for answers, but haven't found proper one yet.
FOUC
FOUC - so called Flash of Unstyled Content can be as very problematic as so many tries of solving this issue.
To the point
Let's consider following configuration of routing (react-router):
...
<PageLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/example' component={Example} />
<Switch>
</PageLayout>
...
where PageLayout is a simple hoc, containing div wrapper with page-layout class and returning it's children.
Now, let's focus on the component rendering based on route. Usually you would use as component prop a React Compoment. But in our case we need to get it dynamically, to apply feature which helps us to avoid FOUC. So our code will look like this:
import asyncRoute from './asyncRoute'
const Home = asyncRoute(() => import('./Home'))
const Example = asyncRoute(() => import('./Example'))
...
<PageLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/example' component={Example} />
<Switch>
</PageLayout>
...
to clarify let's also show how asyncRoute.js module looks like:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Loader from 'components/Loader'
class AsyncImport extends Component {
static propTypes = {
load: PropTypes.func.isRequired,
children: PropTypes.node.isRequired
}
state = {
component: null
}
toggleFoucClass () {
const root = document.getElementById('react-app')
if (root.hasClass('fouc')) {
root.removeClass('fouc')
} else {
root.addClass('fouc')
}
}
componentWillMount () {
this.toggleFoucClass()
}
componentDidMount () {
this.props.load()
.then((component) => {
setTimeout(() => this.toggleFoucClass(), 0)
this.setState(() => ({
component: component.default
}))
})
}
render () {
return this.props.children(this.state.component)
}
}
const asyncRoute = (importFunc) =>
(props) => (
<AsyncImport load={importFunc}>
{(Component) => {
return Component === null
? <Loader loading />
: <Component {...props} />
}}
</AsyncImport>
)
export default asyncRoute
hasClass, addClass, removeClass are polyfills which operates on DOM class attribute.
Loader is a custom component which shows spinner.
Why setTimeout?
Just because we need to remove fouc class in the second tick. Otherwise it would happen in the same as rendering the Component. So it won't work.
As you can see in the AsyncImport component we modify react root container by adding fouc class. So HTML for clarity:
<html lang="en">
<head></head>
<body>
<div id="react-app"></div>
</body>
</html>
and another piece of puzzle:
#react-app.fouc
.page-layout *
visibility: hidden
sass to apply when importing of specific component (ie.: Home, Example) takes place.
Why not display: none?
Because we want to have all components which rely on parent width, height or any other css rule to be properly rendered.
How it works?
The main assumption was to hide all elements until compoment gets ready to show us rendered content. First it fires asyncRoute function which shows us Loader until Component mounts and renders. In the meantime in AsyncImport we switch visibility of content by using a class fouc on react root DOM element. When everything loads, it's time to show everything up, so we remove that class.
Hope that helps!
Thanks to
This article, which idea of dynamic import has been taken (I think) from react-loadable.
Source
https://turkus.github.io/2018/06/06/fouc-react/
Basically, I am using different css files for a reason. My problem is that when I am styling something in one css file, it affects some other components I have, not only the one where I import this css file.
I think the problem is with react-router. I think it shares css files from every component i import to it. When I stop using a component in router, then its css files are not shared.
But how do I do routes different way? I need to import all components to router to have links to my pages.
I also tried to put routes into seperate files, it didn't help as well.
That's how my file with routes looks like:
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route
} from 'react-router-dom';
import './App.css';
import LoginPage from '../pages/LoginPage.js';
import Navbar from './Navbar.js';
import MainPage from '../pages/MainPage.js';
import SportsfieldsListPage from '../pages/SportsfieldsListPage.js';
import MyProfilePage from '../pages/MyProfilePage.js';
import SingleObjectPage from '../pages/SingleObjectPage.js';
import ConfirmPage from '../pages/ConfirmPage.js';
import AdminPage from '../pages/AdminPage.js';
class App extends Component {
render() {
return (
<Router>
<div className="container">
<Navbar />
<Route exact path="/" component={MainPage} />
<Route exact path="/listaBoisk" component={SportsfieldsListPage} />
<Route exact path="/LoginPage" component={LoginPage} />
<Route exact path="/MyProfilePage" component={MyProfilePage} />
<Route path="/object/:id" component={SingleObjectPage}/>
<Route path ="/confirm/:id/:value" component={ConfirmPage}/>
<Route path ="/adminPage" component={AdminPage}/>
</div>
</Router>
);
}
}
export default App;
You can use CSS modules for your project CSS Modules.Using this, unique class names are generated.
If you dont want to do this, then u you can use CSS class nesting, for eg:
For Main Page Component
<div className="main-page">
<div className="title">Main Page</div>
</div>
use css like this:
.main-page .title{
color:red;
}
Similarity for other components use css like this
.login-page .title{
color:green;
}
This way, your css styles will not get mixed up
When doing server side rendering with react-router and Radium I get the following warning that appears to come from Radium appending css prefixes on the client but not on the server.
Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) 8.$=10"><div style="-webkit-transition:b
(server) 8.$=10"><div style="transition:backgroun
I tried to include radiumConfig within my server side rendering code, as shown below, but it doesn't appear to help. Do you have any suggestions?
match({ routes, location }, (error, redirectLocation, renderProps) => {
if (redirectLocation)
res.redirect(301, redirectLocation.pathname + redirectLocation.search)
else if (error)
res.status(500).send(error.message)
else if (renderProps == null)
res.status(404).send('Not found')
else
content = ReactDomServer.renderToString(<RoutingContext {...renderProps} radiumConfig={{userAgent: req.headers['user-agent']}} />);
markup = Iso.render(content, alt.flush());
});
And my routes look like the following, where the App component is wrapped by Radium:
export default (
<Route path="/" component={App}>
<Route path="login" component={Login} />
<Route path="logout" component={Logout} />
<Route name="test" path="test" component={Test} />
<Route name="import" path="import" component={ImportPlaylist} />
<Route name="player" path="/:playlist" component={Player} />
</Route>
);
Here is a working solution. I needed to create a Wrapper component as suggested by #joshparolin on Github.
Wrapper.jsx:
import React from 'react';
import Radium from 'radium';
#Radium
export default class Wrapper extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{this.props.children}
</div>
);
}
};
Server.jsx:
content = ReactDomServer.renderToString(<Wrapper radiumConfig={{userAgent: req.headers['user-agent']}}><RoutingContext {...renderProps} /></Wrapper>);
markup = Iso.render(content, alt.flush());
Client.jsx:
Iso.bootstrap((state, _, container) => {
alt.bootstrap(state);
ReactDOM.render(<Wrapper><Router history={createBrowserHistory()} children={routes} /></Wrapper>, container);
});