0
votes

I have a React component which is trying to render another component that has function inside it as a child. When I try to render that component it returns an [Object]. I am trying to find another way to render that child component.

Right now, I tried to render it with React.createElement(), yet it also returned an object. I am using react-beautiful-dnd library to use Drag and Drop feature. This library has Droppable component and it takes a function inside which has two parameters, provided, snapshot.

Since it takes a function, when I try to render Droppable component, it returns an object instead of react element.

DroppableContent.js

const DroppableContent = ({ droppedBlank, label, id }) => (
  <Droppable droppableId={id || _.uniqueId('droppable_')}>
    {(provided, snapshot) => (
      <span ref={provided.innerRef} style={{ display: 'inline' }} {...provided.droppableProps}>
        {/* blank placeholder */}
        <span className={droppedBlank ? styles.dropped : styles.placeholder}
          style={{ backgroundColor: !droppedBlank && snapshot.isDraggingOver ? '#88d2ee' : '#fff' }}
        >
          {droppedBlank ? <BlankItem label={label} index={id} /> : null}
        </span>
      </span>
    )}
  </Droppable>
);

DragAndDrop.js where I call QuestionPreview component.

import React from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';

import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import _ from 'lodash';
import * as questionStyles from '../Questions.less';
import BlankList from './BlankList';
import BlankItem from './BlankItem';
import QuestionPreview from './QuestionPreview';

const DragAndDrop = ({
  question, onAnswer, answer, hideTitle, className, t, readOnly,
}) => {
  const handleDragEnd = (result) => {
    const { destination, source, draggableId } = result;
    if (!destination) {
      return;
    }

    if (destination.droppableId === source.droppableId && destination.index === source.index) {
      return;
    }

    const destinationId = destination.droppableId;
    const sourceId = draggableId;
    const blank = {
      textIndex: destinationId,
      id: sourceId,
      // answer: _.find(questionBlanks, b => b.id === sourceId).answer,
    };

    let updatedBlanks;
    if (destinationId === 'answerBlanks') {
      updatedBlanks = _.filter(answer.blanks, item => item.id !== blank.id);
    } else {
      updatedBlanks = _.filter(answer.blanks, item => item.textIndex !== blank.textIndex);
      updatedBlanks.push(blank);
    }

    onAnswer(question, { blanks: updatedBlanks });
  };

  const blankLabels = currentLabels => _.filter(currentLabels, l => !_.includes(_.map(answer.blanks, ab => ab.id), l.id)).map((label, index) => (
    <BlankItem key={label.id} label={label} index={index} />
  ));

  const blankItems = currentBlanks => _.map(currentBlanks, (currentBlank, index) => (
    <BlankItem key={currentBlank.id} label={currentBlank} index={index} readOnly />
  ));

  const { text } = question;
  const shuffledLabels = question.labels && _.shuffle(question.labels);
  // filtering answers from blank items so that whenever we drag an item to a blank,
  // answer will be removed.
  const filteredLabels = shuffledLabels && blankLabels(shuffledLabels);
  const filteredBlanks = blankItems(question.blanks);

  return (
    <DragDropContext onDragEnd={handleDragEnd}>
      <div>
        <p style={{ fontWeight: 600 }}>
          {t('defaultDndText', { numberOfBlanks: question.blanks.length })}
        </p>
        <Droppable droppableId="answerBlanks">
          {provided => (
            <div>
              <BlankList innerRef={provided.innerRef} {...provided.droppableProps}>
                {readOnly ? filteredBlanks : filteredLabels}
              </BlankList>
            </div>
          )}
        </Droppable>

        {!hideTitle && (
          <QuestionPreview blanks={_.filter(question.blanks, blank => blank.textIndex < 100)}
            labels={question.labels}
            selectedBlanks={answer.blanks}
            text={text}
            className={[questionStyles.title, className].join(' ')}
          />
        )}
      </div>
    </DragDropContext>
  );
};

DragAndDrop.propTypes = {
  question: PropTypes.shape({
    text: PropTypes.string,
  }).isRequired,
  answer: PropTypes.shape({
    blanks: PropTypes.arrayOf(PropTypes.shape({})),
  }),
  readOnly: PropTypes.bool,
  disabled: PropTypes.bool,
  hideTitle: PropTypes.bool,
  onAnswer: PropTypes.func,
  className: PropTypes.string,
};

DragAndDrop.defaultProps = {
  onAnswer: () => {},
  disabled: false,
  hideTitle: false,
  className: '',
  answer: { blanks: [] },
  readOnly: false,
};

export default withTranslation('question')(DragAndDrop);

QuestionPreview.js where I try to render DroppableContent component.

const QuestionPreview = ({
  text, labels, selectedBlanks, readOnly,
}) => {
  const readOnlyContent = (id) => {
    const droppedBlank = selectedBlanks && _.find(selectedBlanks, blank => blank.textIndex === id);
    const label = droppedBlank && _.find(labels, l => l.id === droppedBlank.id);
    return (
      <span className={droppedBlank ? styles.dropped : styles.placeholder}>
        {droppedBlank && <BlankItem label={label} readOnly />}
      </span>
    );
  };

  const splittedText = splitTextWithBlanks(text);
  const blankIndices = getBlankIndices(text);
  const getContentId = index => blankIndices[index];

  const tempArray = [];

  const html = () => {
    _.map(splittedText, (element, index) => {
      const contentId = getContentId(index);
      const droppedBlank = selectedBlanks && _.find(selectedBlanks, blank => blank.textIndex === contentId);
      const label = droppedBlank && _.find(labels, l => l.id === droppedBlank.id);
      const blankContent = readOnly ? readOnlyContent(contentId) : <DroppableContent id={contentId} droppedBlank={droppedBlank} label={label} />;
      const htmlContent = <span dangerouslySetInnerHTML={{ __html: toHTML(element) }} />;
      tempArray.push(htmlContent);
      if (index !== splittedText.length - 1) {
        tempArray[index] = tempArray[index] + blankContent;
      }
    });
    return tempArray;
  };

  const createdElement = React.createElement('div', null, html());
  return createdElement;
};

This does not return any error but what I want to achieve is that combining htmlContent variable with blankContent. When I do that, it does render blankContent as an Object. In the end, I just want to find a way to parse Droppable component.

1
You have syntex error here {droppedBlank ? <BlankItem label={label} index={id} /> : <span />} place null instead of <span/> - Afraz Ahmad
Yeah, it used to be null actually. Then I was trying something and changed it. Not a syntax error but yeah normally null would be better. Thanks for info :) - rawsly
Can you share you code where you are calling QuestionPreview ? I might know reason But want to confirm - Afraz Ahmad
Added DragAndDrop component where I call QuestionPreview - rawsly

1 Answers

0
votes

You might have error in the following line

 const blankContent = readOnly ? readOnlyContent : <DroppableContent id={contentId} droppedBlank={droppedBlank} label={label} />;

You are passing reference of readOnlyContent, May be you want to call

readOnlyContent (contentId) .BTW your code is complex and hard to maintain/read. try to refactor it

Edit 1 Try this QuestionPreview.js

  const QuestionPreview = ({
    text, labels, selectedBlanks, readOnly,
        }) => {
    const readOnlyContent = (id) => {
    const droppedBlank = selectedBlanks && _.find(selectedBlanks, blank => 
                                       blank.textIndex === id);
    const label = droppedBlank && _.find(labels, l => l.id === 
                                       droppedBlank.id);
    return (
      <span className={droppedBlank ? styles.dropped : styles.placeholder}>
       {droppedBlank && <BlankItem label={label} readOnly />}
     </span>
    );
 };

  const splittedText = splitTextWithBlanks(text);
  const blankIndices = getBlankIndices(text);
  const getContentId = index => blankIndices[index];

  const tempArray = [];

  const html = () => {
   return _.map(splittedText, (element, index) => {
     const contentId = getContentId(index);
     const droppedBlank = selectedBlanks && _.find(selectedBlanks, blank => 
                                          blank.textIndex === contentId);
     const label = droppedBlank && _.find(labels, l => l.id === 
                                                        droppedBlank.id);
      const blankContent = readOnly ? readOnlyContent(contentId) : 
      <DroppableContent id={contentId} droppedBlank={droppedBlank} label= 
      {label} 
      />;
       let htmlContent = <span dangerouslySetInnerHTML={{ __html: 
                                                 toHTML(element) }} />;
    if (index !== splittedText.length - 1) {
        return (
         <Fragment>
           {htmlContent}
           {blankContent}
         </Fragment>
        ) 
     }

    return htmlContent
   });

  };

 return (
   {html()}
 )
};