Writing re-usable components in Knack with React

This may be useful to some who are familiar with React and want to make re-usable components within their applications. I’ve only tried this with React, however, there should be no reason why you can’t do this with other frontend frameworks/libraries.

To start of with, we’ll build a simple counter button to get a grasp of the setup and syntax, then we’ll build something more real-world, a toast component.

To start with, on the app initialization, we’re pulling in a few libraries, and binding html and Components as global variables.

html is giving us JSX syntax, so we can use JS in our code. Components is self explanatory, this will contain a list of objects containing our components.

KnackInitAsync = function ($, callback) {
  window.$ = $;
  LazyLoad.js(
    [
      "https://unpkg.com/htm@2.2.1",
      "https://unpkg.com/react@17/umd/react.production.min.js",
      "https://unpkg.com/react-dom@17/umd/react-dom.production.min.js",
    ],
    function () {
      window.html = htm.bind(React.createElement);

      window.Components = {};

      callback();
    }
  );
};

Let’s start off by creating a Toggle component which increments the count on click of the button and displays the count.

const { useState } = React;
// ...

window.Components = {
  Toggle,
};

function Toggle() {
  const [count, setCount] = useState(0);
  
  return html`
    <div>
      <button onClick=${() => setCount(prev => prev + 1)}>Increase Count</button>
      <div>Current count: ${count}</div>
    </div>
  `;
}

Now, in our render view function, we can bring in our Toggle component from our Components variable and render the component to a wrapper element.

$(document).on("knack-view-render.view_1", function (event, view, data) {
  const { Toggle } = Components;

  $("#view_1").append(`
    <div id="toggleWrapper"></div>
  `);

  ReactDOM.render(
    html`<${Toggle} />`,
    document.querySelector("#toggleWrapper")
  );
});

Now that we got an understanding how this works, let’s create something more real-world. In this example, we’ll create a toast notification component.

function ToastNotification({ heading, content }) {
  window.Components = {
    Toggle,
  };
  // ...
  function ToastNotification({ heading, content }) {
    return html`
      <div class="toast-wrapper">
        <div class="heading-wrapper">
          <i class="fa fa-check-circle"></i>
          <span class="toast-heading">${heading}</span>
        </div>
        <span class="toast-content">${content}</span>
      </div>
    `;
  }

Now we can create a function, which renders our ToastNotification component, and trigger the function in our Knack render view function.

function displayToast(heading, content) {
  const { ToastNotification } = Components;
  $("body").append(`<div id="toastNotification"></div>`);

  ReactDOM.render(
    html` <${ToastNotification} heading=${heading} content=${content} /> `,
    document.querySelector(`#toastNotification`)
  );
  setTimeout(() => {
    $("#toastNotification").remove();
  }, 2500);
}

$(document).on("knack-view-render.view_1", function (event, view, data) {
  displayToast("Success", "This is our success message");
});

And some basic css to display the toast message on the top right the screen.

.toast-wrapper {
  width: 250px;
  padding: 15px 25px;
  color: #fff;
  background: #22c55e;
  display: inline-block;
  position: fixed;
  top: 20px;
  right: 20px;
  border-radius: 10px;
  transition: all 0.4s ease-out;
}
.heading-wrapper {
  display: flex;
  align-items: center;
}
.heading-wrapper > i {
  font-size: 18px;
  margin-right: 8px;
}

.toast-heading {
  font-size: 16px;
  font-weight: 600;
}

.toast-content {
  display: block;
}

These components don’t nessesarily need to be in Knack, and can be hosted externally and used in multiple projects.

Happy to provide more examples if people find this useful

3 Likes

Thanks for this. Although I don’t know React I do know Vue and you prompted me to try implementing that. I similarly included the library in LazyLoad.js() and then initialized the Vue app in a scene render. I wrote it all inline though. But you could declare your components as global variables in Knack’s custom javascript and then they’re basically reusable.

I am still newish to Vue so there might be a better way to do this.

KnackInitAsync = function($, callback) {
    window.$ = $;
    LazyLoad.js(["https://unpkg.com/vue@3"], function() {
        callback();
    });
};

const my_component_definition = {
    props: ["stuff"],
    template: `
         <div>
            <span>{{ stuff.text }}</span>
        </div>
    `,
};

$(document).on("knack-scene-render.scene_430", function(event, scene) {
    const { createApp } = Vue;

    let vue_html = $.parseHTML(`
        <div id="vue_app">
            <h1>{{ greeting }}</h1>
            <my-component :stuff="some_obj"></my-component>
        </div>
    `);

    $("#kn-scene_430").append(vue_html);

    let vue = createApp({
        data() {
            return {
                greeting: "Hello",
                some_obj: {
                    text: "Hey there",
                },
            }
        },
        methods: { },
        computed: { },
        created() { },
    })
    .component("my-component", my_component_definition)
    .mount("#vue_app");
});

I only wanted it for one page in particular so I’m getting the data I need from the API. Works just fine!

1 Like