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

Latest comments (2)

Collapse
 
thevarunraja profile image
Varun Raja

Very cool approach. Thanks for sharing.

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.