Blaze dynamic template loading vs React dynamic component loading

Hey I have a question about dynamic template loading, because I’m stuck. I’m trying to dynamically load React components based on a string reference in the database. I’ve done it with Blaze and its straight forward for me, but it seems less straightforward with React. Here’s some context about what I’m trying to accomplish:

My blaze application uses the Blaze dynamic templates to decide which page is loaded. Each page contains a reference to a template name and that template dictates the logic and layout. Example:

Blaze version

Since blaze templates are globally available its just a matter of referencing their names in the index.html. Below is a stripped down version of my app utilizing this mechanic

Main index.html

<template name="appIndex">
    <div class="container">
        <div class="page">
           {{#if page}}
                {{> UI.dynamic template=page.template data=page.configuration }}
            {{else}}
                <div class="loader"></div>
            {{/if}}
        </div>
    </div>
</template>

Main index.js

Template.appIndex.helpers({
    page() {
        //I know that this is not reactive. Just for illustration purposes
        const segments = Object.values(FlowRouter.current().params).filter((param) => !!param);
        return Pages.findOne({segments});
    }
});

I want to use a similar approach in React. Basically dynamically loading components based on a reference coming from the database. Loading the component itself in the render method is straightforward:

components/page.js

class Page extends Component {

    renderLoader() {
        return (
            <div className="text-center" style={{'margin-top': '30px'}}>
                <i className="fa fa-cog fa-spin fa-3x" />
            </div>
        )
    }

    render() {
        if(this.props.isLoading) {
            return this.renderLoader();
        }
        const PageComponent = this.props.page.component;
        
        return (
            <div style={{'margin-top': '30px'}}>
                <PageComponent page={this.props.page.configuration} />
            </div>
        )
    }
}

But since the components themselves are not globally defined as opposed to Blaze templates, how would I dynamically load those components even when they are coming from a different package. I’ve tried dynamic imports, but this seems to be a bit too much work (storing 3 values: package, component name and file reference). Another way would be to expose a Components object like Meteor does with the Meteor object to expose methods like Meteor.publish.

Can you guys share me some of the options that I have and why I would or would not go for the above 2 options?

Thanks!

you should import all possible react components somewhere:

/path/to/page/templates/index.js 

import BaseTemplate from './base.jsx';
import AboutTemplate from './about.jsx';
import SpecialTemplate from './special.jsx';

export {BaseTemplate, AboutTemplate, SpecialTemplate};


// page.js

import * as PageTemplates from '/path/to/page/templates/'

// ...

 const PageComponent = PageTemplates[this.props.page.component];

1 Like

I have thought about that. The whole point is that it should be possible for 3th party developers to add their own page component without them having to update the package.

I guess that it might be better to enable a app option to include all page components into the app on the entry point. Something like this:

import DefaultPage from './DefaultPage .jsx';
import BlogPage from './BlogPage .jsx';
import GridPage from './GridPage .jsx';

const pageComponents = {DefaultPage, BlogPage, GridPage};

Meteor.startup(() => {
    render(<App pageComponents={pageComponents} />, document.getElementById('render-target'));
});

What do you think?

I guess you gave me a step into the right direction. My main.js now looks like this:

main.js

import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';

import App from '../imports/ui/app.jsx';

import SplashPage from '../imports/ui/pages/SplashPage.jsx';
import GridPage from '../imports/ui/pages/GridPage.jsx';

const pageComponents = {GridPage, SplashPage};

Meteor.startup(() => {
    render(<App pageComponents={pageComponents} />, document.getElementById('render-target'));
});

I’m simply pushing this component object trough my Router to load the component whenever its referenced:

app.js

import PageContainer from './components/PageContainer';

export default class App extends Component {
    renderPageContainer() {
        return ({ match }) => (
            <PageContainer match={match} pageComponents={this.props.pageComponents}/>
        )
    }

    render() {
        const path = "/blog/:slug?";
        return (
            <Router>
                <Container>
                    <Route path={path} component={this.renderPageContainer()}/>
                </Container>
            </Router>
        );
    }
}

Now each page component can be replaced or added from outside :slight_smile: exactly what I wanted. Thanks!