Embedding ReactJS in a CakePHP View

Written by James McDonald

August 11, 2018

When people say they want to use  CakePHP with React typically the recommendation is to convert CakePHP into an API server and then add ReactJS as the front-end served on a different URL then CakePHP.

But what about when you have a CakePHP application and you want to use CakePHP authentication and routing and even a lot of the CRUD views that you have baked?

The following is how to Embed React into a view in CakePHP using the following:

  • An existing CakePHP 3.6+ application
  • create-react-app

How it works

  • The create-react-app build command is modified to copy the production build static react files to the cakephp webroot/react folder.
  • The CakePHP Controller action and view file is then modified to read the create-react-app provided asset-manifest.json file which contains relative paths to the JS and CSS files needed for the react app.

Example asset-manifest.json

When react compiles a production build it inserts a random string in the assets. But handily provides asset-manifest.json to map the resources

{
    "main.css": "static/css/main.ed1a35cf.css", 
    "main.css.map": "static/css/main.ed1a35cf.css.map", 
    "main.js": "static/js/main.bdb0c67c.js", 
    "main.js.map": "static/js/main.bdb0c67c.js.map"
}

Change the React Projects package.json to build and move react to CakePHP webroot

{
"scripts": {
    "start": "react-scripts start",
	"build": "react-scripts build && rm -rf /Path/To/CakePHP/app/webroot/pick-app && cp -rv build /Path/To/CakePHP/app/webroot/pick-app",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
}

Modify the CakePHP Controller Action to Read the React asset-manifest.json

public function react(){
        $filePath = WWW_ROOT . '/react/asset-manifest.json';
        $file = new File($filePath);

        $manifest = json_decode($file->read());
        $file->close();

        $maincss = 'main.css';
        $mainjs = 'main.js';

        $css = '/react/' . $manifest->$maincss;
        $js = '/react/' . $manifest->$mainjs;

        $this->set(compact('css', 'js'));  
}

Modify Template File to Create React Mount Point

<?php
// src/Template/Articles/react.ctp
$this->Html->css($css, [
    'block' => true,
]); ?>
<!-- this is where the react page mounts -->
<div id="root"></div>

<?=$this->Html->script($js); ?>

Handling Access CORS headers

<?php
// create this file in
// src/App/Middleware/HttpOptionsMiddleware.php
// modify src/Application.php to hook this into
// the CakePHP middleware chain
namespace App\Middleware;

class HttpOptionsMiddleware
{
    public function __invoke($request, $response, $next)
    {

        $response = $response
            ->withHeader('Access-Control-Allow-Origin', 'http://localhost:3000')
            ->withHeader('Access-Control-Allow-Credentials', 'true');

        if ($request->getMethod() == 'OPTIONS') {

            $method = $request->getHeader('Access-Control-Request-Method');
            $headers = $request->getHeader('Access-Control-Request-Headers');
            $allowed = empty($method) ? 'GET, POST, PUT, DELETE' : $method;

            $response = $response
                ->withHeader('Access-Control-Allow-Headers', $headers)
                ->withHeader('Access-Control-Allow-Methods', $allowed)
                ->withHeader('Access-Control-Allow-Credentials', 'true')
                ->withHeader('Access-Control-Max-Age', '86400');

            return $response;
        }

        return $next($request, $response);
    }
}

Example Fetch Configuration

fetch('http://yourhost.dev/articles',
    {
        mode: 'cors',
        method: 'POST',
        cache: 'no-cache',
        credentials: "include",
        headers: {
            // need X-Requested-With header
            // so you CakePHP can check it's ajax 
            // with $this->request->is('ajax');
            'X-Requested-With': 'XMLHttpRequest',
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            user_id: 1,
            title: e.target.elements.title.value,
            body: e.target.elements.body.value
        }),
    }).then((response) => {
        if (response.ok) {

            // do another fetch here
            // to update state after
            // creating an article

        }
        return response.json()
        throw new Error(response.status + " " + response.statusText)
    }).catch((e) => console.log(e))

I put it on GitHub

Here is a link to the github repository for cakephp-react-backend

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

You May Also Like…

How to Research a CPU Upgrade

How to Research a CPU Upgrade

Upgrade Time! Doing a lot of VMWare Workstation virtualization to create labs for self-study and training. Finding...