I'm trying to apply code splitting to my React app. The below code is my routes with code splitting applied by Loadable from react-loadable. With the implementation, every time I access each page, the code chunk from webpack is properly loaded. However, when I access my webpage initially, it brings about screen flicker initially. After that, if I move to another page, no more screen flicker. It only happens when accessing my website initially.
Does anyone know why it's happening? Is there something wrong on my configuration?
By the way, I used server-side rendering with Node.js in my React app. Does it have something to do with screen flicker by code splitting?
routes.jsx
import React from "react";
import Loadable from "react-loadable";
import { Route, Switch } from "react-router-dom";
import NotFound from "routes/not-found";
const Landing = Loadable({
loader: () => import(/* webpackChunkName: "landing" */ "routes/landing"),
loading: () => null,
modules: ["landing"]
});
const About = Loadable({
loader: () => import(/* webpackChunkName: "about" */ "routes/about"),
loading: () => null,
modules: ["about"]
});
export default props => {
return (
<Switch>
<Route
exact
path="/"
render={routeProps => <Landing {...routeProps} options={options} />}
/>
{/* <Route exact path="/" component={Homepage} /> */}
<Route
exact
path="/about"
render={routeProps => <About {...routeProps} options={options} />}
/>
<Route component={NotFound} />
</Switch>
);
};
src/index.js
const { store, history } = createStore();
const generateClassName = createGenerateClassName();
const Application = (
<JssProvider generateClassName={generateClassName}>
<MuiThemeProvider theme={theme}>
<Provider store={store}>
<ConnectedRouter history={history}>
<Frontload noServerRender={true}>
<App />
</Frontload>
</ConnectedRouter>
</Provider>
</MuiThemeProvider>
</JssProvider>
);
const root = document.querySelector("#root");
if (root.hasChildNodes() === true) {
// If it's an SSR, we use hydrate to get fast page loads by just
// attaching event listeners after the initial render
Loadable.preloadReady().then(() => {
hydrate(Application, root, () => {
document.getElementById("jss-styles").remove();
});
});
} else {
// If we're not running on the server, just render like normal
render(Application, root);
}
the flickering is happening because the router don't have the source of your component when routing and it renders null as you defined in loading func provided to Loadable HOC
to prevent flickering you can render a Loading component that should look something like this: https://github.com/osamu38/react-loadable-router-example/blob/master/src/components/Loading.js
or you can preload all the pages onComponentDidMount like this (you should convert your component to class component for it):
componentDidMount = () => {
About.preload()
Landing.preload()
}
Related
Currently have a problem where when my users use their browsers built in forward/back buttons it doesn't reload the state of my page. (Due to the states being set and rendered in a useEffect)
To fix this I have tried to monitor when the location changes (eg, they go to another route) when this happens I force reload the page.
However, this results in an infinite loop of refreshing. I'm not sure how to counter this?
I am on react router v6.3
export const App = () => {
let location = useLocation();
useEffect(() => {
window.location.reload();
}, [location]);
return (
<Routes>
<Route exact path="/" element={<HomePage />} />
<Route exact path="/example" element={<Callback />} />
</Routes>
);
};
export const Router = () => {
return (
<BrowserRouter>
<App />
</BrowserRouter>
);
};
I'm testing if my components render with Redux successfully with React Testing Library. I'm having trouble having my utility component to pass the renderWithRedux test. This is my App component.
function App() {
return (
<>
<Router>
<NavBar />
<div className="container">
<Switch>
<Route exact path='/' component={Home}/>
<AuthRoute exact path='/login' component={Login} />
<AuthRoute exact path='/signup' component={Signup} />
<Route exact path='/users/:handle' component={UserProfile} />
<Route exact path='/users/:handle/post/:postId' component={UserProfile} />
</Switch>
</div>
</Router>
</>
);
}
Here is my AuthRoute utility component.
const AuthRoute = ({ component: Component, authenticated, ...rest }) => (
// if authenticated, redirect to homepage, otherwise redirect to signup or login
<Route
{...rest}
render={(props) =>
authenticated === true ? <Redirect to='/' /> : <Component {...props} />
}
/>
);
AuthRoute.test.js
const renderWithRedux = () => render(
<Provider store={myStore}>
<AuthRoute />
</Provider>
);
it('renders with Redux', () => {
const {} = renderWithRedux(<AuthRoute />);
});
I've attempted the solutions from Invariant failed: You should not use <Route> outside a <Router>, but to no avail. I appreciate any help, thank you.
Render the component under test into a router
import { MemoryRouter } from 'react-router-dom';
const renderWithRedux = ({ children }) => render(
<Provider store={myStore}>
{children}
</Provider>
);
it('renders with Redux', () => {
const {} = renderWithRedux(
<MemoryRouter>
<AuthRoute />
</MemoryRouter>
);
});
Just like the Provider to wrap redux things you have to wrap your components with routes using MemoryRouter for the tests.
import { MemoryRouter } from 'react-router';
Basically, you have two wrapper elements. It should go something like this, for example, renderWithReduxWrapp => renderWithRouter => YourTestingComponent.
I had a similar issue when trying to test Button render (which has a Link) depending on props, and was able to solve it by creating some helper functions.
Here is the example:
This is the main component, UserCard.js, which renders user data from redux, and only shows a button if withButton props is passed.
import React from "react";
import { Link } from "react-router-dom";
import { Button } from "react-bootstrap";
const CardComponent = ({ withButton }) => {
const userInfo = useSelector((state) => getUserSelector(state));
return (
<div>
<div>{userInfo}</div>
{withButton && (
<Link to="/settings" className="button-link">
<Button block>EDIT CONTACT INFO</Button>
</Link>
)}
</div>
);
};
export default CardComponent;
This is a CardComponent.test.js file.
First, you need to add these lines of code
const ReduxWrapper = ({ children }) => {
<Provider store={store}>{children} </Provider>;
}
const AppWrapper = ({ children }) => (
<BrowserRouter>
<ReduxWrapper>{children}</ReduxWrapper>
</BrowserRouter>
);
const renderWithRouter = (ui, { route = '/' } = {}) => {
window.history.pushState({}, 'Test page', route);
return render(ui, { wrapper: AppWrapper });
};
After that, you need to start your test with renderWithRouter instead of just render method.
it('should render settings button if prop withButton is passed', () => {
renderWithRouter(<CardComponent withButton />, { wrapper: ReduxWrapper });
// apply you code here. I only needed to check if the button is renederd or not.
const settingsButton = screen.queryByText(/edit contact info/i);
expect(settingsButton).toBeInTheDocument();
});
While I'm ultimately trying to write an Enzyme test for the flow in this react router example: https://reacttraining.com/react-router/web/example/auth-workflow
import React from 'react';
import ReactDOM from 'react-dom';
import Enzyme, { shallow, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { MemoryRouter, Route, Switch, Link } from 'react-router-dom';
const Home = () => <div>Home</div>;
const MockComp = () => (
<div className="protected">
<nav>hi</nav>
Protected
</div>
);
const MockDenied = () => <div className="denied">Denied</div>;
test('Renders visited protected component if authorized', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<div>
<Link to="/foo" />
<Switch>
<Route path="/" component={Home} />
<Route path="/401" component={MockDenied} />
<ProtectedRouteBasic
path="/foo"
auth={{ hasAuth: true }}
component={MockComp}
/>
</Switch>
</div>
</MemoryRouter>
);
wrapper.find('a').simulate('click', { button: 0 });
expect(wrapper.find('.protected').length).toEqual(1);
expect(wrapper.find('.denied').length).toEqual(0);
});
I've found a number of issues and have tried to peel away the complexity and then slowly reintroduce the elements that I've removed.
So I've landed on this test as what I will need to get working to proceed:
test('Clicking link will render component associated with path', () => {
const wrapper = mount(
<MemoryRouter>
<div>
<Link to="/foo" />
<Switch>
<Route path="/" component={Home} />
<Route path="/foo" component={MockComp} />
</Switch>
</div>
</MemoryRouter>
);
wrapper.find('a').simulate('click', { button: 0 });
expect(wrapper.find('.protected')).toHaveLength(1);
});
However, this test isn't working as expected as I expect the test to pass in its current state. I've read this thread to update my simulate call to include the {button: 0} as well as this thread about wrapping the entire router in a functional component, however, that option's not available to me as far as I know, since the framework I'm working with doesn't seem to allow for it. Additionally, I believe that that piece is immaterial to the issue I'm having. That said, any help would be much appreciated.
From the Switch docs:
Renders the first child <Route> or <Redirect> that matches the location.
In this case <Route path="/" component={Home} /> matches when the path is both / and /foo so Home is always rendered.
You can fix this by using either exact so it only matches if the path is exactly /, or moving it to the end of the Route list so other routes match first:
test('Clicking link will render component associated with path', () => {
const wrapper = mount(
<MemoryRouter>
<div>
<Link to="/foo" />
<Switch>
<Route path="/foo" component={MockComp} />
<Route path="/" component={Home} />
</Switch>
</div>
</MemoryRouter>
);
wrapper.find('a').simulate('click', { button: 0 });
expect(wrapper.find('.protected')).toHaveLength(1); // SUCCESS
});
I'm using lazy to split my routes and I wanna know if there is any way I can get the loading progress in lazy and suspense.
Currently I'm using it like this.
const Home = lazy(() => import("./Home"));
const About = lazy(() => import("./About"));
function App() {
return (
<Router>
<Switch>
<Suspense fallback={<div>loading</div>}>
<Route path="/" exact={true} component={Home} />
<Route path="/About" component={About} />
</Suspense>
</Switch>
</Router>
);
}
■ But I want to show the loading progress (like youtube).
■ Is there any way I can retrieve the progress for example like below.
<Suspense fallback={({progress}) => <LoadingBar progress={progress}/>}>
lazy uses JavaScript promises which either resolve or reject, but they don't report any progress.
You can however fake the progress with something like http://ricostacruz.com/nprogress/ which increments the progress bar randomly.
This is my solution:
const LazyLoad = () => {
useEffect(() => {
NProgress.start();
return () => {
NProgress.stop();
};
});
return '';
};
<Suspense fallback={<LazyLoad />}>
The javascript way
Install nprogress
npm i nprogress
Import CSS globaly
you may customize the nprogress layout in your global css to make it suitable for your website
import "nprogress/nprogress.css"
React lazy uses promise to suspense its component so we can use it to control our nprogress package
Lazy -----> Promise ----> resolve ----> render
here is how to do it
const MyPage = React.lazy(() => mew Promise(
(resolve, reject) => {
NProgress.start()
import("path/to/page").then(() => {
NProgress.done()
resolve(module)
}).catch(err) => {
NProgress.done()
reject(err)
})
})
)
then you can wrap it into a suspense component and add a loading.
<Route path="/" exact={true} component={
{/* i use a custom loading component (dot animations) */}
<Suspense fallback={() => "..."}>
<MyPage />
</Suspense>
</Route>
this would be a close example of what you want, however you can't make the loading as real as the promise itself. usually others use this solution aswell, so don't overthink it and leave it as is
I am using React (16.3.2) with TypeScript (2.8.3), Keycloak-js (3.4.3) and React Router 4 (4.2.2) together. Here is the Keycloak init:
const auth = Keycloak('./../keycloak.json');
const init = () => {
return auth.init({ onLoad: 'login-required', checkLoginIframe: false });
};
The keycloak.json file is stored in public folder
I do Keycloak initialization before ReactDOM.render method:
import { init } from './auth';
init()
.success((authenticated: boolean) => {
if (authenticated) {
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root') as HTMLElement
);
} else {
console.log('not authenticated');
}
})
.error(() => {
console.log('failed to initialize');
});
Then the App (ThemeProvider comes from styled-components):
const App: React.SFC<Props> = ({ theme }) => {
return (
<BrowserRouter>
<ThemeProvider theme={theme}>
<Switch>
<Redirect from="/" exact={true} to="/books" />
<Route path="/books" component={BooksList} />
<Route component={Error404} />
</Switch>
</ThemeProvider>
</BrowserRouter>
);
};
Then the BooksList:
const BooksList: React.SFC<RouteComponentProps<void>> = ({ match }) => {
return (
<ColumnView>
<Switch>
<Route path={match.url} component={List} />
</Switch>
<Switch>
<Route path={match.url} exact component={EmptyView} />
<Route path={match.url + '/details/:id'} component={BookDetails} />
<Route component={Error404} />
</Switch>
</ColumnView>
);
};
When I open my website on URL localhost:3000 everything works as it should. Keycloak renders a login page and I can navigate through the whole website. The problem appears when I want to enter a different URL by typing it to the browser, for example localhost:3000/books/details/11. Suddenly Keycloak starts to search for the keycloak.json file in a very different directory - not localhost:3000/keycloak.json but localhost:3000/books/details/keycloak.json.
The problem seems to be non existent when I write the localization of the configuration file as:
const auth = Keycloak('./../../../keycloak.json');
Where the number of '../' depends on how much nested my router is. This fixes everything.
So the solution is quite easy - I had to delete the single dot in front of the initialization URL to make the path direct not relative:
const auth = Keycloak('/../keycloak.json');