Cables + React , how to embed a patch in a React.Component?

Hey Guys,

Have anyone manage to get your cables patch embedded in a React.Component ? Been trying a few different approaches but no success :confused:

Thanks

this is a very rough version of what we do in one of our projects, you need to fix some paths and container ids to your liking. but in general this should give you an idea:

class CablesPatch extends React.Component {
  constructor(props) {
    super(props);
    this.canvasRef = React.createRef();
    this.patch = null;
  }

  componentWillMount() {
  }

  async componentDidMount() {

    const [libCore] = 'path/to/libs.core.min.js';
    const [cablesMin] = 'path/to/cables.min.js';
    const [ops] = 'path/to/ops.js';
    const [patchFile] = 'path/to/the_individual_patch.json';

    scriptjs([libCore, cablesMin], 'cablesLib');
    scriptjs.ready('cablesLib', () => {
      scriptjs(ops, 'ops');
    });

    scriptjs.ready('ops', () => {
      this.patch = window.CABLES.EMBED.addPatch("MYCONTAINERELEMENT", {
        patchFile,
        prefixAssetPath: '',
        onError: err => {
          throw err;
        },
        glCanvasResizeToParent: true
      });
    });
  }

  render() {
    return (
        <div id="MYCONTAINERELEMENT" ref={this.canvasRef} style={{ width: "100%", "min-height": "600px" }}/>
    );
  }
}

basically following the “simple version” in: https://docs.cables.gl/dev_embed/dev_embed.html

2 Likes

Hey Stephan,

Thanks for the help ! definitely got me to a solution. scriptjs doesn’t really work for me but I manage to do an ugly workaround loading each individual async .

Thanks for your help

fyi: soon there will be more export options to export patches minified in only one .js file

2 Likes

Also quick one ! There’s a misspelled consolr on the error output inside cables.min
Thanks for the great work guys .

Hi Pedro,

I am the one who wrote the React integration for our cables patch and here are some caveats I found on the way:

Working with document and async loading tags can lead to some weird unexpected behaviour. I would refrain from doing this:

        const script = document.createElement("script");

        script.src = "https://use.typekit.net/foobar.js";
        script.async = true;

        document.body.appendChild(script);
    }

Refrain from loading the scripts asynchronously, because you need the canvas to be there before the scripts are executed. Managing this in didMount can be quite tricky especially if you work with state management libraries like MobX or Redux. I couldn’t get it to work with react-helmet (still don’t know why). Another package to try could be react-script-tag but personally, I thought scriptjs works best because the syntax is clear and concise & you can use it in componentDidMount with no problems at all.

The order of the script loading is important atm. As pandur pointed out, there will be only 1 JS file in the future but the general sequence should be something like this:

  1. lib.core
  2. cables
  3. external libs like perlin
  4. ops

If you load them in a different order, patch instantiation will fail.

So in general:

  1. Mount the dom
  2. Load scripts in given order, a library is helpful here (componentDidMount!!)
  3. invoke CABLES.Patch constructor

You can also write me on slack if there are further questions (simo).

regards,

s.

Is there an updated way of doing this now that would match with how the cables-webpack-skeleton is setup? I’m trying to get this setup with choojs and figured this would be a good start but I can’t seem to figure it out.

I am also not clear on what’s happening with ‘scriptjs’. Is that a package? I have never heard of that.

hey,

have you tried setting up the webpack-skeleton? i am not really familiar with choojs, so
it would be great if you could get into more detail on what problems you have.

the general way of integrating a patch into an existing projects is still:

  • export patch
  • put stuff into accessible folder (i.e. public/)
  • load patch in html header
  • optional: set variables from outside

webpack-skeleton does some of these steps for you (1-3) but you need to integrate your framework into the skeleton. cables is (still) not a full javascript-module that can be used in “import” statements.

hope that helps a bit, otherwise i’d need a bit more info on your exact problems.

Hello,

Choo works a lot like react as far as I know. I am using it because a starter project was built with it that I can not rebuild. I get an error that says there was a problem parsing the file when I try to import it into a component (not sure why it isn’t letting me link to github):

var Nanocomponent = require('nanocomponent')
var html = require('nanohtml')
const cab = require('./patch/js/patch.js');

class Button extends Nanocomponent {
  constructor() {
    super()
    this.color = null
  }

  createElement(color) {
    this.color = color
    // console.log(patch)
    return html `
      <button style="background-color: ${color}">
        Click Me
      </button>
    `
  }

  // Implement conditional rendering
  update(newColor) {
    return newColor !== this.color
  }
}
 
module.exports = Button

Is there something about how cables is packaged that doesn’t allow it to be imported like this?

There might be something strange about how choo is parsing this, I really don’t know. Any thoughts or guesses are appreciated, I understand that most people have never used choo.

Ok, I am now just trying to get this to work in react like the example above. With this code I get an error:

Uncaught TypeError: Cannot read property 'EMBED' of undefined
import React from 'react'
import scriptjs from 'scriptjs'

class CablesPatch extends React.Component {
  constructor(props) {
    super(props);
    this.canvasRef = React.createRef();
    this.patch = null;
  }

  async componentDidMount() {

    const [libCore] = '../assets/patch/js/libs.core.min.js';
    const [cablesMin] = '../assets/patch/js/cables.min.js';
    const [ops] = '../assets/patch/js/ops.js';
    const [patchFile] = '../assets/patch/js/new_project.json';

    scriptjs([libCore, cablesMin], 'cablesLib');
    scriptjs.ready('cablesLib', () => {
      scriptjs(ops, 'ops');
      console.log('loaded!', ops)
    });
   
    scriptjs.ready('ops', () => {
      this.patch = window.CABLES.EMBED.addPatch("MYCONTAINERELEMENT", {
        patchFile,
        prefixAssetPath: '',
        onError: err => {
          throw err;
        },
        glCanvasResizeToParent: true
      });
    });
  }

  render() {
    return (
        <div id="MYCONTAINERELEMENT" ref={this.canvasRef} style={{ width: "100%", "minHeight": "600px" }}/>
    );
  }
}

export default CablesPatch

Any ideas? Thanks.

hey,

cables does not really work with the “require” or “import” at the moment, what we do to use
it in react is this in “index.html”:

<script src="%PUBLIC_URL%/js/patch.js"></script>

and then in your component:

 if (this.patch === null) {
  this.patch = new window.CABLES.Patch({
    patch: window.CABLES.exportedPatch,
    doRequestAnimation: false,
    clearCanvasColor: false,
    clearCanvasDepth: false,
    glCanvas: this.canvas,
    glCanvasResizeToWindow: true
    onError: function(e) {
      console.error('err', e);
    }
  });
}
window.cgl = this.patch.cgl;

depending on your canvas setup you also might need something like:

    <canvas id="glcanvas" width="800" height="480"></canvas>

and then do something like this in your component:

 this.patch=new window.CABLES.Patch(
        {
            patch: window.CABLES.exportedPatch,
            prefixAssetPath:'',
            glCanvasId:'glcanvas',
            onError:alert
        });

or any combination of the above…really depends on your general setup…