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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
This is super cool thank you!! I use Laravel for API. I was trying to figure out how to my create-react-app on index.php. Using your same code above I can do this.
However is it possible to server side render the html on first load (even if its a deep link [not specifically index]) and then have react take over from there?
Actually I can’t figure out how to put a create-react-app into index.php of Laravel. May you please help? Your article and writing is amazing.
You don’t need to run create-react-app in your existing Laravel app. When you run php artisan preset:react, Laravel replaces existing Vue scaffolding and install React scaffolding along with all the necessary tools and dependencies to transpile JSX to browser ready code.
Thanks Waleed. I was hoping to do server side rendering with Laravel and React + redux + react-router. If you have some time would you be able to do this for beginners. I am struggling extremely a lot. 🙁
What an awesome set of tutorials you have made. Major thanks for sharing, and look forward to more! Subscribed!
I had a seperate project I created with create-react-app. I now want my laravel index.php to load my create-react-app, is this possible?
In Laravel React preset,
resources/assets/src/app.js
is the entry point of your React app. In create-react-app,src/App.js
is the entry point of your React app. you can copy whatever you have in your src folder in react-create-app to Laravel’sresources/assets/src
folder. Make sure you also update your project’swebpack.config.mix
file and pointmix.react()
path toresources/assets/src/App.js
instead ofresources/assets/src/app.js
.Thank you so much I wasn’t able to figure this out. If you get some time whenever in the future, it would be awesome to have an article on this. Everyone I know uses CRA. 🙂 Having an easy to copy paste to laravel would be so cool!
I think why I’m failing is because the CRA needs to be built with the build scripts provided by CRA, but im trying to make another build script build it.
I was trying a cop out solution now. I ran npm run build, and am trying to just paste these built files, is this way easier? May you please share how to do this method too? Thanks so much!