I have the following basic starting point to load a component via import().
function getComponentAsync(importPath: string) {
return async (nextState, callback) => {
const { default: Component } = await import(importPath);
callback(null, Component);
}
}
export default (
<Route path="/" component={App}>
<IndexRoute getComponent={getComponentAsync('app/components/Home')} />
</Route>
);
The goal is to simply load a component at some path. However, I keep running into some errors:
I placed a debug statement to confirm that the import path was correctly passed down to the function. It appears to be.
However, I really can't find what it is trying to import nor why it is failing to import it... why would this be? I've also tried swapping out import for require.ensure and got the same results.
Related
I want to clear the Google console and I know two ways:
The first one is by use (useEffect).
The second one is by putting the code directly in (return function).
Both ways are shown in the code below, but which one is correct and why?
import { Routes, Route } from "react-router-dom";
import Home from "./routes/home/Home";
import Navigation from "./routes/navigation/Navigation";
import Authentication from "./routes/authentication/Authentication";
import { useEffect } from "react";
const App = () => {
useEffect(() => {
setTimeout(() => console.clear(), 1500);
}, []);
return (
<Routes>
<Route path="/" element={<Navigation />}>
<Route index={true} element={<Home />} />
<Route path="auth" element={<Authentication />} />
</Route>
{setTimeout(() => console.clear(), 1500)}
</Routes>
);
};
export default App;
Using useEffect is the correct way. Here is the explanation:
In React, function components has the same life cycle as class components, as you can see in the image below, they load, render and then something called "componentDidMount" is called:
For code to subscribe, clean, fetch, etc. the optimal way is putting all inside the componentDidMount stage, in function components, this stage is defined as a useEffect without dependencies, as this loads after the component does it's render stage. (you can learn more about it here: https://reactjs.org/docs/hooks-effect.html)
Another thing that you can do is using the "componentWillUnmount" stage, this component's stage is called when is about to disappear, as the result of a navigation or something related to that. So, this should work if you want to clear the console every time the user go to other site page.
In function components, this is called the "cleanup" function:
useEffect(() => {
return () => {
console.clear();
}
});
Hope that helps you :)
<Route path="*">
<Redirect to="/" />
</Route>
The code above is what I am using to handle all not found routes. I have all routes in switch and for the most part this is working fine <Switch>. I was wondering how I would go about sending a windows alert saying something along the lines of "this page doesn't exist" when the unknown route is called. I tried using render and calling a function, but that didn't seem to work. Any thoughts?
edit: to make it clear, I am using React framework, javascript, react-router.
Your current implementation simply redirects back to (/) when the route is not found.
However, to display a window.alert() you should consider making a new NotFound component and handle the display of the alert on that page.
Afterward, use the useHistory hook to redirect back to '/'
See example code below
NotFound Component
import { useEffect } from "react";
import { useHistory } from "react-router-dom";
const NotFound = () => {
let history = useHistory();
useEffect(() => {
window.alert('Page Not found');
history.push('/');
}, []);
return (
<div>404</div>
)
}
export default NotFound;
Existing Routes
<Route path="*">
<NotFound />
</Route>
I'm working in a project that uses:
react/react-dom#16.9.0
#loadable/component
styled-components
react-router-dom
The application renders both server side and client side.
I'm using #loadable/component to dynamically code split this way.
router.tsx
import * as React from 'react'
import loadable from '#loadable/component'
import { Route, Switch } from 'react-router-dom'
const NotFound = loadable(() =>
import('../components/NotFound/NotFound' /* webpackChunkName: "notfound" */)
)
const routes = (
<Switch>
<Route component={NotFound} />
</Switch>
)
export default routes
When loading the page, this error appear on the console and the page seems to flick for a second.
react-dom.development.js:546 Warning: Did not expect server HTML to contain a <div> in <main>.
When I check the output in both sides (server/client), they are identical.
When I remove #loadable/component like bellow, it works and the error is gone.
router-without-loadable.tsx
import * as React from 'react'
import { Route, Switch } from 'react-router-dom'
import NotFound from '../components/NotFound/NotFound'
const routes = (
<Switch>
<Route component={NotFound} />
</Switch>
)
export default routes
Seems to be something to do with #loadable/component but I'm not 100% sure.
Finally have an answer for this:
For #loadable/component to work properly, you need to put the magic webpack comment (/* webpackChunkName: "notfound" */) before the path of the file this way.
const NotFound = loadable(() =>
import(/* webpackChunkName: "notfound" */ '../components/NotFound/NotFound')
)
Reference:
https://github.com/smooth-code/loadable-components/issues/23
And more important, in the server side, you need to wrap you app in a ChunkExtractorManager and pass the client extractor (I was passing the server extractor, it's not very clear in the docs).
const statsFile = path.resolve('./wwwroot/dist/loadable-stats.json')
const extractor = new ChunkExtractor({
statsFile,
entrypoints: ['client'] // name of the proper webpack endpoint (default: main)
})
<ChunkExtractorManager extractor={extractor}>
<App />
</ChunkExtractorManager>
Here is a proper clear example on how to implement it:
https://github.com/iamssen/seed
Update 24.09.2019
Added to the official docs
https://www.smooth-code.com/open-source/loadable-components/docs/server-side-rendering/#chunkextractor-entrypoints
I think the problem is your NotFound component is not loaded and thus Route dont know what to render which is causing the error.
You would need to modify something like below:
<Route path="/404/" exact component={props => <NotFound {...props} />} />
I've been working on trying to modularize my React.js app (that will be delivered as a Desktop app with Electron) in a way that if I make a new module in the future, I can just add a new folder and modify a couple of files and it should integrate fine.
I got originally inspired by this article: https://www.nylas.com/blog/react-plugins/
After that point, I started doing as much research as I could and ended up creating a JSON file that would live in the server with a manifest of the plugins that are registered for that specific client.
Something like this:
{
"plugins": [
{
"name": "Test Plugin",
"version": "0.0.1",
"path": "testplugin",
"file": "test",
"component":"TestPlugin"
},
{
"name": "Another Plugin",
"version": "0.0.1",
"path": "anothertest",
"file": "othertest",
"component":"TestPluginDeux"
}
]
}
After that, I made a couple folders that match the path value and that contain a component that matches the name in the manifest (e.g. testplugin/test.jsx that exports the TestPlugin component as a default). I also made a pluginStore file that reads the manifest and mounts the plugins in the this.state.
Then, did a ton of research on Google and here and found this answer: React - Dynamically Import Components
With that function, I was able to iterate through the manifest, find the folders in the directory, and mount the plugins in the this.state by running the mountPlugins() function I had created in the pluginStore, inside a componentDidMount() method in my homepage.
So far so good. I'm using React-Router and I was able to mount the plugins dynamically in the State and able to load them in my Home Route by just calling them like this: <TestPlugin />.
The issue that I have now, is that I wanted to dynamically create Routes that would load these components from the state, either by using the component or the render method, but I had no luck. I would always get the same result... Apparently I was passing an object instead of a String.
This was my last iteration at this attempt:
{this.state.modules.registered.map((item) =>
<Route exact path={`/${item.path}`} render={function() {
return <item.component />
}}></Route>
)}
After that, I made a Route that calls a PluginShell component that is called by a Navlink that sends the name of the plugin to inject and load it dynamically.
<Route exact path='/ex/:component' component={PluginShell}></Route>
But I ended having the same exact issue. I'm passing an object and the createElement function expected a string.
I searched all over StackOverflow and found many similar questions with answers. I tried applying all the possible solutions with no luck.
EDIT:
I have put together a GitHub repo that has the minimal set of files to reproduce the issue.
Here's the link:
https://codesandbox.io/embed/aged-moon-nrrjc
Okey pokey. There are a lot of moving parts here that can be vastly simplified.
I'd recommend moving toward a more developer-friendly, opinionated state store (like Redux). I've personally never used Flux, so I can only recommend what I have experience with. As such, you can avoid using plain classes for state management.
You should only import the modules ONCE during the initial application load, then you can dispatch an action to store them to (Redux) state, then share the state as needed with the components (only required if the state is to be shared with many components that are spread across your DOM tree, otherwise, not needed at all).
Module imports are asynchronous, so they can't be loaded immediately. You'll have to set up a condition to wait for the modules to be loaded before mapping them to a Route (in your case, you were trying to map the module's registered string name to the route, instead of the imported module function).
Module imports ideally should be contained to the registered modules within state. In other words, when you import the module, it should just overwrite the module Component string with a Component function. That way, all of the relevant information is placed within one object.
No need to mix and match template literals with string concatenation. Use one or the other.
Use the setState callback to spread any previousState before overwriting it. Much simpler and cleaner looking.
Wrap your import statement within a try/catch block, otherwise, if the module doesn't exist, it may break your application.
Working example (I'm just using React state for this simple example, I also didn't touch any of the other files, which can be simplified as well):
App.js
import React from "react";
import Navigation from "./components/MainNavigation";
import Routes from "./routes";
import { plugins } from "./modules/manifest.json";
import "./assets/css/App.css";
class App extends React.Component {
state = {
importedModules: []
};
componentDidMount = () => {
this.importPlugins();
};
importPlugins = () => {
if (plugins) {
try {
const importedModules = [];
const importPromises = plugins.map(plugin =>
import(`./modules/${plugin.path}/${plugin.file}`).then(module => {
importedModules.push({ ...plugin, Component: module.default });
})
);
Promise.all(importPromises).then(() =>
this.setState(prevState => ({
...prevState,
importedModules
}))
);
} catch (err) {
console.error(err.toString());
}
}
};
render = () => (
<div className="App">
<Navigation />
<Routes {...this.state} />
</div>
);
}
export default App;
routes/index.js
import React from "react";
import React from "react";
import isEmpty from "lodash/isEmpty";
import { Switch, Route } from "react-router-dom";
import ProjectForm from "../modules/core/forms/new-project-form";
import NewPostForm from "../modules/core/forms/new-post-form";
import ProjectLoop from "../modules/core/loops/project-loop";
import Home from "../home";
const Routes = ({ importedModules }) => (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/projectlist/:filter" component={ProjectLoop} />
<Route exact path="/newproject/:type/:id" component={ProjectForm} />
<Route exact path="/newpost/:type" component={NewPostForm} />
{!isEmpty(importedModules) &&
importedModules.map(({ path, Component }) => (
<Route key={path} exact path={`/${path}`} component={Component} />
))}
</Switch>
);
export default Routes;
You've got the right idea, if anything I guess your syntax is slightly off. I didn't have to tweak much from your example to get dynamic routing work.
Here's a working example of what I think you want to do:
const modules = [{
path: '/',
name: 'Home',
component: Hello
},{
path: '/yo',
name: 'Yo',
component: Yo
}];
function DynamicRoutes() {
return (
<BrowserRouter>
{ modules.map(item => <Route exact path={item.path} component={item.component}/>) }
</BrowserRouter>
);
}
https://stackblitz.com/edit/react-zrdmcq
I think the problem is the way you are trying to render <item.component /> but not sure, did you get the same error whit this?
Try:
<Route exact path={`/${item.path}`} render={function() {
return React.createElement(item.component, props)
}}></Route>
)}
I was building a search engine for custom project.
There I have a search bar from where user can search.
When the user searches, I want the given link to work as it works in case of google
www.google.com/ search? queryRelatedInfo
Notice the search? and then whatever query/parameter/ID
for this I tried something like this in
import React, {Component} from 'react';
import {
BrowserRouter,
Route,
Switch,
Redirect,
} from 'react-router-dom';
import SearchScreen from "./container/searchScreen.js"
import HomeScreen from "./container/home.js";
class route extends Component {
render () {
return (
<BrowserRouter>
<div>
<Switch>
<Route path ="/" exact render ={(props) => <HomeScreen {...props}/>} />
<Route path ="/search?:id" exact render ={(props) => <SearchScreen {...props}/>} />
</Switch>
</div>
</BrowserRouter>
)
}
}
export default route
Notice, <Route path ="/search?:id" above.
Unfortunately this didn't worked out.
I understand that <Route path ="/:id" works but how can i make <Route path ="/search?:id to work i.e how can I make some link like http://localhost:3000/search?9e9e to work
I think this is related with historyApiFallback. That parameter;
(https://webpack.js.org/configuration/dev-server/#devserver-historyapifallback)
When using the HTML5 History API, the index.html page will likely have to be served in place of any 404 responses. devServer.historyApiFallback is disabled by default. Enable it by passing:
module.exports = {
//...
devServer: {
historyApiFallback: true
}
};
Your react app is a single page application. So all path except home path actually is an virtual path, they are not physically exist. The paths must routed to home path. So react-router can manage.
you don't need to put the path like this /search?:id, just put it search
<Route path ="/search" exact render ={(props) => <SearchScreen {...props}/>} />
then inside your SearchScreen component, get the value of search parameter from the URL, check this issue will help.
after the user make search, pass the value like this /search?s=value_here