Laravel + React
This tutorial will show you how to use React with Laravel in a way that lets you sprinkle React into a legacy Laravel codebase and blade templates. We will not be creating an SPA or using Create React App.
You can view and download the full sample project.
https://github.com/jcz530/laravel-plus-react
After going through this guide...
- We'll be able to add React components into blade files.
- We'll have reusable components that can be combined to make complex components.
- We'll use webpack (Laravel Mix) to build our files.
- We will not have an SPA.
- React will not be served with SSR (Server Side Rendering).
- We will not be able to use the components as inline components like is popular with Vue.
Background
I was inspired to write this guide because recently, I added React into a legacy project of mine, and I didn't want to rewrite the whole project to turn it into a React SPA. Instead, I wanted to reap the benefits of writing new React components that I could start sprinkling into my project right away.
There are a lot of ways to get React to load and render components, and this is simply the method I choose when working on my project. I'll walk you through how and why I chose this setup.
First thing's first, navigate to your existing or new Laravel project.
Install Dependencies
npm i react react-dom
Folder Structure
In the /resources/js/
folder, we'll add a new folder where all of our React files will live. We want to keep these files all together and not mixed in with other JS files. This will keep the project organized, make some of the webpack setup easier, and allow for the use of other technologies.
In my case, I created a source folder for all of my React files at /resources/js/src/
.
I have the following folders in the src
folder.
- /src/components
- /src/hooks
- /src/layouts
- /src/pages
Your exact folders may vary depending on your needs and organizational style, but this could be a good place to start.
Laravel Mix - Webpack setup
Aliases
This step is optional, but I think it makes the project a lot easier and cleaner to work with. Defining aliases in the webpack configs will allow you to refer to your files without needing to know where in the file path you are.
For example, if you want to refer to your theme file from a component deep in the folder structure, without aliases, you might write
import theme from '../../../themes/theme.js'
With aliases, you would simply write
import theme from 'themes/theme.js'
To use aliases, you'll need to add them to your mix file webpack.mix.js
.
mix.webpackConfig({
resolve: {
alias: {
//adding react and react-dom may not be necessary for you but it did fix some issues in my setup.
'react' : path.resolve('node_modules/react'),
'react-dom' : path.resolve('node_modules/react-dom'),
'components' : path.resolve('resources/js/src/components'),
'pages' : path.resolve('resources/js/src/pages'),
'themes' : path.resolve('resources/js/src/themes'),
'layouts' : path.resolve('resources/js/src/layouts'),
'hooks' : path.resolve('resources/js/src/hooks'),
},
},
});
Bundle and Extract React
After you've added your aliases, you'll need to tell webpack to bundle your files and extract libraries. In the same webpack.mix.js
file, add the following line. Notice that we're using mix.react
and we are using app.js
. If your app.js file already has legacy code, you could create a new app file for the React components.
mix.react('resources/js/app.js', 'public/js').extract(['react', 'react-dom']);
Rendering the components
This is where things get tricky.
Even though we aren't building an SPA, we still want to be able to build complex components that reuse multiple components. We're going to be mixing React components into blade files, and it would be great if we could retain some of the JS feel for the components so that we know we're referring to a React component, and it's not just a random div with an id.
Instead of referring to components as <div id="MyComponent" />
We are instead going to use <MyComponent />
.
This isn't valid html, so if you want to use the id method, all you'll have to do is uncomment one of the lines in the ReactRenderer.js file coming up.
Create a simple component
We need a simple component to test with, and this is about as simple as they get.
Create a new file with the following code in src/components/MySimpleComponent.js
.
import React from 'react';
export default function MySimpleComponent(props) {
return (
<>
<h2>This was loaded from a React component.</h2>
</>
);
}
Set up app.js
Next, we need to set up the app.js file. These are the lines that you'll need to add to the app.js file.
require('./bootstrap')
import React from 'react'
import ReactRenderer from './src/ReactRenderer'
import MySimpleComponent from 'components/MySimpleComponent'
const components = [
{
name: "MySimpleComponent",
component: <MySimpleComponent />,
},
]
new ReactRenderer(components).renderAll()
A little explanation.
In our app.js file we will import any components that we want to use within the blade files and add them to an array. We'll use the 'name' element to find all the references to the component in the blade files, and we'll use the 'component' element to render it.
Next we need to add the ReactRenderer.js
file.
import React from 'react';
import ReactDOM from 'react-dom';
export default class ReactRenderer {
constructor(components) {
this.components = components;
}
renderAll() {
for (let componentIndex = 0; componentIndex < this.components.length; componentIndex++) {
// Use this to render React components in divs using the id. Ex, <div id="MySimpleComponent"></div>
// let container = document.getElementById(this.components[componentIndex].name);
// Use this to render React components using the name as the tag. Ex, <MySimpleComponent></MySimpleComponent>
let containers = document.getElementsByTagName(this.components[componentIndex].name)
if (containers && containers.length > 0) {
for (let i = containers.length - 1; i >= 0; i--) {
let props = this.getPropsFromAttributes(containers[i]);
let element = this.components[componentIndex].component;
if (props !== null) {
element = React.cloneElement(
element,
props
)
}
ReactDOM.render(element, containers[i]);
}
}
}
}
// Turns the dom element's attributes into an object to use as props.
getPropsFromAttributes(container) {
let props = {};
if (container.attributes.length > 0) {
for (let attributeIndex = 0; attributeIndex < container.attributes.length; attributeIndex++) {
let attribute = container.attributes[attributeIndex];
if (this.hasJsonStructure(attribute.value)) {
props[attribute.name] = JSON.parse(attribute.value);
} else {
props[attribute.name] = attribute.value;
}
}
return props;
}
return null;
}
hasJsonStructure(str) {
if (typeof str !== 'string')
return false;
try {
const result = JSON.parse(str);
const type = Object.prototype.toString.call(result);
return type === '[object Object]' || type === '[object Array]';
} catch (err) {
return false;
}
}
}
You can read through the code to more fully understand what is happening. At its core, it's just finding all DOM elements that match your components and rendering them with any props included as well.
Put it to work
Now that we have everything in place, we can start to build more components and add them to blade files.
Here are some examples of adding it to blade files.
...
<MySimpleComponent></MySimpleComponent>
@guest
<MySecondComponent
title="This is using blade's {{'@'}}guest helper to show to 'Guests' only"
/>
@endguest
@auth
{{-- Remember to use "json_encode" to pass in objects --}}
<MySecondComponent
title="This is showing to authed users"
user="{{ json_encode(auth()->user()) }}"
/>
@endauth
...
In the source code for this tutorial, I've also included a second component that accepts a title
prop. This code is a snippet from the app.blade.php
file in the source code.
If you download and run the sample project, you will get something that looks like this.
I encourage you to download the repo, explore, and make modifications to test it out. https://github.com/jcz530/laravel-plus-react