I'm new on React and I try to build my first application, for the route I used react-router and this is my App
class App extends Component {
render() {
return (
<Router history={browserHistory}>
<Route path='/' component={Container}>
<IndexRoute component={Home}/>
<Route path='/address' component={Address}>
<IndexRoute component={TwitterFeed} />
<Route path='instagram' component={Instagram} />
<Route path='query' component={Query} />
</Route>
<Route path='/about/:name' component={About} />
<Route path='/namedComponent' component={NamedComponents}>
<IndexRoute components={{ title: Title, subTitle: SubTitle }} />
</Route>
<Route path='*' component={NotFound} />
</Route>
</Router>
)
}
}
Everything work well but not this line <Route path='/about/:name' component={About} />, in fact if i try to write /address/12345 white page appears to me whitout error, someone can help me?
This is my webpack.config.js
'use strict';
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var webpack = require('webpack');
module.exports = {
entry: {
app: [
'./src/main.js'
]
},
output: {
path: __dirname + '/dist',
publicPath : '/',
filename: 'bundle.js'
},
plugins: process.env.NODE_ENV === 'production' ? [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin({ filename: '[name].css', disable: false, allChunks: true})
] : [],
devServer: {
contentBase: "./dist",
inline: true,
port: 3000,
},
//source map for js
devtool: 'source-map',
module: {
loaders: [
//babel ECMAScript 6 compiler
{
test: /\.js?$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.scss$/,
loaders: [ 'style-loader', 'css-loader?sourceMap', 'sass-loader?sourceMap' ] //sourceMap for debug css in devTools
/*loader: ExtractTextPlugin.extract({
fallback : 'style-loader', use : 'css-loader?sourceMap!sass-loader?sourceMap'
}) FOR PROD*/
}
]
},
}
And i use this script : webpack-dev-server --inline --content-base dist --history-api-fallback
In your component "Container" you should have children object.
You can have a better look on the snippet bellow.
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
const BasicExample = () =>
<Router>
<div>
<ul>
<Route to="/" component={ComponentThatHaveChildren} />
<Route path="/path1/path2" component={ChildrenComponent} />
</ul>
</div>
</Router>;
const ComponentThatHaveChildren = props =>
<div>
<h1>Component That Have Children</h1>
<h2>{props.children}</h2>
</div>;
const ChildrenComponent = () =>
<div>
<h2>Children</h2>
</div>;
export default BasicExample;
Related
I am developing React app and I have some problems while implementing routing.
<BrowserRouter>
<Layout>
<Route path="/" component={Home} exact={true} />
<Route path="/signin" component={Signin} />
<Route path="/register" component={Register} />
<Route path="/about" component={About} />
<Route path="/class/:id" component={Class} />
<Route path="/class" component={Classes} exact={true} />
</Layout>
</BrowserRouter>
When I navigate home page on local , i.e localhost:8000 , home page is displayed.
And when I click class link, browser navigates to /class and classes page is displayed.
And when I click specific class item , browser navigate to /class/:class-id and specific class detail page is displayed.
But when I manually type in /class/:class-id in browser, class detail page is not displayed.
When I inspect the browser, bundle.js was referred to localhost:8000/class/bundle.js.
But bundle.js is referred to localhost:8000/bundle.js.
I am using webpack and here's webpack configuration.
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InterpolateHtmlPlugin = require('interpolate-html-plugin');
const path = require('path');
module.exports = {
mode: 'development',
entry: "./src/index.js",
// Here the application starts executing
// and webpack starts bundling
output: {
// options related to how webpack emits results
path: path.resolve(__dirname, "dist"), // string
// the target directory for all output files
// must be an absolute path (use the Node.js path module)
filename: "bundle.js", // string,
// the filename template for entry chunks,
assetModuleFilename: "assets/[hash][ext][query]"
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader'
},
{
test: /\.less$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
{ loader: "less-loader" }
]
},
{
test: /\.(jpe?g|png|gif|svg)$/,
type: "asset/resource"
}
]
},
resolve: {
extensions: ['.js', '.jsx'],
alias: {
'#': path.resolve(__dirname, 'src/'),
}
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
favicon: './public/favicon.ico',
}),
new InterpolateHtmlPlugin({
PUBLIC_URL: 'http://localhost:8000/public'
})
],
devServer: {
historyApiFallback: true,
port: 8000,
open: true
},
externals: {
// global app config object
config: JSON.stringify({
apiUrl: 'http://localhost:4000'
})
}
}
I found a simple solution.
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
favicon: './public/favicon.ico',
publicPath: '/'
}),
new InterpolateHtmlPlugin({
PUBLIC_URL: 'http://localhost:8000/public'
})
]
I added publicPath: '/' to HtmlWebpackPlugin so that bundle.js can be referred to absolute path, not relative path. localhost:8000/bundle.js
So it works perfect now.
On the other hand, I researched isomorphic application but found that its main benefits are to improve performance & SEO implementation.
This idea appeared to improve performance & SEO implementation after modern JS frameworks which have client rendering ability.
Since Google announced and supported SEO regarding javascript, this idea lost enthusiast , but still be used for its benefits.
I am sure isomorphic and catch-all method can resolve my issue with perfect.
But for now, I prefer my current solution, it's simple.
I'm developing a project using React/Redux and Node.js.
I added react-router-dom as a dependency into my project and configured the router like this:
import ...
const Router = () => (
<main>
<Switch>
<Route exact path='/' component={components.main}/>
<Route path='/caseDetail/:id' component={components.caseDetail}/>
<Route component={components.notFound}/>
</Switch>
</main>
)
export default Router
And setup my webpack like this
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const merge = require('webpack-merge')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const env = require('./src/env.js')
const TARGET = process.env.npm_lifecycle_event
process.env.BABEL_ENV = TARGET
const PATHS = {
app: path.join(__dirname, 'src'),
build: path.join(__dirname, 'build'),
assets: path.join(__dirname, 'assets')
}
const common = {
entry: [PATHS.app],
module: {
rules: [
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
include: PATHS.app,
loaders: ['babel-loader']
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { modules: true } }],
include: /flexboxgrid/
},
{
test: /\.scss$/,
exclude: /flexboxgrid/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { modules: true } },
'sass-loader'
]
},
{
test: /\.jpe?g$|\.gif$|\.png$/i,
loader: 'url-loader?name=[name].[ext]'
},
{
test: /\.otf$|\.eot$|\.svg$|\.ttf|\.woff|\.woff2$/,
loader: 'url-loader?name=[name].[ext]'
}
]
},
resolve: {
extensions: ['.js', '.jsx'],
modules: ['node_modules', path.resolve(__dirname, './node_modules')],
mainFields: ['browser', 'web', 'browserify', 'main', 'style']
}
}
if (TARGET === 'start' || !TARGET) {
module.exports = merge(common, {
module: {
rules: [
{
test: /\.jsx?$/,
use: ['source-map-loader'],
enforce: 'pre'
}
]
},
devtool: 'inline-source-map',
devServer: {
contentBase: PATHS.build,
historyApiFallback: true,
hot: true,
inline: true,
progress: true,
stats: 'errors-only',
https: true,
host: env.host,
port: env.port,
overlay: {
errors: true
},
watchOptions: {
watch: true
}
},
plugins: [
new MiniCssExtractPlugin({ filename: 'assets/style.css' }),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: PATHS.app + '/index.html',
inject: 'body'
})
],
output: {
path: PATHS.build,
filename: 'bundle.js'
}
})
}
if (TARGET === 'production') {
module.exports = merge(common, {
plugins: [
new MiniCssExtractPlugin({ filename: 'style.css' }),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: "'production'"
}
})
],
output: {
path: '/build',
filename: 'bundle.js'
}
})
}
My problem is
When I call this route /caseDetail/1 it works by using Link:
<Link to={{ pathname: `/caseDetail/` + caseStudy.id }}>
BUT, if I call directly by the browser, like https://localhost:3000/caseDetail/1
It fails, I get this message on console
Refused to apply style from 'https://localhost:3000/caseDetail/assets/style.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
0:12 GET https://localhost:3000/caseDetail/bundle.js net::ERR_ABORTED 404
0:1 Refused to execute script from 'https://localhost:3000/caseDetail/bundle.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
SOLVED
How to fix. Thanks to #soroush-chehresa
I added into my router file, the BrowserRouter like this:
import React from 'react'
import { Switch, Route, BrowserRouter } from 'react-router-dom'
import { components } from './loader'
const Router = () => (
<main>
<BrowserRouter>
<Switch>
<Route exact path='/' component={components.main}/>
<Route exact path='/caseDetail' component={components.caseDetail}/>
<Route path='/caseDetail/:id' component={components.caseDetail}/>
<Route component={components.notFound}/>
</Switch>
</BrowserRouter>
</main>
)
export default Router
And also added into my webpack.config.js on output
publicPath: '/'
If you are using react-router-redux wrap Router with ConnectedRouter:
import { ConnectedRouter } from 'react-router-redux';
import { createBrowserHistory } from 'history';
const Router = () => (
<main>
<ConnectedRouter history={createBrowserHistory()}>
<Switch>
<Route exact path='/' component={components.main}/>
<Route path='/caseDetail/:id' component= {components.caseDetail}/>
<Route component={components.notFound}/>
</Switch>
</ConnectedRouter>
</main>
);
Otherwise if you are using react-router-dom wrap Router with BrowserRouter:
import { BrowserRouter } from 'react-router-dom';
const Router = () => (
<main>
<BrowserRouter>
<Switch>
<Route exact path='/' component={components.main}/>
<Route path='/caseDetail/:id' component={components.caseDetail}/>
<Route component={components.notFound}/>
</Switch>
</BrowserRouter>
</main>
);
When using hash history code splitting works with react router but now i'm about to go into production and i want to switch to browser-history it gives an error when i try to change route, example if i try going to the login route 127.0.0.1:8080/auth/login :
Refused to execute script from
'http://127.0.0.1:8080/auth/3.bundle.js' because its MIME type
('text/html') is not executable, and strict MIME type checking is
enabled.
and
Uncaught (in promise) Error: Loading chunk 3 failed. (error:
http://127.0.0.1:8080/auth/3.bundle.js)
at HTMLScriptElement.onScriptComplete (bootstrap:108)
This is my router
<Router history={history}>
<ConnectApp />
</Router>
Connect app:
<Switch>
{/* Front end */}
<Route path="/" component={AsyncHome} exact />
<Route path="/post/:id" component={AsyncPost} exact />
{/* authentication */}
<Route path="/auth/:section" component={AsyncLogin} exact />
{/* Backend */}
<PrivateRoute path="/admin/:path" component={AsyncAdmin} exact />
<PrivateRoute
path="/admin/edit-post/:id"
component={AsyncEditPost}
exact
/>
<Route component={Err404} />
</Switch>
history.js:
import { createBrowserHistory } from 'history';
export default createBrowserHistory({
// add configurations here
});
webpack.dev.config.js
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
historyApiFallback: true
},
plugins: [
new BundleAnalyzerPlugin()
]
}
*If there is any more code to add please indicate in the comment
Thank you
Add publicPath:"/" to the config:
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
historyApiFallback: true
},
plugins: [
new BundleAnalyzerPlugin()
],
output: {
path: path.resolve('dist'),
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
publicPath: '/' // Add this line
},
}
I am using Webpack 3.7.1 and React 15.6.1 and I am trying to load different components dynamically.
What I would like to do
Loading the components asynchronously from the different chunks webpack created when code splitting
What i did
Using getComponent() and import() to generate the chunks
Configured the webpack.config file properly so that the chunks are created (code splitting)
The issue
Chunks are generated but not loaded properly when accessing a route
getComponent() does not seem to work
My Webpack.config file
module.exports = {
devServer: {
historyApiFallback: true
},
entry: {
app:"./src/index.js",
vendor: [
"axios",
"react",
"react-dom",
"react-redux",
"react-router",
"react-router-dom",
"redux"
]
},
output: {
path: __dirname + '/public/views',
filename: '[name].js',
chunkFilename: '[chunkhash].chunk.js',
publicPath: "/views/"
},
module: {
loaders: [
{
test: /\.js$/,
loader: "babel-loader",
exclude: [/node_modules/, /pdfmake.js$/]
},
{
test: /\.json$/,
loader: "json-loader"
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: Infinity
}),
new webpack.NamedModulesPlugin(),
new HtmlWebpackPlugin({
filename: __dirname + "/views/index.ejs",
template: __dirname + "/views/template.ejs",
inject: 'body',
chunks: ['vendor', 'app'],
chunksSortMode: 'manual'
}),
new PreloadWebpackPlugin({
rel: "preload",
include: ["vendor", "app"]
}),
new webpack.optimize.OccurrenceOrderPlugin(),
]
};
My index.js file (root of my react app)
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import promise from "redux-promise";
import reducers from "./reducers";
import AppInit from "./containers/appInit";
import ProfRegisteringModal from "./containers/modals/register_prof_explanation_modal";
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
function errorLoading(err) {
console.error("Dynamic page loading failed", err);
}
function loadRoute(cb) {
return module => cb(null, module.default);
}
console.log("testst");
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<AppInit>
<BrowserRouter>
<div style={{ height: "100%" }}>
<ProfRegisteringModal />
<Switch>
<Route
path="/inscription/:user"
getComponent={(location, callback) => {
import(
"./components/registering/registering_landing_page.js"
)
.then(loadRoute(cb))
.catch(errorLoading);
}}
/>
<Route
path="/inscription"
getComponent={(location, callback) => {
import(
"./components/registering/registering_landing_page.js"
)
.then(loadRoute(cb))
.catch(errorLoading);
}}
/>
<Route
path="/connexion"
getComponent={(location, callback) => {
import("./containers/registering/signing_in.js")
.then(loadRoute(cb))
.catch(errorLoading);
}}
/>
<Route
path="/equipe"
getComponent={(location, callback) => {
import("./components/team_pres.js")
.then(loadRoute(cb))
.catch(errorLoading);
}}
/>
<Route
path="/"
getComponent={(location, callback) => {
import("./containers/app_container.js")
.then(loadRoute(cb))
.catch(errorLoading);
}}
/>
</Switch>
</div>
</BrowserRouter>
</AppInit>
</Provider>,
document.querySelector(".root")
);
This file got correctly loaded as I can see the console.log("test") appearing in my console.
None of the components are correctly loaded when accessing any of the routes.
Thank you very much for your help
I think what your code is missing is a way to trigger an update.
I remember solving this issue by creating a wrapper around the import() promise.
// AsyncComponent.js
export default function wrapper(importComponent) {
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
Comp: null
};
}
componentDidMount() {
importComponent()
.then(Comp => this.setState({
Comp
}))
.catch(err => this.setState({
error: err
}));
}
render() {
if(this.state.error) {
return <h2> Loading error
<button onClick={e => this.componentDidMount()}> Try again </button>
</h2>
}
const Comp = this.state.Comp;
return Comp ?
<Comp {...this.props} /> :
<div> Still Loading: You can add a spinner here </div>
}
}
return AsyncComponent;
}
// Routes.js
import AsyncComponent from './component/AsyncComponent';
const Users = AsyncComponent(() => import(/* webpackChunkName:"users" */ './Users'))
const Home = AsyncComponent(() => import(/* webpackChunkName:"home" */ './Home'))
const Equipe = AsyncComponent(() => import(/* webpackChunkName:"equipe" */ './Equipe'))
<Route path='/users' component={Users} />
<Route path='/equipe' component={Equipe} />
I have an app which is within a sub folder of my primary domain. So Test.com is my domain and my app is in a folder called 'Player' in that domain i.e Test.com/player.
The problem is React Router points to Test.com when it needs to point to Test.com/player.
The other issue is my index file is in a folder called 'public'.
How do I do this in React Router 4/htacess?
Thanks
App
import React from "react";
import {render} from "react-dom";
import {BrowserRouter, Route, Switch, hashHistory} from "react-router-dom";
import Nav from "./components/Nav";
import Home from "./components/Home";
import About from "./components/About";
import Contact from "./components/Contact";
class App extends React.Component {
render() {
return (
<BrowserRouter history={hashHistory}>
<div>
<Nav />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
<Route render={() => {
return <h1>Not Found!</h1>
}} />
</Switch>
</div>
</BrowserRouter>
);
}
}
render(<App />, document.getElementById("main"));
Webpack
const webpack = require("webpack");
module.exports = {
entry: {
filename: "./app/app.js"
},
output: {
filename: "./app/build/bundle.js"
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
query: {
presets: ["es2015", "react"]
}
}
]
},
devServer: {
inline: true,
port: 5000,
contentBase: "./public",
historyApiFallback: true,
}
}
According to React Router Docs, BrowserRouter has
basename: string The base URL for all locations. If your app is served
from a sub-directory on your server, you’ll want to set this to the
sub-directory. A properly formatted basename should have a leading
slash, but no trailing slash.
you can use
class App extends React.Component {
render() {
return (
<BrowserRouter history={hashHistory} basename="/player">
<div>
<Nav />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
<Route render={() => {
return <h1>Not Found!</h1>
}} />
</Switch>
</div>
</BrowserRouter>
);
}
}
You also need those properties on your webpack.config.js
module.exports = {
entry : 'your entry',
output : {
path : 'your path',
filename : 'your filename',
publicPath : '/' //ADD THIS LINE (either / or /player will work)
},
devServer : {
historyApiFallback : true // ADD THIS LINE
}
}