0

I'm working on an application that presents a list of tasks. When a user clicks on a task, it should present a form so that the user can update the status of that task. There are different types of tasks, however, and the correct form needs to be presented based on the task type. Each task type has a task definition with a field that identifies the form required for that task. When someone clicks on a task, the task object, including the task formComponent is passed to the formWrapper component. Each form component is in a JSX file and they are all exported as TaskForm.

I need to select the component based on the task definition. I thought I could accomplish this with react.Lazy by doing the following:

In the formWrapper's componentDidMount():

    // Store the path to the required JSX file in the state.
    this.setState( {formFile: '../Processes/'+this.state.task.formComponent} );

Then in formWrapper's render():

    TaskForm = React.lazy(() => import(this.state.formFile));
    return (
        <Suspense fallback={<div>Loading...</div>}>
             <TaskForm task={this.props.task} />
        </Suspense>

When I launch node.js, however it throws the warning "Critical dependency: the request of a dependency is an expression" and the form fails to load.

Am I even close to being on the right track? I can't statically define the forms. There are several of them and they are subject to change.

3 Answers 3

1

The code has to be statically analyzable because Webpack (which I believe is what you're using to bundle your app) needs to know what files are required so it can include them in the bundle. It isn't smart enough to take an expression like this.state.formFile.

I'm confident though that you're incorrect when you say you can't statically define the forms, or at least that it would be intolerable to do so. The files are enumerable, and all can be imported into this file.

Sign up to request clarification or add additional context in comments.

3 Comments

You are correct, we are using Webpack. The forms are linked to processes, and when the processes change, the forms will change with them. We can statically define the forms (the JSX files), but perhaps a more accurate way of stating it is that we don't want to statically reference the forms. We want the data to determine which form (Component) is referenced. It doesn't have to be a file path, it could be Component name.
Sure, and you can still create an object that maps a string to a lazy-loaded component. Then in the formWrapper's render function you can do const TaskForm = forms[this.state.formName].
I will have to post my solution later, to close the question, but thank you for the push in the right direction.
0

I suppose you want to dynamically import a component based on some condition. I have created a small function to do so:

const getComponent = (path) => {
  const Component = React.lazy(() => import(`${path}`));
  return Component;
};

// Usage

render() {
 const Component = getComponent(path);
 return <Component />;
}

Here is the working Sandbox: https://codesandbox.io/s/intelligent-snowflake-c0l6y?file=/src/App.js:306-344

Here is the live deployed app: https://csb-c0l6y.netlify.app/

You can confirm with network request that when we Load Component B a new fileis requested.

Note: I have not battle-tested this pattern. I think it may have some performance implication but should be easy to fix.

Comments

0

My eventual solution, after the exchange with @backtick, was to place all the form JSX files in a folder with an index.js file that exported each of the forms in the folder (there is no other code in the file, just exports):

export { Form1 } from './Form1';
export { Form2 } from './Form2';

Then, in the document that needs to load one of the forms, I read the form name from the database and use it to lazy load the form in my render() function:

        const MyForm = React.lazy(() => import('../forms')
            .then(forms => (
                { default: forms[this.state.formFile] }
            ))
        );

The .then function exports the selected form as default so that only one component is stored in MyForm. If this.state.formFile = Form1, it loads Form1.

In the return() of my form, I include MyForm as a component:

                    <Suspense fallback={<div>Loading...</div>}>
                        <MyForm task={this.props.task} formVariables={this.state.formVariables}/>
                    </Suspense>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.