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…

Toggen builds a new website

So I have built and hosted a new website for Roger Clark Pest Management they are a family owned and operated Pest...

WizTree

Just downloaded WizTree (not a sponsor) from https://wiztreefree.com/ It seems to do what it says on the tin which is...

Scam call from “Telstra”

Just had a call from a man with a sub-continental accent from caller ID 0734946596 Told me there was problem with my...