I'm trying to use React.lazy to lazy load a React Component.
The component looks like this:
function App() {
const HubScreen = React.lazy(() => import("./screens/hub").then((mod) => {
console.log(mod.default)
return mod.default;
}));
return (
<BrowserRouter>
<MuiThemeProvider theme={theme}>
<MainContaier>
<div id="screen">
<CssBaseline />
<Switch>
<React.Suspense fallback={<h1>Loading...</h1>}>
<Route exact path="/" component={HomeScreen} />
<Route path="/hub" render={() => <HubScreen />} />
</React.Suspense>
</Switch>
</div>
</MainContaier>
</MuiThemeProvider>
</BrowserRouter >
)
}
And this is the component I'm importing
import React from "react";
function HubScreen() {
return (
<div>Hi</div>
);
}
export default HubScreen;
When I navigate to /hub I see the value of mod.default as undefined. Along with my Chrome window becoming completely unresponsive, requiring a force stop.
I know that my path to the module ./screens/hub, is correct because, if I put a fake path like ./screens/hube then webpack gives me the error:
Module not found: Error: Can't resolve './screens/hube' in '/home/travis/Workspace/avalon/assets/js'
I'm stumped haven't found a similar problem anywhere.
This answer gave me some insight as to why my browser was hanging up. However I still seem to have the same root problem; the undefined module.default. After changing the root component to this:
const HubScreen = React.lazy(() => import("./screens/hub"));
function App() {
return (
<BrowserRouter>
<MuiThemeProvider theme={theme}>
<MainContaier>
<div id="screen">
<CssBaseline />
<Switch>
<React.Suspense fallback={<h1>Loading...</h1>}>
<Route exact path="/" component={HomeScreen} />
<Route path="/hub" component={HubScreen} />
</React.Suspense>
</Switch>
</div>
</MainContaier>
</MuiThemeProvider>
</BrowserRouter >
)
}
I get the War:
Warning: lazy: Expected the result of a dynamic import() call. Instead received: [object Object]
Your code should look like:
const MyComponent = lazy(() => import('./MyComponent'))
And then the error:
Uncaught Error: Element type is invalid. Received a promise that resolves to: undefined. Promise elements must resolve to a class or function.
Which I have taken to mean that undefined is being returned from the import resolution, as the console.log seems to confirm.
Also another possibility of getting this error is by missing the default export in your Component which becomes a named export.
This syntax is only supported for Default exports.
const HubScreen = React.lazy(() => import("./screens/hub"));
import React, {useState} from 'react';
const ChangeName = (props) => {
return (
<div>
<p>Name: {props.name}</p>
<p>Email: {props.email}</p>
<div>
<button onClick={()=>props.changeStatus()}>Change Details</button>
</div>
</div>
)
}
export default ChangeName; ====> By missing the default Export
First off, move your const HubScreen outside of your component. When App() rerenders, it will cause an infinate loop to keep trying to load HubScreen over and over again. Secondly, just use () => import... and let React.lazy use the default component exported. Additionally, you should not need to use render for the route. Just provide the component:
const HubScreen = React.lazy(() => import("./screens/hub"));
function App() {
return (
<BrowserRouter>
<MuiThemeProvider theme={theme}>
<MainContaier>
<div id="screen">
<CssBaseline />
<Switch>
<React.Suspense fallback={<h1>Loading...</h1>}>
<Route exact path="/" component={HomeScreen} />
<Route path="/hub" component={HubScreen} />
</React.Suspense>
</Switch>
</div>
</MainContaier>
</MuiThemeProvider>
</BrowserRouter >
)
}
Related
how is it possible to create lazy loading with this syntax?
const A = React.lazy(() => import("./A"));
const B = React.lazy(() => import("./B"));
function App() {
return (<Routes>
<Route
path="firstPath"
element={<A />} />
<Route
path="secondPath"
element={<B />} />
</Routes>)
}
I was thinking that with this syntax A and B will be called when we pass them into element props and lazy loading wouldn't be possible.
You can wrap all in one Suspense:
const Home = lazy(() => import('./home'));
const About = lazy(() => import('./about'));
function App() {
return (
<Router>
<Suspense fallback={<p> Loading...</p>}>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/about' element={<About />} />
</Routes>
</Suspense>
</Router>
);
}
It is, and you wrap the route's components in a Suspense wrapper.
React-router Lazy Loading
import { Suspense } from 'react';
const A = React.lazy(() => import("./A"));
const B = React.lazy(() => import("./B"));
function App() {
return (
<Routes>
<Route
path="firstPath"
element={
<Suspense fallback={<>...</>}>
<A />
</Suspense>
}
/>
<Route
path="secondPath"
element={
<Suspense fallback={<>...</>}>
<B />
</Suspense>
}
/>
</Routes>
);
}
I don't think the React Router API affords this.
Code splitting is orthogonal to module loading, i.e. it can happen correctly without having achieved the behavior of: loading modules only once routed to.
Suspense can be placed anywhere above, and it must be by the time we ask a question like this as the application will fault without it.
When you write <A /> and <B /> in this parent component's rendering, as you have, as the Route element prop values, this causes the lazy module loading to happen then, during App render for any route, as these expressions are renderings of A and B.
If the React-Router API permitted lazy loading, it would let you defer rendering the routed-to component until that route is selected.
The typical way any value's computation is deferred in EcmaScript is by creating a function that returns the result. For example, in some other imaginary Router API, one might be permitted to write instead () => <A /> and () => <B />.
If at the basic level we end up with, internal to the imaginary Router library, const SelectedRoute: React.FC = () => <A />, then the lazy load of the A module would commence only once render arrives at <SelectedRoute />.
Thank you for reading.
I am using the following material-ui theme Paperbase and within the Header.js component, I have the following useEffect hook:
const [temperature, setTemperature] = useState([]);
const getTemperature= async () => {
try {
const response = await fetch('/get-temperature')
const tempData = await response.json();
setTemperature(tempData);
} catch (err) {
console.error(err.message);
}
};
useEffect(() => {
getTemperature();
}, []);
The main purpose of this, is to display the current temperature as info, within the header component, which gets displayed at first page load/render.
Now within my App.js below, I have the following return setup where the above Header component is called.
return (
<Router>
<UserProvider myinfo={myinfo}>
<Switch>
<Route path="/">
<ThemeProvider theme={theme}>
<div className={classes.root}>
<CssBaseline />
<nav className={classes.drawer}>
<Hidden xsDown implementation="css">
<Navigator />
</Hidden>
</nav>
<div className={classes.app}>
<Header
onDrawerToggle={handleDrawerToggle}
/>
<main className={classes.main}>
<Switch>
<Route exact path="/new-user"
render={(props) => <Content key={props.location.key} />}
/>
<Route exact path="/view-results"
render={(props) => <ViewResults key={props.location.key} />}
/>
</Switch>
</main>
</div>
</div>
</ThemeProvider>
</Route>
</Switch>
</UserProvider>
</Router>
);
My question is, how can I trigger a rerender of Header (parent) whenever the user routes to either /new-user or /view-results which in turn calls either Content.js or ViewResults.js, inorder to make the useEffect in Header.js refresh the data, from the REST api fetch and display the latest temperature in the header again?
Ideally anytime Content.js or ViewResults.js is rendered, ensure that Header.js getTemperature() is called.
Any help would be much appreciated.
Your current code is pretty close to a multi layout system. As being a component child of Route, you can access the current location via useLocation() or even the native window.location.pathname.
This is my example of multi layout React app. You can try to use it to adapt to your code.
The MainLayout use a fallback route when no path is specified. It also contains a Header and include a page
const Dispatcher = () => {
const history = useHistory();
history.push('/home');
return null;
};
const App = () => (
<BrowserRouter>
<Switch>
<Route
component={Dispatcher}
exact
path="/"
/>
<Route
exact
path="/login/:path?"
>
<LoginLayout>
<Switch>
<Route
component={LoginPage}
path="/login"
/>
</Switch>
</LoginLayout>
</Route>
<Route>
<MainLayout>
<Switch>
<Route
component={HomePage}
path="/home"
/>
</Switch>
</MainLayout>
</Route>
</Switch>
</BrowserRouter>
);
And here is the code for MainLayout
const MainLayout = ({ children }) => (
<Container
disableGutters
maxWidth={false}
>
<Header location={props.location} />
<Container
component="main"
maxWidth={false}
sx={styles.main}
>
{children}
</Container>
<Footer />
</Container>
);
Now that Header can be anything. You need to put a capture in this component
import { useLocation } from 'react-router-dom'
cont Header = (props) => {
const { pathname } = useLocation();
//alternatively you can access props.location
useEffect(() => {
if (pathname === '/new-user') {
getTemperature();
}
}, [pathname]);
};
Note that Header is not a direct descendant of Route therefore it cannot access the location directly via props. You need to transfer in chain
Route -> MainLayout -> Header
Or better use useLocation
I have a web app which is under development which is just like google drive using firebase. I have this useParams() in Dashboard Screen which is the main page of the App with All the different Folder Routes. So for this screen i have used useParams and now when i console.log(params) it shows an empty object {} and also when i click the button it does not navigate only the URL changes
Github Code :- https://github.com/KUSHAD/RDX-Drive/
In App.js
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import PrivateRoute from './Components/Route/PrivateRoute';
import Dashboard from './Screens/Main/Dashboard';
import ViewProfile from './Screens/Profile/ViewProfile';
import Signup from './Screens/Auth/Signup';
import Login from './Screens/Auth/Login';
import ForgotPassword from './Screens/Auth/ForgotPassword';
function App() {
return (
<>
<div className='App'>
<div className='main'>
<BrowserRouter>
<Switch>
{/* Drive */}
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
{/* Profile */}
<PrivateRoute path='/profile' component={ViewProfile} />
{/* Auth */}
<Route path='/signup' component={Signup} />
<Route path='/login' component={Login} />
<Route path='/forgot-password' component={ForgotPassword} />
</Switch>
</BrowserRouter>
</div>
</div>
</>
);
}
export default App;
In Dashboard.js
import NavBar from '../../Components/Shared/NavBar';
import Container from 'react-bootstrap/Container';
import AddFolderButton from '../../Components/Main/AddFolderButton';
import { useDrive } from '../../services/hooks/useDrive';
import Folder from '../../Components/Main/Folder';
import { useParams } from 'react-router-dom';
export default function Dashboard() {
const params = useParams();
console.log(params);
const { folder, childFolders } = useDrive();
return (
<div>
<NavBar />
<Container fluid>
<AddFolderButton currentFolder={folder} />
{childFolders.length > 0 && (
<div className='d-flex flex-wrap'>
{childFolders.map(childFolder => (
<div
key={childFolder.id}
className='p-2'
style={{ maxWidth: '250px' }}>
<Folder folder={childFolder} />
</div>
))}
</div>
)}
</Container>
</div>
);
}
Issue
After scouring your repo looking for the usual suspect causes for "it does not navigate only the URL changes" I didn't find anything odd like multiple Router components, etc. I think the issue is your PrivateRoute component isn't passing the props to the Route correctly. You're destructuring a prop called rest and then spread that into the Route, but you don't pass a rest prop to the PrivateRoute
export default function PrivateRoute({ component: Component, rest }) { // <-- rest prop
const { currentUser } = useAuth();
return (
<Route
{...rest} // <-- nothing is spread/passed here
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
The routes, these are not passed any prop named rest:
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
What I believe to be occurring here is the exact and path props aren't passed to the underlying Route component and so the first nested component of the Switch is matched and rendered, the "/" one that doesn't have any route params.
Solution
The fix is to spread the rest of the passed props into rest instead of destructuring a named rest prop.
export default function PrivateRoute({ component: Component, ...rest }) {
const { currentUser } = useAuth();
return (
<Route
{...rest}
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
An improvement of your private route may be as follows:
export default function PrivateRoute(props) {
const { currentUser } = useAuth();
return currentUser ? (
<Route {...props} />
) : (
<Redirect to='/login' />
);
}
This checks your user authentication and renders either a Route or Redirect. This pattern allows you to use all the regular Route props so you aren't locked into using the render prop to render the component.
I have just updated my React app to 16.6.0 and react-scripts to 2.0.3 to start using lazy and I got this error when following an example on official docs:
Failed prop type: Invalid prop component of type object supplied to Route, expected function
Disregarding of it everything seems to be working, except this error in the console.
Here is some of my code:
// imports here
...
const Decks = lazy(() => import('./pages/Decks'));
...
class App extends Component {
...
render() {
return (
<ConnectedRouter history={history}>
<div>
<MenuAppBar />
<div style={{paddingTop: '4rem'}}>
<Suspense fallback={<LazyLoading />}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/decks" component={Decks} />
...
</Switch>
</Suspense>
</div>
<Footer />
</div>
</ConnectedRouter>
);
}
...
}
What could I be doing wrong here?
When using a lazy loaded component, you would need to supply it to the Route component like
// imports here
...
const Decks = React.lazy(() => import('./pages/Decks'));
...
class App extends Component {
...
render() {
return (
<ConnectedRouter history={history}>
<div>
<MenuAppBar />
<div style={{paddingTop: '4rem'}}>
<Suspense fallback={<LazyLoading />}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/decks" render={(props) => <Decks {...props} />} />
...
</Switch>
</Suspense>
</div>
<Footer />
</div>
</ConnectedRouter>
);
}
...
}
Probably its an incorrect PropType check in react-router and may have been fixed in the latest versions to make it compatible with react v16.6
Update "react-router-dom" to "^4.4.0-beta.6" cat fix it.
It is a bug: https://github.com/ReactTraining/react-router/issues/6420#issuecomment-435171740
I am trying to get context variables inside a React Route. This is my code
<Switch>
<Route exact path="/" component={Home} />
<Route path="/home/:loc" render={(props)=> {
<AppContext.Consumer>
{
context=><Home context={context} />
}
</AppContext.Consumer>
}} />
</Switch>
This is my AppContext.js
import React from 'react';
export const AppContext = React.createContext();
This is how I am setting the context
<AppContext.Provider value={this.state}>
<div className="App">
<Header />
<Content />
<Footer />
</div>
</AppContext.Provider>
This is the error I am getting
Expected an assignment or function call and instead saw an expression no-unused-expressions
Any idea whats wrong here?
Expected an assignment or function call and instead saw an expression no-unused-expressions
is not an error but linter warning. no-unused-expressions rule often indicates that the code may not work as intended because of developer's mistake.
The problem with posted code is that render arrow function doesn't return anything.
It should be:
<Route path="/home/:loc" render={(props)=> (
<AppContext.Consumer>
{
context=><Home context={context} />
}
</AppContext.Consumer>
)} />
=> {...} requires explicit return, while => (...) is implicit return.