DEV Community

Rafael Cachoeira
Rafael Cachoeira

Posted on • Updated on

Invoke React components from data-attributes

Motivation

I have an ASPNET CORE MVC + jQuery application and I need to gradually migrate some components to React!

Since we already have the data from the ViewModel, I would like to pass it to the React component.

Proposal

Why not use data attributes?
I created a data attribute structure that can be read in React and invoke my components (Lazy) with their properties.
That way, I don't need to write javascript code every time to bind 'react' to html.

Requisites

  • Webpack
  • Webpack chunk
  • React

My structure of data-attributes

  <div data-js-component="FavoriteButton"
       data-js-props='{
          "favorite": @Model.Document.Favorite.ToString().ToLower()
      }'>
  </div>
Enter fullscreen mode Exit fullscreen mode
  • data-js-component: string (name of component to scan and invoke)
  • data-js-props: json (all properties of initial state)

My React Component

import React from 'react';

export default function FavoriteButton({ favorite }) {
  ...
  ...
}

Enter fullscreen mode Exit fullscreen mode

My InvokeComponents:

How to Works

First, register your components with their respective path to lazy import on 'components' object.
It will be searched for [data-js-component] in the html. When the element is found, it will be read from the 'components' object.
The [data-js-props] will be cast to json and pass to React Component found.


import { lazy, Suspense } from 'react';
import ReactDOM from 'react-dom';

const InvokeComponents = (function() {

    //register here your components
    const components = {
        DocumentFavoriteButton: lazy(() => import('./Documents/DocumentFavoriteButton')),
        FavoriteButton: lazy(() => import('./FavoriteButton'))
    }

    const elements = document.querySelectorAll('[data-js-component]');

    for (const element of elements) {
        const { jsComponent, jsProps } = element.dataset;

        const ComponentFound = components[jsComponent];
        let props = JSON.parse(jsProps);

        ReactDOM.render(
            <Suspense fallback={<p>...</p>}>
                <ComponentFound {...props} />
            </Suspense>,
            element
        );

    }
})();

export default InvokeComponents;

Enter fullscreen mode Exit fullscreen mode

Now, register your InvokeComponent on _layout cshtml page:

<script src="/dist/Components/InvokeComponents.js" asp-append-version="true"></script>
Enter fullscreen mode Exit fullscreen mode

And finishing, modify your webpack.config like this to support chunk used on lazy.

  output: {
        path: path.resolve(__dirname, 'wwwroot'),
        publicPath: '/',
        chunkFilename: '[hash].[name].js',
        filename: '[name]'
    },

Enter fullscreen mode Exit fullscreen mode

Discussion (1)

Collapse
brunot profile image
Bruno

Instead of using React take a look at Preact + Preact Habitat. It is a really nice combination to achieve what you are trying to do. You will end up with simple widgets you can invoke and have a very small size because of Preact. Later, if you really need, you can easily replace Preact with React and get rid of Preact-habitat.