Is it possible to unload dynamic css imports in react? - javascript

I have two files which i load with react.lazy and suspense:
import React, { Suspense, lazy } from "react";
import { Route, Redirect } from 'react-router-dom';
const MainLayout = lazy(() => import('Components/Layout/MainLayout'))
export const PrivateRoute = () => (
<Route render={() => {
return (
localStorage.getItem('user') != null// validation that it is a valid user
?
<Suspense fallback={<div>Loading...</div>}>
<MainLayout/>
</Suspense>
: <Redirect to={{ pathname: '/login'}} />)
}} />
)
Second:
import React, { Suspense, lazy } from "react";
const DefaultLayout = lazy(() => import('Components/Layout/DefaultLayout'))
export const PublicRoute = () => (
<Suspense fallback={<div>Loading...</div>}>
<DefaultLayout/>
</Suspense>
)
the /login path is refrencing a component (login) that is inside of the
DefaultLayout component.
Scenario:
When the user is not logged in I load the DefaultLayout component which in turn contains my login component which imports cssFile1.css.
When the user enters the credentials i forward them to a path that is contained in my PrivateRoute where in turn i have cssFile2.css
The problem here is that cssFile1.css was loaded when i was using the loginpage but when the user logs in i want to unload cssFile1.css, is this possible and if yes then how?

I found a (sort of) reasonable way to do this in React. In short, you can lazy-load React components that contain the import './style.css', and when it loads, you can capture the imported StyleSheet to toggle its StyleSheet.disabled property later.
Here's my original solution to this problem.
Here's a Gist.

Ok, This might not be the most optimum approach, but can't the whole CSS inside the cssFile1.cssbe scoped? As in all the rules are written targetting elements if they are inside a certain container with a class say 'cssFile1'.
Likewise the 2nd CSS file will target all the elements only if they are located inside a container with the class 'cssFile2'.
now all you have to do to "unload/switch" the css is changing the main container class and the respective CSS rules will apply.
One last tip is, if you are using SASS / LESS its just a matter of enclosing the rules inside a container and all the rules will be scoped upon compilation.

If I understand right, then no, it's impossible, because of Webpack imported css when React compiled, anyway, you can try to css in js libs, such a Aphrodite.

Related

How to avoid inheritance of .css in React

I have a react application. The App.js fragment is as follow:
import ServiceManual from './components/pages/ServiceManual'
import './App.css';
const App = () => {
return (
<>
<Router>
<Switch>
<Route path='/ServiceManual' exact component={ServiceManual} />
<Route path='/' exact component={Home} />
</Switch>
</Router>
</>
)
};
The ServiceManual component contains it's own ServiceManual.module.css
import React from 'react';
import { Container } from 'react-bootstrap';
import '../App.css';
import './ServiceManual.module.css';
export default function ServiceManual() {
return <Container fluid className="mainPage">
....
Now, my problem is that some definitions contained into the ServiceManual.module.css influence the content of my Home page.
It looks like the import of the component in the App.js build an enormous container where all the imported definition will finish.
I found on the internet the suggestion to name xxx.module.css instead of xxx.css a .css file that has to be used just in the module where it is imported. But this is not true.
When I open the browser debugger on my home page I see some .css definitions (defined in xxx.module.css) that shouldn't be there and that change the way my homepage is displayed.
Has anyone a hint, please?
The truth is, all CSS files are included in the page as long as they are imported, no matter where.
If you need different styles for different elements, give them unique class names and handle them by their class names in their various CSS scripts.

React.lazy() import can't find module if its path is from a constant

I'm trying to implement a contextual sidebar based on the route. The idea is that the container page is the same but the sidebar and some of the content is dynamically loaded.
React.lazy() does exactly what I want but for some reason, it only works if I define my module path as an actual string:
const SideNav = React.lazy(() =>
import("./navigation/concepts") // this is fine
);
However, I would very much like to make this dynamic so I could define a map of routes to module locations as such:
const module_map = {"/docs/concepts" : "./navigation/concepts"};
console.log(module_map[this.props.location.pathname] === "./navigation/concepts"); // Just for sanity: this print 'true'
const SideNav = React.lazy(() =>
import(module_map[this.props.location.pathname])
);
This results in an error:
Error: Cannot find module './navigation/concepts'
I tried defining the module path first as a constant and even passing it as a property from the parent component but the result is the same.
Any ideas why this is happening and how can I resolve it?
use __dirname instead of using relative paths.
import React, { lazy, Suspense } from "react";
const file = `${__dirname}/components/MyComponent`;
const MyComponent = lazy(() => import(file));
export default function App() {
return (
<Suspense fallback="Loading">
<MyComponent />
</Suspense>
);
}
codesandbox

Is this the correct way of using React.Suspense?

I have several questions about React.Suspense.
Here is some example code for reference:
// components/ComponentA/index.js
import React from "react";
const style = {
display: "grid",
placeItems: "center"
};
export default function ComponentA() {
return <div style={style}>I am componentA</div>;
}
// components/ComponentB/index.js
import React from "react";
const style = {
display: "grid",
placeItems: "center"
};
export default function ComponentB() {
return <div style={style}>I am componentB</div>;
}
// App.js
import React from "react";
import "./styles.css";
const ComponentA = React.lazy(() => import("./components/ComponentA"));
const ComponentB = React.lazy(() => import("./components/ComponentB"));
export default function App() {
const [state, toggleState] = React.useReducer((state) => !state, false);
return (
<div className="App">
<button onClick={toggleState}>click to show</button>
<React.Suspense fallback={<div>Loading...</div>}>
{state ? <ComponentA /> : <ComponentB />}
</React.Suspense>
</div>
);
}
When running this code, I've noticed several things right away.
The first thing I noticed is that when the app loads for the first time, there is a split moment where you can see loading... before I am componentB is rendered.
The second thing I noticed is that if you click the button, you can, again, for a split moment see loading... before I am componentA is rendered on the screen.
I assume this is the expected result with dynamic imports.
The final thing that I noticed is that continuing to toggle the button, I never see loading... again. I assume that this is because the components have already been imported and used by the client. Which is, I also assume, expected behavior.
My three questions here are,
am I using React's dynamic imports correctly (or should I use import elsewhere) and,
when should React.Suspense and dynamic imports be used
If the components are relatively simple, do I need to even consider lazy loading components?
Sandbox for reference
For the First question -
Yes, you are using the lazy() function to import components Correctly.
For the second question -
From the React docs,
Suspense lets components “wait” for something before rendering.
It is used to improve your site's performance with regards to its First Contentful Paint and Loading times on slow or bad networks, as well as for asynchronous tasks like data fetching where your component depends on the data returned so you show a Loading.. message to the user as a Fallback.
For example, a component which I have written and used-
import React, { Component, lazy, Suspense } from "react";
import { Route, Switch } from "react-router-dom";
import Error404 from "./Error404";
import LoadingSpinner from "./LoadingSpinner";
const Dashboard = lazy(() => import("./Dashboard"));
const Employees = lazy(() => import("./EmployeesPage"));
class Container extends Component {
//handles paths '/' and '/employees' along with any unknown paths
render() {
return (
<div id="container">
<div id="background">
</div>
<div id="content">
<Switch>
<Route exact path="/">
<Suspense fallback={<LoadingSpinner />}>
<Dashboard />
</Suspense>
</Route>
<Route path="/employees">
<Suspense fallback={<LoadingSpinner />}>
<Employees />
</Suspense>
</Route>
<Route component={Error404} />
</Switch>
</div>
</div>
);
}
}
export default Container;
Here, I am lazy loading my two components Dashboard and Employees which in themselves contain many components and are relatively complex.
Lazy loading them prevents the connection to be clogged while loading the site for the first time.
These components are now loaded only when the user navigates to the specified URL by some NavLinks.
Since it's safe to assume that the user will spend some time in the Dashboard before going to the Employees page(at least in my app), this approach works fine.
For the third question -
If you have components that you are sure will be needed when the site loads, it's better to not lazy load them to keep the User Experience good.
Small components (by their bundle size) don't need lazy loading, only when you think that many components are gonna be used only rarely, then you can lazy load them in one bundle.
Lastly -
You can open up the Network tab on the developer tools of a browser and see the bundles loading in as the user requests for them (React handles that in lazy()). The Bundles are labelled like this- Network Tab screenshot
Hope you find the answer useful and any recommendations for the post are appreciated✌

Remove Unused JS/CSS bundles Code Splitting Webpack

I hope I can keep this brief.
The basic jist of my issue stems from SASS imports.
I have an App Component that handles all the routing.
In the imports is a Login Component. In this component is an a .scss import
import './Login.scss';
In this file is imports of various other .scss files.
Another route in my App component is ForgotPassword component.
Similar to Login it has a .scss import of its own.
Now, the issue is, if a user loads the Login component, it means that all the css and imports are stored in a css file and that carries over to every other component in my project.
Let's say one of the .scss imports was something like Card which styles .Card classes. I have a variable
$Card-padding: 15px !default;
In ForgetPassword I also have that import. First issue is that I now have two imports of Card and if I try to override the $Card-padding variable it will also override on Login.
I am currently using react-loadable in my App component where I handle my routes.
import React from "react";
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Loadable from 'react-loadable';
import './App.scss';
const Login = Loadable({
loader: () => import('../../login/Login'),
loading() {
return null;
}
});
const ForgotPassword = Loadable({
loader: () => import('../../forgot-password/ForgotPassword'),
loading() {
return null;
}
});
const App = () => {
return (
<div className="App">
<Router>
<div className="App-container">
<Switch>
<Route path="/login" component={() => <Login />} exact />
<Route path="/forgotpassword" component={() => <ForgotPassword />} exact />
</Switch>
</div>
</Router>
</div>
);
}
export default App;
My question really is, can I remove unused CSS with React and Webpack, or is there a better method of css containment so that it cannot be accessed outside of the component?
I feel like this is something that lots of people do, but I cannot seem to find out how to do this. There may be a term that I have missed which would lead be to the correct resources. But I am lost. I'm not a huge fan of css in js, a lot of the styling comes from another project that is similar so is much simpler to copy it over other than having to write css in js for each class and permutation.
Every question on this on here does not have an accepted answer, so I'm throwing a Hail Mary in the hopes that someone has a React/Webpack solution to this.
I had tried ExtractTextPlugin in webpack, but it never seemed to spit out the Login.scss, so I may have configured it incorrectly. I am currently using MiniCssExtractPlugin to compile my scss files.
if I try to override the $Card-padding variable it will also override
on Login.
I don't believe you should be selectively overriding sass variables.
To override a css property, why not add specific css class for that component:
// global styles
.card {
padding: $Card-padding;
}
login.scss:
$mynewpadding: 100px;
.card.login {
// override the default padding here
padding: $mynewpadding;
}
forgottenpassword.scss:
$myothernewpadding: 30px;
.card.forgottenpassword {
// overrider the default padding here
padding: $myothernewpadding;
}
imho the above would be much clearer and easier to maintain..

React routes not automatically updating when nested under 'smart' Redux container components

I'm trying to create an Electron app using React, React-router and Redux. What I'm finding is that my routing logic works absolutely fine when I'm nesting the switch/route logic under a purely presentational component (Page), but that I'm forced to refresh the page to see navigational changes if nested under a 'smart' container component.
Near the top of my React component hierarchy (right beneath HashRouter) I have a Page:
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
<DetailPane>{props.children}</DetailPane>
</div>
);
}
Here, DetailPane and SideBar are both container components wrapped around presentational components of the same name.
At startup (and during hot reloads), I create my React hierarchy using this function:
export default () => (
<Router>
<Page>
<Switch>
<Route exact path='/txDefinitions/:definitionName/:fieldName' component={FieldPage}/>
<Route exact path='/txDefinitions/:definitionName?' component={DefinitionPage}/>
<Route exact path='/rxDefinitions/:definitionName?' component={DefinitionPage}/>
<Route exact path='/'/>
<Route component={Route404}/>
</Switch>
</Page>
</Router>
This means that <Switch>...</Switch> gets nested underneath <DetailPane>.
If I try to navigate around my app (clicking links in the side bar), I won't actually see the detail pane render the new component until I force-reload the Electron app.
However, I find that routing works as expected if I omit DetailPane from Page:
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
{props.children}
</div>
);
}
Here is my React hierarchy without DetailPane (works fine):
Here is my React hierarchy with DetailPane (does not work right):
(Apologies for using images but I'm not sure if there's a way to copy from React devtools into clipboard - appears larger if opened in a new tab).
As I was writing this question, I realised this wouldn't be a huge issue for me because earlier refactoring had made the 'smart' version of DetailPane apparently obsolete. Using the purely presentational version of DetailPane
instead resolves this issue:
import * as React from 'react';
//import {DetailPane} from '../../containers'; // Smart/Redux
import {DetailPane} from '../../components'; // Dumb/presentational
import {SideBar} from '../../containers/';
const styles = require('./Page.scss');
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
<DetailPane>{props.children}</DetailPane>
</div>
);
}
However, I'm still curious why this doesn't work for the container component version. For reference, this is the container component version of DetailPane:
import {connect} from 'react-redux';
import {DetailPane} from '../../components';
// TODO: delete this container?
function mapStateToProps (state): {} {
return {};
}
function mapDispatchToProps (dispatch) {
// TODO.
return {};
}
export default connect(mapStateToProps, mapDispatchToProps)(DetailPane);
The connect HOC implements shouldComponentUpdate logic so if the props don't change, the component doesn't update.
To prevent this from occurring, and have the component always render, you can override the pure option in the connect call.
export default connect(mapStateToProps, mapDispatchToProps, undefined, { pure: false })(DetailPane);
See the react-redux API docs for more details.

Categories

Resources