Search code examples
reactjscarouselreact-bootstrap

Dynamic react-bootstrap carousel breaking apart. (A valid React element (or null) must be returned.)


I will try and explain best I can. I want to create a Carousel dynamically. This is the representation of my data going into making this happen.

Below is a question array. This is represented by a question with (in this case ) four answers to choose from in the question_list_items.

    let question = [{ id: 52, question: "I still enjoy the things I used to", type: "Text list",
        question_list_items: Array(4)
        0: {id: 164, list_item: "Definitely as much", item_value: "0", sort_order: 4, question_id: 52, …}
        1: {id: 165, list_item: "Not quite so much", item_value: "1", sort_order: 3, question_id: 52, …}
        2: {id: 166, list_item: "Only a little", item_value: "2", sort_order: 2, question_id: 52, …}
        3: {id: 167, list_item: "Not at all", item_value: "3", sort_order: 1, question_id: 52, …}
    ];

Main render Here is the start of the Carousel component. I start with the header section and call the <Questionnaire/> component to fill in the body.

return (
    <Carousel
        activeIndex={index}
        direction={direction}
        onSelect={(i,e)=>this.handleSelect(i,e)}
    >

       <Questionnaire/>

    </Carousel>
);

Questionnaire Component
It is here where I think it goes wrong. Here I loop over all the questions inside this.props.questions. I check the Type of the question to render the correct type question. This is done with the help of a switch. In this case Im only showing the <TextList /> component.

const Questionnaire = SortableContainer(({items}) => {
    this.props.questions.map((row, index) => {
        let element;
        switch (row.type) {
            case "Text list":
                element = <TextList key={`questionList-${index}`} index={index} row={row} />;
                break;
        }
        return element;
    })
});

TextList Component

In this component is where I build a single slide of the carousel. This component Calls the <TextListButton /> component to render the answer options for the current question.

const TextList = SortableElement(({row}) => {
    console.log("row",row);
    return (
    // ******************************************************************************************************
    // ************************************      LIST TYPE QUESTION     *************************************
    // ******************************************************************************************************
        <Carousel.Item  style={{"padding": "0 20px 0 20px", height: "600px", width: "1024px", textAlign: "center"}}>
            <h3>{row.question}</h3>
            <div style={{ paddingTop: "40px" }}>
                <Row>
                    <Col xs={3} />
                    <Col xs={6}>
                        <div>
                            <ButtonGroup vertical block>
                                {row.question_list_items.map((row, idx) => {
                                    return <TextListButton key={`buttonList-${idx}`} index={idx} row={row} />;
                                })}
                            </ButtonGroup>
                        </div>
                    </Col>
                    <Col xs={3} />
                </Row>
            </div>

            <Carousel.Caption>
                <p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
            </Carousel.Caption>
        </Carousel.Item>
    );
});

TextListButton Component This creates each answer item to choose from.

const TextListButton = SortableElement(({row}) => {
    console.log("item row",row);
    return (
        <Button>{row.list_item}</Button>
    );
});

NOTE on Questionnaire Component

I have tried to wrap the Questionnaire component in <div> tags. This makes it render, but its not a carousel anymore, its just one long list of questions and answers. Im not sure how to approach this. Any help will be appreciated.

const Questionnaire = SortableContainer(({items}) => {
    return (
        <div>  <=********************************************HERE
        {this.props.questions.map((row, index) => {
            let element;
            switch (row.type) {
                case "Text list":
                    element = <TextList key={`questionList-${index}`} index={index} row={row} />;
                    break;

            }
            return element;
        })}
        </div>  <=********************************************HERE
    );
});

Update on declaring let element = null;

It seems to run for 14 questions, but then fails. ( there is only 14 questions )

enter image description here

STACT TRACE

enter image description here


Solution

  • When you defined let element; its initialized as undefined in Javascript and if your switch dont match the defined case (in your example Text list) then you are returning undefined thus the error.

    On side note since you only have one condition you dont need switch/case you are better of with if statement unless the example in question was trimmed down version.

    Since you are using React 15.x you cannot leave child orphan, thus you need to wrap your array in a div or some other html element: Rendering React Components from Array of Objects

    Your first issue was you were rendering undefined in array which is not allowed, thus you can define element = null

    const Questionnaire = SortableContainer(({items}) => {
        return (
            <div>  <=******************************************** You can remove this if you want or replace it with empty <> fragment
            {this.props.questions.map((row, index) => {
                let element = null; <=********************************************HERE
                switch (row.type) {
                    case "Text list":
                        element = <TextList key={`questionList-${index}`} index={index} row={row} />;
                        break;
    
                }
                return element;
            })}
            </div>
        );
    });
    

    Your second issue is with rendering array list without wrapping in a HTML element. You would have to restructure your code so that its not returning array child elements without a parent container.

    One possible way is you can try to update your Carousel direct as below:

    <Carousel
    activeIndex={index}
    direction={direction}
    onSelect={(i,e)=>this.handleSelect(i,e)}
    >
      {this.props.questions.filter(question => question.type === "Text list")
        .map((row, index) => 
        <Carousel.Item>
          <Questionnaire question={row.question} answers={row.question_list_items} />
        </Carousel.Item>
        )
      }
    </Carousel>
    

    And Questionnaire can act as dumb component which only renders question/answers