Previous versions of React Router used a static routing approach for building single page applications where you would define a route to render a component against it after it matches the specified URL. This approach didn’t conform to the principles of React. React Router devs were not happy about it either. So they decided to change the API and switch to dynamic routing model where almost everything is a component in React Router.

In this tutorial, I’m using React v16.2.0, React Router 4.2.2 and Laravel 5.6.

{ "scripts": { [...] }, "devDependencies": { [...] "laravel-mix": "^2.0", "react-dom": "^16.2.0", "react-router": "^4.2.0", "react-router-dom": "^4.2.2" [...] } } 1 2 3 4 5 6 7 8 9 10 11 12 13 { "scripts" : { [ . . . ] } , "devDependencies" : { [ . . . ] "laravel-mix" : "^2.0" , "react-dom" : "^16.2.0" , "react-router" : "^4.2.0" , "react-router-dom" : "^4.2.2" [ . . . ] } }

After a fresh Laravel installation run php artisan preset react and npm install && npm run dev to replace existing Vue scaffolding with React and install dependencies. Now add react router to your project by running npm install react-router react-router-dom --save-dev. Now we’ll be making some structural changed to our resources folder. Laravel resources folder has two subfolders js and scss. There are no hard and fast rules for structuring react apps but I like to keep JS and CSS files in the same folder. So I’ll be moving app.scss and _variables.scss files to js folder and remove it. Now rename js folder to src.

It will look something like this after making changes. We also need to modify our webpack.mix.config to make use of this structure. I wrote a very detailed tutorial on how to extract CSS from your react components with Laravel mix. This is how webpack.mix.js will look like.

let mix = require('laravel-mix'); const ExtractTextPlugin = require("extract-text-webpack-plugin"); mix.react('resources/assets/src/app.js', 'public/assets/bundle') .version() .disableNotifications(); mix.webpackConfig({ module: { rules: [ { test: /\.s[ac]ss$/, exclude : [], loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use : [ { loader : 'css-loader', }, { loader : 'sass-loader', } ] }) } ] }, plugins: [ new ExtractTextPlugin('[name].css') ], devtool : 'source-map' }).sourceMaps(); mix.options({ processCssUrls: false }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 let mix = require ( 'laravel-mix' ) ; const ExtractTextPlugin = require ( "extract-text-webpack-plugin" ) ; mix . react ( 'resources/assets/src/app.js' , 'public/assets/bundle' ) . version ( ) . disableNotifications ( ) ; mix . webpackConfig ( { module : { rules : [ { test : / \ . s [ ac ] ss $ / , exclude : [ ] , loader : ExtractTextPlugin . extract ( { fallback : 'style-loader' , use : [ { loader : 'css-loader' , } , { loader : 'sass-loader' , } ] } ) } ] } , plugins : [ new ExtractTextPlugin ( '[name].css' ) ] , devtool : 'source-map' } ) . sourceMaps ( ) ; mix . options ( { processCssUrls : false } ) ;

We’re using extract-text-webpack-plugin to extract CSS to dedicated file because there’s no option available to do it automatically in laravel mix.

Now create a view under resources/views directory and name it app.blade.php.

<!DOCTYPE html> <html> <head> @yield('title') <meta name="csrf-token" content="{{csrf_token()}}" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" type="text/css" href="{{mix('/assets/bundle/app.css')}}"> </head> <body> <div id="root"></div> <script type="text/javascript" src="{{mix('/assets/bundle/app.js')}}"></script> </body> </html> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE html> <html> <head> @yield('title') <meta name = "csrf-token" content = "{{csrf_token()}}" /> <meta name = "viewport" content = "width=device-width, initial-scale=1.0" > <link rel = "stylesheet" type = "text/css" href = "{{mix('/assets/bundle/app.css')}}" > </head> <body> <div id = "root" > </div> <script type = "text/javascript" src = "{{mix('/assets/bundle/app.js')}}" > </script> </body> </html>

Now add a route to serve it against every incoming request.

<?php Route::get('{all?}', function(){ return view('app'); })->where('all', '([A-z\d-\/_.]+)?'); 1 2 3 4 5 <?php Route:: get ( '{all?}' , function ( ) { return view ( 'app' ) ; } ) -> where ( 'all' , '([A-z\d-\/_.]+)?' ) ;

All routes defined above this will return their specified responses. We’re rendering app.blade.php view against every incoming request and including all the necessary scripts to bootstrap our SPA so that react router can take care of routing and rendering of React components.

In our app.js file, we are requiring app.scss file which includes bootstrap. Bootstrap is already available in react preset. if it’s not installed, you can install it by running npm install bootstrap --save-dev.

require('./app.scss'); require('./bootstrap'); require('./components/Example'); 1 2 3 4 5 require ( './app.scss' ) ; require ( './bootstrap' ) ; require ( './components/Example' ) ;

On the second line, we are requiring bootstrap.js file which includes bootstrap, jquery, and axios. Now run npm run dev and open your app in the browser and you’ll be presented with this example react component.

For our crypto app, I’m using coinmarketcap API to fetch cryptocurrencies data. Get rid of the example component and modify app.js file.

import './app.scss'; import './bootstrap'; import React from "react"; import ReactDOM from 'react-dom'; import {BrowserRouter} from 'react-router-dom'; import Root from './components/Root' if($('#root').length){ ReactDOM.render( <BrowserRouter> <Root/> </BrowserRouter>, document.getElementById('root') ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import './app.scss' ; import './bootstrap' ; import React from "react" ; import ReactDOM from 'react-dom' ; import { BrowserRouter } from 'react-router-dom' ; import Root from './components/Root' if ( $ ( '#root' ) . length ) { ReactDOM . render ( < BrowserRouter > < Root / > < / BrowserRouter > , document . getElementById ( 'root' ) ) ; }

We’re requiring React, ReactDOM and BrowserRouter. We’re using BrowserRouter instead of HashRouter because we’re rendering app view against every possible route and we’re sure about it. We use HashRouter in situations where we have limited access to the server or serving our app statically via index.html file. We are rendering Root Component which is further divided into two components.

import React from "react"; import {Route} from 'react-router'; import CurrencySidebar from './CurrencySidebar'; import Currency from './Currency'; import './Currencies.scss'; class Root extends React.Component{ constructor(props){ super(props); this.state = { currencies : [] } this.api_url = 'https://api.coinmarketcap.com/v1/ticker/?limit=20'; } componentDidMount(){ $.ajax({ type : 'GET', url : this.api_url, dataType: "json", crossDomain: true, success : function(response){ this.setState({ currencies : response }) }.bind(this) }) } render(){ return ( <div> <Route path="/" render={(props) => ( <CurrencySidebar currencies={this.state.currencies} {...props} /> )}/> <Route exact path="/currency/:id" render={(props) => ( <Currency {...props}/> )}/> </div> ); } } export default Root; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import React from "react" ; import { Route } from 'react-router' ; import CurrencySidebar from './CurrencySidebar' ; import Currency from './Currency' ; import './Currencies.scss' ; class Root extends React . Component { constructor ( props ) { super ( props ) ; this . state = { currencies : [ ] } this . api_url = 'https://api.coinmarketcap.com/v1/ticker/?limit=20' ; } componentDidMount ( ) { $ . ajax ( { type : 'GET' , url : this . api_url , dataType : "json" , crossDomain : true , success : function ( response ) { this . setState ( { currencies : response } ) } . bind ( this ) } ) } render ( ) { return ( < div > < Route path = "/" render = { ( props ) = > ( < CurrencySidebar currencies = { this . state . currencies } { . . . props } / > ) } / > < Route exact path = "/currency/:id" render = { ( props ) = > ( < Currency { . . . props } / > ) } / > < / div > ) ; } } export default Root ;

In componentDidMount() lifecycle method, I’m fetching top 20 cryptocurrencies from API and updating Root component’s currencies state. CurrencySidebar component takes currencies prop and renders them.

import React from 'react'; import {Link} from 'react-router-dom'; class CurrencySidebar extends React.Component{ constructor(props){ super(props); this.state = { currencies : this.props.currencies } } componentWillReceiveProps(nextProps){ this.setState({ currencies : nextProps.currencies }); } render(){ return( <div className="currencies col-xs-12 col-sm-12 col-md-3 col-lg-2 "> {this.state.currencies.length ? this.state.currencies.map(function(currency){ return ( <li key={currency.id}> <Link to={'/currency/'+currency.id} className={this.props.location.pathname === '/currency/'+currency.id ? "active" : ''} > {currency.name} </Link> </li> ) }.bind(this)) : <div>Loading...</div>} </div> ); } } export default CurrencySidebar; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import React from 'react' ; import { Link } from 'react-router-dom' ; class CurrencySidebar extends React . Component { constructor ( props ) { super ( props ) ; this . state = { currencies : this . props . currencies } } componentWillReceiveProps ( nextProps ) { this . setState ( { currencies : nextProps . currencies } ) ; } render ( ) { return ( < div className = "currencies col-xs-12 col-sm-12 col-md-3 col-lg-2 " > { this . state . currencies . length ? this . state . currencies . map ( function ( currency ) { return ( < li key = { currency . id } > < Link to = { '/currency/' + currency . id } className = { this . props . location . pathname === '/currency/' + currency . id ? "active" : '' } > { currency . name } < / Link > < / li > ) } . bind ( this ) ) : < div > Loading . . . < / div > } < / div > ) ; } } export default CurrencySidebar ;

In our CurrencySidebar component, we’re updating currencies state with props. We are also implemeting componentWillReceiveProps() lifecycle method because on the initial render props will be empty. Root component’s componentDidMount() is making an asynchronous ajax call to fetch currencies and when it gets a response, it will pass down new props to CurrencySidebar component and we’ll update state with new props. When populating with data, it will look like this.

In our Root component, we are rendering CurrencySidebar for all paths starting with ‘/’. We are rendering a list of currencies with clickable links to '/currencies/:id' route in CurrencySidebar component. <Link/> lets us navigate without reloading the page. We’ll render Currency component only when user visits '/currencies/:id' route with a valid cyrpto id. This is our Currency component.

import React from 'react'; class Currencies extends React.Component{ constructor(props){ super(props); this.state = { currency: '' }; this.api_url = 'https://api.coinmarketcap.com/v1/ticker/'; } componentDidUpdate(prevProps, prevState){ if (this.props.location !== prevProps.location) { this.getCurrency(this.api_url + this.props.match.params.id + '/'); } } componentDidMount(){ this.getCurrency(this.api_url + this.props.match.params.id + '/') } getCurrency(url){ $.ajax({ type : 'GET', url : url, dataType: "json", crossDomain: true, success : function(response){ this.setState({ currency : response[0] }) }.bind(this) }) } render(){ return( <div className="currency col-xs-12 col-sm-12 col-md-9 col-lg-10"> { this.state.currency ? <div> <h1 className="display-2"> {this.state.currency.name} </h1> <dl className="row"> <dt className="col-sm-3">Symbol</dt> <dd className="col-sm-9"> {this.state.currency.symbol} </dd> <dt className="col-sm-3">Rank</dt> <dd className="col-sm-9"> {this.state.currency.rank} </dd> <dt className="col-sm-3">Price USD</dt> <dd className="col-sm-9"> {this.state.currency.price_usd} </dd> <dt className="col-sm-3">Price BTC</dt> <dd className="col-sm-9"> {this.state.currency.price_btc} </dd> <dt className="col-sm-3">24H Volume USD</dt> <dd className="col-sm-9"> {this.state.currency['24h_volume_usd']} </dd> <dt className="col-sm-3">Market Cap USD</dt> <dd className="col-sm-9"> {this.state.currency.market_cap_usd} </dd> <dt className="col-sm-3">Available Supply</dt> <dd className="col-sm-9"> {this.state.currency.available_supply} </dd> <dt className="col-sm-3">Total Supply</dt> <dd className="col-sm-9"> {this.state.currency.total_supply} </dd> <dt className="col-sm-3">Max Supply</dt> <dd className="col-sm-9"> {this.state.currency.max_supply} </dd> <dt className="col-sm-3">Percentage Change 1H</dt> <dd className="col-sm-9"> {this.state.currency.percent_change_1h} </dd> <dt className="col-sm-3">Percentage Change 24H</dt> <dd className="col-sm-9"> {this.state.currency.percent_change_24h} </dd> <dt className="col-sm-3">Percentage Change 7D</dt> <dd className="col-sm-9"> {this.state.currency.percent_change_7d} </dd> </dl> </div> : <div>Loading Currency</div> } </div> ); } } export default Currencies; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 import React from 'react' ; class Currencies extends React . Component { constructor ( props ) { super ( props ) ; this . state = { currency : '' } ; this . api_url = 'https://api.coinmarketcap.com/v1/ticker/' ; } componentDidUpdate ( prevProps , prevState ) { if ( this . props . location !== prevProps . location ) { this . getCurrency ( this . api_url + this . props . match . params . id + '/' ) ; } } componentDidMount ( ) { this . getCurrency ( this . api_url + this . props . match . params . id + '/' ) } getCurrency ( url ) { $ . ajax ( { type : 'GET' , url : url , dataType : "json" , crossDomain : true , success : function ( response ) { this . setState ( { currency : response [ 0 ] } ) } . bind ( this ) } ) } render ( ) { return ( < div className = "currency col-xs-12 col-sm-12 col-md-9 col-lg-10" > { this . state . currency ? < div > < h1 className = "display-2" > { this . state . currency . name } < / h1 > < dl className = "row" > < dt className = "col-sm-3" > Symbol < / dt > < dd className = "col-sm-9" > { this . state . currency . symbol } < / dd > < dt className = "col-sm-3" > Rank < / dt > < dd className = "col-sm-9" > { this . state . currency . rank } < / dd > < dt className = "col-sm-3" > Price USD < / dt > < dd className = "col-sm-9" > { this . state . currency . price_usd } < / dd > < dt className = "col-sm-3" > Price BTC < / dt > < dd className = "col-sm-9" > { this . state . currency . price_btc } < / dd > < dt className = "col-sm-3" > 24H Volume USD < / dt > < dd className = "col-sm-9" > { this . state . currency [ '24h_volume_usd' ] } < / dd > < dt className = "col-sm-3" > Market Cap USD < / dt > < dd className = "col-sm-9" > { this . state . currency . market_cap_usd } < / dd > < dt className = "col-sm-3" > Available Supply < / dt > < dd className = "col-sm-9" > { this . state . currency . available_supply } < / dd > < dt className = "col-sm-3" > Total Supply < / dt > < dd className = "col-sm-9" > { this . state . currency . total_supply } < / dd > < dt className = "col-sm-3" > Max Supply < / dt > < dd className = "col-sm-9" > { this . state . currency . max_supply } < / dd > < dt className = "col-sm-3" > Percentage Change 1H < / dt > < dd className = "col-sm-9" > { this . state . currency . percent_change_1h } < / dd > < dt className = "col-sm-3" > Percentage Change 24H < / dt > < dd className = "col-sm-9" > { this . state . currency . percent_change_24h } < / dd > < dt className = "col-sm-3" > Percentage Change 7D < / dt > < dd className = "col-sm-9" > { this . state . currency . percent_change_7d } < / dd > < / dl > < / div > : < div > Loading Currency < / div > } < / div > ) ; } } export default Currencies ;

In our componentDidMount() lifecycle method we’re calling getCurrency(url) method to fetch a single currency with matching id. We’are also implementing componentDidUpdate(prevProps, prevState) to handle a case where the user navigates through the list and click another currency. We are also checking location to make sure redirect happened. If there was a change, we’re calling API again with a new id and updating state with a new response. After applying some styling to our components in Currencies.scss file.

.currency, .currencies{ padding : 0px; display: inline-block; float:left; } .currencies{ background-color:black; li{ height: 50px; list-style: none; width: 100%; a{ text-decoration: none; padding: 15px; display: block; width: 100%; height: 100%; color:white; .fa{ margin-right: 5px; } &:hover, &.active{ color:black; background-color:white; } } } } .currency{ h1, .row{ margin-left: 20px; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 .currency, .currencies { padding : 0px ; display : inline-block ; float : left ; } .currencies { background-color : black ; li { height : 50px ; list-style : none ; width : 100% ; a { text-decoration : none ; padding : 15px ; display : block ; width : 100% ; height : 100% ; color : white ; .fa { margin-right : 5px ; } &:hover, &.active { color : black ; background-color : white ; } } } } .currency { h1, .row { margin-left : 20px ; } }

Our crypto app is ready for display.

I’ve also created an example code GitHub repo. If you’ve any issue, please comment and I’ll try to help you.