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 /Users/jmcd/sites/caketmp/webroot/react && cp -rv build /Users/jmcd/sites/caketmp/webroot/react",
    "test": "react-scripts test --env=jsdom",
    "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