How to create multiple page app using react - javascript

I have created a single page web app using react js. I have used webpack to create bundle of all components. But now I want to create many other pages. Most of pages are API call related. i.e. in the index.html, I have displayed content from API. I want to insert content in another page parsing data from API. Webpack compresses everything of react in a file which is bundle.js. However, the configuration of webpack is as follow:
const webpack = require('webpack');
var config = {
entry: './main.js',
output: {
path:'./',
filename: 'dist/bundle.js',
},
devServer: {
inline: true,
port: 3000
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
}
module.exports = config;
Now, I am confused what kind of configuration of webpack will be for other page or what is the correct way to build multi-pages app using react.js

Preface
This answer uses the dynamic routing approach embraced in react-router v4+. Other answers may reference the previously-used "static routing" approach that has been abandoned by react-router.
Solution
react-router is a great solution. You create your pages as Components and the router swaps out the pages according to the current URL. In other words, it replaces your original page with your new page dynamically instead of asking the server for a new page.
For web apps I recommend you read these two things first:
Full Tutorial
The react-router docs; it will help you get a better understanding of how Router works.
Summary of the general approach:
1 - Add react-router-dom to your project:
Yarn
yarn add react-router-dom
or NPM
npm install react-router-dom
2 - Update your index.js file to something like:
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render((
<BrowserRouter>
<App /> {/* The various pages will be displayed by the `Main` component. */}
</BrowserRouter>
), document.getElementById('root')
);
3 - Create a Main component that will show your pages according to the current URL:
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from '../pages/Home';
import Signup from '../pages/Signup';
const Main = () => {
return (
<Switch> {/* The Switch decides which component to show based on the current URL.*/}
<Route exact path='/' component={Home}></Route>
<Route exact path='/signup' component={Signup}></Route>
</Switch>
);
}
export default Main;
4 - Add the Main component inside of the App.js file:
function App() {
return (
<div className="App">
<Navbar />
<Main />
</div>
);
}
5 - Add Links to your pages.
(You must use Link from react-router-dom instead of just a plain old <a> in order for the router to work properly.)
import { Link } from "react-router-dom";
...
<Link to="/signup">
<button variant="outlined">
Sign up
</button>
</Link>

(Make sure to install react-router using npm!)
To use react-router, you do the following:
Create a file with routes defined using Route, IndexRoute components
Inject the Router (with 'r'!) component as the top-level component for your app, passing the routes defined in the routes file and a type of history (hashHistory, browserHistory)
Add {this.props.children} to make sure new pages will be rendered there
Use the Link component to change pages
Step 1
routes.js
import React from 'react';
import { Route, IndexRoute } from 'react-router';
/**
* Import all page components here
*/
import App from './components/App';
import MainPage from './components/MainPage';
import SomePage from './components/SomePage';
import SomeOtherPage from './components/SomeOtherPage';
/**
* All routes go here.
* Don't forget to import the components above after adding new route.
*/
export default (
<Route path="/" component={App}>
<IndexRoute component={MainPage} />
<Route path="/some/where" component={SomePage} />
<Route path="/some/otherpage" component={SomeOtherPage} />
</Route>
);
Step 2 entry point (where you do your DOM injection)
// You can choose your kind of history here (e.g. browserHistory)
import { Router, hashHistory as history } from 'react-router';
// Your routes.js file
import routes from './routes';
ReactDOM.render(
<Router routes={routes} history={history} />,
document.getElementById('your-app')
);
Step 3 The App component (props.children)
In the render for your App component, add {this.props.children}:
render() {
return (
<div>
<header>
This is my website!
</header>
<main>
{this.props.children}
</main>
<footer>
Your copyright message
</footer>
</div>
);
}
Step 4 Use Link for navigation
Anywhere in your component render function's return JSX value, use the Link component:
import { Link } from 'react-router';
(...)
<Link to="/some/where">Click me</Link>

This is a broad question and there are multiple ways you can achieve this. In my experience, I've seen a lot of single page applications having an entry point file such as index.js. This file would be responsible for 'bootstrapping' the application and will be your entry point for webpack.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Application from './components/Application';
const root = document.getElementById('someElementIdHere');
ReactDOM.render(
<Application />,
root,
);
Your <Application /> component would contain the next pieces of your app. You've stated you want different pages and that leads me to believe you're using some sort of routing. That could be included into this component along with any libraries that need to be invoked on application start. react-router, redux, redux-saga, react-devtools come to mind. This way, you'll only need to add a single entry point into your webpack configuration and everything will trickle down in a sense.
When you've setup a router, you'll have options to set a component to a specific matched route. If you had a URL of /about, you should create the route in whatever routing package you're using and create a component of About.js with whatever information you need.

Following on from the answer above by #lucas-andrade: for react-router versions >= 6, Switch no longer exists (see https://github.com/remix-run/react-router/issues/8439). The content of step 3 becomes:
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import Home from '../pages/Home';
import Signup from '../pages/Signup';
const Main = () => {
return (
<Routes>
<Route path='/' element={Home}></Route>
<Route path='/signup' element={Signup}></Route>
</Routes>
);
}
export default Main;

The second part of your question is answered well. Here is the answer for the first part: How to output multiple files with webpack:
{
entry: {
outputone: './source/fileone.jsx',
outputtwo: './source/filetwo.jsx'
},
output: {
path: path.resolve(__dirname, './wwwroot/js/dist'),
filename: '[name].js'
},
}
This will generate 2 files: outputone.js and outputtwo.js in the target folder.

One can also use gatsby a react framework dedicated to static multipage apps.

Related

How can I import component from variable or props - Dynamic router

Context for a good solution (short question in bottom).
I have a database (API) which generate list of data of apps like AppA, AppB, AppC, etc. with their name, path...
With a map, I generate (react router) links to these apps based on this data list (in the main <App/>) and front.
With another identical map (below), I have made the router which should call the App based on the route and app name:
function(Router) {
const [routes, setRoutes] = useState([]);
useEffect(() => {
fetch("MyAPI")
.then(res => res.json())
.then((result) => {setRoutes(result.results)}
)
}, [])
// The route for each app and after the map, the route to the home with these links
return (
<Switch>{
routes.map(result =>
<Route exact path={"/"+result.route} key={keygen(16)}>
<AppCaller name={result.name} id={result.id} path={"/"+result.route}/>
</Route>
)}
<Route exact path="/">
<App />
</Route>
</Switch>
)
}
export default Router
My first problem is I cannot neither give a component Name like <result.name/> from the API to call this component in the Router nor import dynamically this component.
My first solution was to create another component <AppCaller/> with the name and path as Props to remove the component problem like this :
import React from "react";
import Window from "./dashComponents"
import subApp from "./Apps/Store"
class AppCaller extends React.Component {
render() {
return (
<Window name={this.props.name}>
<subApp/>
</Window>
)
}
}
export default AppCaller
In fact, I can keep the <subApp/> name even if the app is different. The only thing i need to change is the "where" is the app comes from.
Begin of short question
So, How can I change the path of the import statement to "import" the good App in this dynamic component ? (with a Props in this case)
import subApp from this.props.path
This may looks like this (if it was static):
import subApp from "./Apps/App1" in the App1 file
import subApp from "./Apps/App1" in the App2 file etc.
Another idea seen in React Documentation and in Stack Overflow is to use Lazy Import but it does not works:
import React, {Suspense} from "react";
import Window from "./dashComponents"
//import subApp from "./Apps/Store"
const path = this.props.path
let name = this.props.name
function AppCaller() {
const SubApp = React.lazy(() => import('./Apps/'+name))
return (
<Window name={name}>
<Suspense fallback={<h2>"Chargement..."</h2>}>
<SubApp/>
</Suspense>
</Window>
)
}
export default AppCaller
OR
class AppCaller extends React.Component {
render() {
const SubApp = React.lazy(() => import('./Apps/'+this.props.name));
return (
<Window name={this.props.name}>
<Suspense fallback={"Chargement..."}>
<SubApp/>
</Suspense>
</Window>
)
}
}
export default AppCaller
"The above error occured in one of your React Component..."
Thank you for your help.
I Try to be very accurate and find everywhere. So please, be free to tell me precision before judging.
Edit 1 with full Error
I have this error when I click in one of the links generated.
The above error occurred in one of your React components:
in Unknown (created by AppCaller)
in Suspense (created by AppCaller)
in div (created by Window)
in Window (created by AppCaller)
in AppCaller (created by RoutesX)
in Route (created by RoutesX)
in Switch (created by RoutesX)
in RoutesX
in div
in Router (created by BrowserRouter)
in BrowserRouter
Consider adding an error boundary to your tree to customize error handling behavior.
Visit 'Facebook' to learn more about error boundaries.
Edit 2 with initial Error
react-dom.development.js:11865 Uncaught ChunkLoadError: Loading chunk 0 failed.
(error: http://dash.localhost:8000/0.main.js)
at Function.requireEnsure [as e] (http://dash.localhost:8000/static/frontend/main.js:106:26)
at eval (webpack:///./src/components/AppCaller.js?:45:36)
at initializeLazyComponentType (webpack:///./node_modules/react-dom/cjs/react-dom.development.js?:1432:20)
at readLazyComponentType (webpack:///./node_modules/react-dom/cjs/react-dom.development.js?:11862:3)
...
requireEnsure # main.js:106
eval # AppCaller.js:45
...
And
0.main.js:1 Failed to load resource: the server responded with a status of 404 (Not Found)
Ok Everybody !
So, it was not a React but a Webpack problem !
When you use a React lazy to import specific component, and you "compile" with npm run dev (or build) webpack split the code by Chunck but the configuration file is wrong.
Here's my configuration file which works:
module.exports = {
output: {
filename: 'App.js',
publicPath: '/static/frontend/',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
}
};
I needed to add the "public path" to the path of the "chunck' scripts. :)

Error: <Route> elements are for router configuration only and should not be rendered in react-router v4

Tried to upgrade react-router from 2 to 4 and broke it and now cant render my app.
getting various errors (the most recent is: <Route> elements are for router configuration only and should not be rendered)
I have also had the error where my ./ route renders fine but every other route blows up when I refresh and says Cannot GET /randomRoute
I am creating a react app and my main index.js file (where I include ReactDOM.render) also includes the routes and looks like so:
import React from 'react';
import ReactDOM from 'react-dom';
import { Route } from 'react-router';
import { BrowserRouter as Router, Match, HashRouter } from 'react-router-dom'
import Header from './components/header';
import './index.scss';
class App extends React.Component {
render() {
return (
<Router history={HashRouter}>
<div>
<Route path={"/"} component={Header} />
</div>
</Router>
);
}
}
ReactDOM.render(<App />,
document.getElementById('content'));
why would I be getting that current error and can anyone give me a simple start to the basics I need to include just to get routing working? it worked in version 2 but I wanted to upgrade and now cant get it working again
The problem is that you are specifying history object as a Router type.
From the Documentation
A <Router> that uses the hash portion of the URL (i.e.
window.location.hash) to keep your UI in sync with the URL.
This is similar to what you would do when you specify history as
hashHistory in Router v2.
Also, history object has been seprated into a seprate package from v4 onwards.
You can either make use of BrowserRouter or HashRouter to render your Routes.
Change your Route Configuration to below if you want to use BrowserRouter which is <Router> that uses the HTML5 history API (pushState, replaceState and the popstate event) to keep your UI in sync with the URL.This is similar to what you would do when you specify history as browserHistory in Router v2.
Also you need to import Route from 'react-router-dom'.
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Match, Route} from 'react-router-dom'
import Header from './components/header';
import './index.scss';
class App extends React.Component {
render() {
return (
<Router >
<div>
<Route path={"/"} component={Header} />
</div>
</Router>
);
}
}
Well, in react router v4 the API is different. You have to define it in your index.js file like this,
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<div>
<Switch>
<Route path="/path/one" component={ComponentOne} />
<Route path="/path/two" component={ComponentTwo} />
<Route path="/" component={IndexComponent} />
</Switch>
</div>
</BrowserRouter>
</Provider>
, document.querySelector('.container'));
Make sure the order is important here. Put the most generic one at last. Hope this helps. Happy coding !

React, Electron, And Router

I am currently to adapt a simple web app i made (using React and Redux) into a native desktop app.
I am using GitHub's Electron and Webpack to do this. Everything is fine if I use hashHistory from React-Router.. but I want to use browserHistory so my app will still look nice (URL-wise) when running as a webapp. If i do this though, I get the following error:
No route matches path ".../index.html"
Which makes sense to me. I am loading index.html as the main file for Electron:
mainWindow.loadURL('file://' + __dirname + '/index.html');
I'm just wondering if it is at all possible to use browserHistory with React-Router and Electron. If anyone knows it would be greatly appreciated!
Not exactly. But there is even better solution .
You should Separate from your react app the bootstrap file.
Bootstrap file which load your app and pass to it some additional params from outside.
In your situation you will create two bootstrap files, one for electron - with memoryHistory (I think it is better for electron) and second one for browsers with browser history.
Example of bootstrap file for electron index-electron.jsx:
import React from "react";
import ReactDOM from "react-dom";
import { createMemoryHistory } from "react-router";
import App from "./App.jsx";
const initialState = window.__INITIAL_STATE__;
const config = window.__CONFIG__;
const history = createMemoryHistory("begin-path");
ReactDOM.render(
<App
config={config}
history={history}
initialState={initialState}
/>, document.getElementById("root"));
Example of bootstrap file for browsers index-browser.jsx:
import React from "react";
import ReactDOM from "react-dom";
import { browserHistory } from 'react-router';
import App from "./App.jsx";
const initialState = window.__INITIAL_STATE__;
const config = window.__CONFIG__;
ReactDOM.render(
<App
config={config}
history={history}
initialState={initialState}
/>, document.getElementById("root"));
In my examples difference is small (only history) but you can make more changes. As you see I also provide additional begin params from outside (initialState, config);
And how your App should :
import React from 'react';
import { Router, Route } from 'react-router';
class App extends React.Component {
static propsTypes = {
history: React.PropTypes.object,
config: React.PropTypes.object,
initialState: React.PropTypes.object
};
render() {
return (
<Router history={this.props.history}>
<Route ...>
...
</Route>
</Router>
);
}
}
export default App;
Above code is only conception. It is from my project, where I removed obsolete things. Therefore without some modification it may not work
Now for electron you use index-electron.jsx, and for browsers index-browser.jsx. Most of your code is reusable cross both envs. And it is very flexible.

How to use React Router with Preact

I'm using Preact as my View framework (can NOT use React due to the Facebook IP problems). I needed to use React Router for the location routing because it has more flexibility than the Preact Router that the same team built.
I managed to get React Router to accept Preact in place of React, however, I can't get it to match locations. I'm not sure if it's a compatibility problem, or a configuration problem. I've tried using just one pair of routes ( App and Account ) and it still doesn't work with that simplified setup.
Q: Does anyone see if I'm doing something wrong here?
The error I get is: Location "/account/12345/" did not match any routes
main.js
import { h, render } from 'preact';
import { Router, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import createStore from './createStore';
import createRoutes from './createRoutes';
process.env.DEBUG && console.log('Hello, developer!');
const history = browserHistory;
const store = createStore( history );
const routes = createRoutes( store );
render((
<Provider store={ store } key="redux-provider">
<Router history={ history } createElement={ h } routes={ routes } />
</Provider>
), document.body );
createRoutes.js
import { h } from 'preact';
import { IndexRoute, Route } from 'react-router';
// App Component
import App from './components/app';
// Sub Components
import Account from './components/account';
import Conversation from './components/conversation';
import Dashboard from './components/dashboard';
// Error Components
import BadAccount from './components/bad-account';
import NotFound from './components/not-found';
// Routes
export default ()=> (
<Route path="/" component={App}>
{/* Get URL parameter */}
<Route path="account/:accountID" component={Account}>
{/* Index Route */}
<IndexRoute component={Dashboard} />
{/* Sub Routes ( Alphabetical Please ) */}
<Route path="chat" component={Conversation} />
{/* Catch-All Route */}
<Route path="*" component={NotFound} />
</Route>
{/* Handle Invalid URIs */}
<Route path="*" component={BadAccount} />
</Route>
);
createStore.js
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerMiddleware } from 'react-router-redux';
import messages from './resources/messages/reducer';
import conversation from './resources/conversation/reducer';
import layout from './resources/layout/reducer';
import profile from './resources/profile/reducer';
import contract from './resources/contract/reducer';
/*const { devToolsExtension } = window;*/
export default history => {
// Sync dispatched route actions to the history
const reduxRouterMiddleware = routerMiddleware( history );
// Create single reducer from all modules
const rootReducer = combineReducers({
messages,
conversation,
layout,
profile,
contract
});
// List redux middleware to inject
const middleware = [
thunk,
reduxRouterMiddleware
];
// Compose the createStore function
const createComposedStore = compose(
applyMiddleware( ...middleware )/*, // Figure this out...
( process.env.DEBUG && devToolsExtension ) ? devToolsExtension() : f => f*/
)( createStore );
// Create the store
const store = createComposedStore( rootReducer );
// Hook up Redux Routing middleware
// reduxRouterMiddleware.listenForReplays(store);
// Return store
return store;
};
(OP already solved his issue, but this ranks high in Google and is not very helpful for newcomers, so I thought I'd provide some background info)
Preact and preact-compat
preact is a minimal version of React that weighs just 3Kb. It implements a subset of React's API, with some small differences here and there. It also comes with a helper library, preact-compat, which provides compatibility with React by filling in missing parts and patching up API differences.
React-Router
react-router is a router library designed to work with React. But you can make it work with Preact as well, using preact-compat.
Setting up preact-compat
npm i --save preact-compat
Make sure you set up aliases for react and react-dom in your webpack / browserify configuration, or write some code to set up these aliases manually.
example webpack config
{
// ...
resolve: {
alias: {
'react': 'preact-compat',
'react-dom': 'preact-compat'
}
}
// ...
}
Then you can use React Components as-is. They won't know they are being rendered by Preact i.s.o. React. Have a look at this preact-compat-example.
Issues with compatibility
Keep in mind that when you are using Preact Compat, you are taking a risk. Jason is a very smart guy, but his library is only a fraction of the size of the one provided by Facebook so there's bound to be some differences. Components that use lesser known features of React might not work correctly. If you encounter such issues, report them to the preact-compat issue tracker (with a minimal reproduction in the form of a GitHub repo) to help him improve it.
There have been a few of such issues in the past that prevented React-Router from working correctly with Preact, but they have been fixed since and you should now be able to use the two together nicely.
Fiddle of Preact + React-Router
Have a look at this JS Fiddle for a working example.
Updated answer is there is a preact-router package now: https://www.npmjs.com/package/preact-router
import Router from 'preact-router';
import { h } from 'preact';
const Main = () => (
<Router>
<Home path="/" />
<About path="/about" />
<Search path="/search/:query" />
</Router>
);
render(<Main />, document.body);
Found the issue, was a pair of problems with Preact's compatibility with React:
Contexts not handled correctly:
https://github.com/developit/preact/issues/156
props.children not handled correctly:
https://github.com/developit/preact-compat/issues/47#issuecomment-220128365
Here is extend solution for preact-router with hash support.
Works with reload and direct access.
https://www.webpackbin.com/bins/-KvgdLnM5ZoFXJ7d3hWi
import {Router, Link} from 'preact-router';
import {h, render} from 'preact';
import {createHashHistory} from 'history';
[cut]...[/cut]
const App = () => (
<div>
<Header />
<Router history={createHashHistory()}>
<Page1 default path="/" />
<Page2 path="/page2" />
</Router>
</div>
);
render(<App />, document.body);

How to test a React component that uses context like in react-router 2.0 with Jest 0.8.x

I had this problem in the past while using older versions of react-router which I solved using: stubRouterContext + a hacky way to access the component instance (using refs: https://github.com/reactjs/react-router/issues/1140#issuecomment-113174774)
I thought this would improve in the future but I am hitting the same wall with react-router 2.0 (I am not saying this is a problem with react-router but since it uses context, it affects my tests). So, I have a component that uses context to push new state into the url this.context.router.push(...) which is the way to go now
https://github.com/reactjs/react-router/blob/master/upgrade-guides/v2.0.0.md#programmatic-navigation
I am telling jest.dontMock('react-router') but my test will fail with:
TypeError: Cannot read property 'push' of undefined
This happens because the instance returned by TestUtils.renderIntoDocument will have:
context: Object { router: undefined }
Now, what is the real problem here? Is it Jest? I'm pretty sure I am not the only one who encounters this and since stubRouterContext it's not in the official docs of react-router anymore, is there any broad accepted solution for this?
How would I make the test to work? Which is basically having the correct context and being able to access everything from the component instance returned by TestUtils.renderIntoDocument.
I am using react 0.14.7, jest-cli 0.8.2 and react-router 2.0.
Here's the setup that I've ended up with for my context dependent components (stripped down for simplicity, of course):
// dontmock.config.js contains jest.dontMock('components/Breadcrumbs')
// to avoid issue with hoisting of import operators, which causes
// jest.dontMock() to be ignored
import dontmock from 'dontmock.config.js';
import React from "react";
import { Router, createMemoryHistory } from "react-router";
import TestUtils from "react-addons-test-utils";
import Breadcrumbs from "components/Breadcrumbs";
// Create history object to operate with in non-browser environment
const history = createMemoryHistory("/products/product/12");
// Setup routes configuration.
// JSX would also work, but this way it's more convenient to specify custom
// route properties (excludes, localized labels, etc..).
const routes = [{
path: "/",
component: React.createClass({
render() { return <div>{this.props.children}</div>; }
}),
childRoutes: [{
path: "products",
component: React.createClass({
render() { return <div>{this.props.children}</div>; }
}),
childRoutes: [{
path: "product/:id",
component: React.createClass({
// Render your component with contextual route props or anything else you need
// If you need to test different combinations of properties, then setup a separate route configuration.
render() { return <Breadcrumbs routes={this.props.routes} />; }
}),
childRoutes: []
}]
}]
}];
describe("Breadcrumbs component test suite:", () => {
beforeEach(function() {
// Render the entire route configuration with Breadcrumbs available on a specified route
this.component = TestUtils.renderIntoDocument(<Router routes={routes} history={history} />);
this.componentNode = ReactDOM.findDOMNode(this.component);
this.breadcrumbNode = ReactDOM.findDOMNode(this.component).querySelector(".breadcrumbs");
});
it("should be defined", function() {
expect(this.breadcrumbNode).toBeDefined();
});
/**
* Now test whatever you need to
*/
Had same trouble, undefined this.context.history in unit tests.
Stack: React 0.14, react-router 1.0, history 1.14, jasmine 2.4
Was solved next way.
For react-router history I use "history/lib/createBrowserHistory", which allows links without hash. This module is singleton. That means once created it could be used through all your apps.
So I've decided to throw away History mixin and use history directly from separate component next way:
// history.js
import createBrowserHistory from 'history/lib/createBrowserHistory'
export default createBrowserHistory()
// app.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute } from 'react-router'
import history from './history'
import Main from './Main'
const Layout = React.createClass({
render() {
return <div>{React.cloneElement(this.props.children)}</div>
}
})
const routes = (
<Route component={Layout} path='/'>
<IndexRoute component={Main} />
</Route>
)
ReactDOM.render(
<Router history={history}>{routes}</Router>,
document.getElementById('my-app')
)
// Main.js
import React from 'react'
import history from './history'
const Main = React.createClass({
next() {
history.push('/next_page')
},
render() {
return <button onClick={this.next}>{'Go to next page'}</button>
}
})
export default Main
Works like a charm and could be tested easily. No more mixins, no more context (at least in this particular case). I believe same approach could be used for standart react-router browser history, but didn't check.

Categories

Resources