0

I have made for me a Tutorial-Project where I collect various React-Examples from easy to difficult. There is a "switch/case" conditional rendering in App.js, where I - depending on the ListBox ItemIndex - load and execute the selected Component.

I am trying to optimize my React code by removing the "switch/case" function and replacing it with a two dimensional array, where the 1st column contains the Component-Name 2nd column the Object. Further I would like to lazy-load the selected components.

Everything seems to work fine, I can also catch the mouse events and also the re-rendering begins but the screen becomes white... no component rendering.

App.js

import SampleList, { sampleArray } from './SampleList';

class App extends React.Component {
    constructor(props) {
      super(props);
      this.selectedIndex = -1;  
    }
    renderSample(index) {
      if((index >= 0) && (index < sampleArray.length)) {
        return React.createElement(sampleArray[index][1])
      } else {
        return <h3>Select a Sample</h3>;
      }
    }
    render() {
      return (
        <header>
          <h1>React Tutorial</h1>
          <SampleList myClickEvent={ this.ClickEvent.bind(this) }/>
          <p />
          <div>
            <Suspense> /**** HERE WAS MY ISSUE ****/
              { this.renderSample(this.selectedIndex) }
            </Suspense>
          </div>
        </header>
      );
    }
    ClickEvent(index) {
      this.selectedIndex = index;
      this.forceUpdate();
    }
}

SampleList.js

import React from 'react';

const SimpleComponent = React.lazy(() => import('./lessons/SimpleComponent'));
const IntervalTimerFunction = React.lazy(() =>  import('./lessons/IntervalTimerFunction'));

const sampleArray = [ 
    ["Simple Component", SimpleComponent], 
    ["Interval Timer Function", IntervalTimerFunction]
];

class SampleList extends React.Component {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
        this.selectOptions = sampleArray.map((Sample, Index) => 
            <option>{ Sample[0] }</option>
        );
      }
    render() {
        return (
            <select ref={this.myRef} Size="8" onClick={this.selectEvent.bind(this)}>  
                 { this.selectOptions }
            </select>  
        );
    }
    selectEvent() {
        this.props.myClickEvent(this.myRef.current.selectedIndex);
    }
}

export default SampleList;
export { sampleArray };
2
  • The above code has a few too many issues to troubleshoot by sight alone. Please upload a working version of your app to the sandbox of your choice, reproducing the undesired behavior, so we can help! Commented Apr 9, 2022 at 11:19
  • Thank you. But Cesare Polonara's answer already pointed me to the issue. I had the "Suspense" tags set but I did underestimate the importancy of the "fallback" property for that tag. I have added my initial fauly code for App.js and marked the position where my issue was. Everything else was fine. Commented Apr 9, 2022 at 17:46

1 Answer 1

3

You have several issues in that code:

  • If you use React.lazy to import components dynamically, use Suspense to show a fallback;
  • The select can listen to the change event, and receive the value of the selected option, that is convenient to pass the index in your case;
  • Changing a ref with a new index doesn't trigger a re-render of your components tree, you need to perform a setState with the selected index;
  • I suggest you to switch to hooks, to have some code optimizations;
  • Code:

    import React, { Suspense, useState, useMemo } from 'react';
    const SimpleComponent = React.lazy(() => import('./lessons/SimpleComponent'));
    const IntervalTimerFunction = React.lazy(() => 
          import('./lessons/IntervalTimerFunction'));
    
    const sampleArray = [
      ['Simple Component', SimpleComponent],
      ['Interval Timer Function', IntervalTimerFunction],
    ];
    
    export default function App() {
      const [idx, setIdx] = useState(0);
      const SelectedSample = useMemo(() => sampleArray[idx][1], [idx]);
      const handleSelect = (idx) => setIdx(idx);
    
      return (
        <Suspense fallback={() => <>Loading...</>}>
          <SampleList handleSelect={handleSelect} />
          <SelectedSample />
        </Suspense>
      );
    }
    
    class SampleList extends React.Component {
      constructor(props) {
        super(props);
      }
      selectEvent(e) {
        this.props.handleSelect(e.target.value);
      }
      render() {
        return (
          <select ref={this.myRef} Size="8" onChange={this.selectEvent.bind(this)}>
            {sampleArray.map((sample, idx) => (
              <option value={idx}>{sample[0]}</option>
            ))}
          </select>
        );
      }
    }

Working example HERE

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

2 Comments

Much appreciated brother. Works fine now. Sorry for confusion. I had only two lines of code of App.js. Now I have updated with full "App.js" with my faulty code. "Suspense" was used but I underestimated the "fallback" property of it. Shame on me! :) The rest worked fine as it is. But thank you for your detailed answer. And Yes. I forgot that changing values without binding it to a state is not triggering a re-render. Thanks for reminding me. I started 2 weeks ago with Javascript and React. Read trough many stuff but missed the importancy for "fallback"-property for Suspense. Got it now.
Ok, glad it helped, try to be more precise the next time by pasting all relevant code. Anyway you should listen for change event on select elements and not click, try that. Please edit the title of your question to include Suspense and Lazy words, since that was the nature of your issue, and might help other people that land here.

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.