Is this the correct way of using React.Suspense? - javascript

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✌

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.

What is the right way to elevate props in React?

I apologize if my phrasing is wrong in the title. I've recently gotten cookies going in my app. My Topnav component needs access to them, but I'm unsure how to get them there.
App.js -
import React, { Component } from 'react';
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import Landing from './pages/Landing.js';
import LoginPage from './pages/LoginPage.js';
import Topnav from './pages/components/global/Topnav.js';
import './Global.css';
const App = props => (
<div>
<Topnav />
<Switch>
<Route exact path='/' component={Landing} />
<Route exact path='/login' component={LoginPage} />
</Switch>
</div>
);
export default App;
My login component grabs the cookie from express (fetch) and then does a <Redirect to='/' />
This loads up my Landing page, where I'm able to grab the cookie, but how do I get the cookie to the Topnav? I saw an answer to something like this on stack where it seems like App.js grabs the cookie and passes it as a props to the components, but I don't see how it could if it never refreshes. I've thought about forcing an entire window refresh (which does work for Topnav when I do a refresh manually), but I've also seen answers here that say don't do that.
Use Context
You need to use the new context hook from react.
Create a context
This is a context that you can access around your app.
const MyContext = React.createContext(defaultValue);
Make a provider
Wrap the provider around your main app
<MyContext.Provider value={/* some value */}>
Access the context at the point at which you get the cookies
Use this in both your login and top nav to use the value from the context
const value = useContext(MyContext);
There are multiple ways to approach this.
Probably a beginner friendly one.
When your login Component does the login successfully you need to signal to the App Component about it probably using a onLoginSuccessful which can then read the cookie and do a setState with it and use this component state value in the props to your Topnav and Landing Component

Is it possible to unload dynamic css imports in react?

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.

ReactJS - Accessing data on Different Pages

I am trying to access data gathered from a user on one page and use it on another page. I have tried following these articles:
https://travishorn.com/passing-data-between-classes-components-in-react-4f8fea489f80
https://medium.com/#ruthmpardee/passing-data-between-react-components-103ad82ebd17
https://codeburst.io/react-js-pass-data-from-components-8965d7892ca2
I have not been able to get it to work. this.props.{variableName}keeps returning as undefined. My code is as follows.
The following is the Home Page:
import React, { Component } from 'react';
import {Button} from 'reactstrap';
import { browserHistory } from 'react-router';
class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {
working: "",
};
}
WorkReqNav(){
this.setState=({working: "WORKING"});
browserHistory.push("/WorkReq");
}
render() {
return (
<div>
<Button size="lg" onClick={this.WorkReqNav.bind(this)} type='button'>HIT IT!</Button>
</div>
);
}
}
export default HomeScreen;
The following is the workReq screen:
import React, { Component } from 'react';
import {Button} from 'reactstrap';
class WorkReq extends Component {
constructor(props) {
super(props);
}
workCheck(){
var working = this.props.working;
alert(working);
}
render() {
return (
<div>
<Button size="lg" onClick={this.workCheck.bind(this)} type='button'>HIT IT!</Button>
</div>
);
}
}
export default WorkReq;
If you need anything more, please let me know. i am really new to React and this is my first time attempting anything like this.
welcome to the React world. I bet you'll love it when you gradually get familiar with cool stuff that you can do with React. Just be patient and keep practicing.
So the first suggestion I would make is that, like any other javascript environment, React also evolves very quickly. So although basic principles are the same, when you follow a new article on one hand, on the other hand you can check if the libraries or methodologies that are demonstrated are up to date.
Fasten your belts and let's do a quick review based on your question and libraries that I see you used in your example.
In terms of router, I see that you directly export things from react-router
When we check the npm page (https://www.npmjs.com/package/react-router) of react-router they make the following suggestion
If you are writing an application that will run in the browser, you
should instead install react-router-dom
Which is the following package https://www.npmjs.com/package/react-router-dom
You can get more details and find more tutorials in order to improve your skills by checking their official page https://reacttraining.com/react-router/web/guides/philosophy
Let's take a look at the code snippet sasha romanov provided that's based on react-router-dom syntax
with react-router-dom when you define a route with following syntax
<Route path="/" component={HomePage} exact />
react-router-dom automatically passes match, location, and history props to HomePage component. So when you console.log() these props, you should be able to display somethings on your console. And once you have access to history props, instead of browserHistory, you can use this.props.history.push("/some-route") for redirections.
Let's take a look at the part related to withRouter. In the example above, we could use history because HomePage component was passed directly to the Router component that we extract from react-router-dom. However, in real life, there might be cases in which you want to use history props in a component that's not passed to the Router but let's say just a reusable button component. For these cases, react-router-dom provides a Higher Order Component called withRouter
A Higher Order Component is (from React's official documentation)
https://reactjs.org/docs/higher-order-components.html
Concretely, a higher-order component is a function that takes a
component and returns a new component.
So basically, whenever you wrap any component with withRouter such as export default withRouter(MyWrappedReusableComponent), in your reusable component, you will have access to the props history, location, pathname
That said, my first impression regarding to your problem does not seem to be related to router logic but rather exchanging data between components.
In your original question, you mentioned that
I am trying to access data gathered from a user on one page and use it on another page
There are a couple of cases/ways to approach this issue
1) If these two components are completely irrelevant, you can use state management system such as Redux, mobx or you can use React's context API https://reactjs.org/docs/context.html. HOWEVER, since you are new to React, I would suggest not tackle with these right know till you are comfortable with the basic flow. Because at some point trying to implement a flow with a lot of libraries etc. is quite overwhelming. Believe me, I tried when I was also new to React and I was really close to break my computer after opening my 100th browser tab to look for another method from another library
2) You can implement a simple parent-child relationship to pass data between components. Let me explain what I mean by using references from your code snippet.
I believe you want to update working which is a state in your HomeScreen and you want to pass and use this updated value in your WorkReq component.
If we ignore all the routing logic and decide to go without routes, what you need to do is the following
import React, { Component } from 'react';
import {Button} from 'reactstrap';
import { browserHistory } from 'react-router';
import WorkReqComponent from 'path/to/WorkReqDirectory';
class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {
working: "WORKING",
};
}
render() {
return (
<div>
<WorkReqComponent working={this.state.working} />
</div>
);
}
}
By this way, when you log this.props.working; in your WorkReqComponent you should be able to display the data that you passed. You can refer to this as passing data from parent to child.
I checked the articles you listed. They also seem to explain data transfer between parent to child, child to parent or between siblings.
In your case, what you really need to implement can be categorized as between siblings
I prepared a sample for you with react-router-dom to demonstrate one possible structure which might yield your expected outcome.
https://codesandbox.io/s/ojp2y0xxo6
In this example, the state is defined inside of the parent component called App. Also state update logic is also defined inside of the parent component. HomeScreen and WorkReq components are the children of App thus they are siblings. So, in order to transfer data between siblings, one of them was given the task of updating parent's state via passing state update logic to this component. The other one has the task of displaying parent's state's value.
At this point, since you are new and in order not to overwhelm yourself, you can experiment with parent-child-sibling data transfer topic. Once you are getting comfortable with the implementation and the logic, you can gradually start taking a look at React's context api and Redux/mobx.
Let me know if you have any questions regarding to the sample I provided
You can use react-router-dom lib and from seeing your code i think in parent component (app.js) you defined route for each child component you'd like to access
like this example here:
import { BrowserRouter, Route, Switch } from 'react-router-dom';
<BrowserRouter>
<div>
<Switch>
<Route path="/" component={HomePage} exact />
<Route path="/homescreen" component={HomeScreen} />
<Route path="/workreq" render={(props) => <WorkReq {...props} />} /> // here you can pass the props by calling render
<Route component={NoMatch} />
</Switch>
</div>
</BrowserRouter>
and then if you want to change route you can just call this.props.history.push('/workreq')
and if you didn't include route for the component in <BrowserRouter />
in the component that it's not included you can import withRouter and export like this withRouter(HomeScreen) and now you can access router props
if this isn't the answer you are looking please inform me to update my answer, i hope this can help

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