Recursive Mithril View function - not rendering further

118 Views Asked by At

Here is a DisplayComment function. I want to make a comment thread. Basically a reply structure kind of thing. Each parent comment is displayed in the table and I want to render its replies just below it using the same function and so on for their replies. Screenshot of Display of parent comment

function DisplayComment(commentStruct, identity) {
  const comment = commentStruct.comment;
  return {
    oninit: (v) => {
      console.log(comment.mComment);
    },
    view: (v) => [
      m('tr', [
        m('i.fas.fa-angle-right', {
          class: 'fa-rotate-' + (commentStruct.showReplies ? '90' : '0'),
          style: 'margin-top:12px',
          onclick: () => {
            commentStruct.showReplies = !commentStruct.showReplies;
            // console.log(commentStruct.showReplies);
          },
        }),

        m('td', comment.mComment),
        m(
          'select[id=options]',
          {
            onchange: (e) => {
              if (e.target.selectedIndex === 1) {
                // reply
                util.popupMessage(
                  m(AddComment, {
                    parent_comment: comment.mComment,
                    channelId: comment.mMeta.mGroupId,
                    authorId: identity,
                    threadId: comment.mMeta.mThreadId,
                    parentId: comment.mMeta.mMsgId,
                  })
                );
              } else if (e.target.selectedIndex === 2) {
                // voteUP
                AddVote(
                  util.GXS_VOTE_UP,
                  comment.mMeta.mGroupId,
                  comment.mMeta.mThreadId,
                  identity,
                  comment.mMeta.mMsgId
                );
              } else if (e.target.selectedIndex === 3) {
                AddVote(
                  util.GXS_VOTE_DOWN,
                  comment.mMeta.mGroupId,
                  comment.mMeta.mThreadId,
                  identity,
                  comment.mMeta.mMsgId
                );
              }
            },
          },
          [
            m('option[hidden][selected]', 'Options'),
            util.optionSelect.opts.map((option) =>
              m('option', { value: option }, option.toLocaleString())
            ),
          ]
        ),
        m('td', rs.userList.userMap[comment.mMeta.mAuthorId]),
        m(
          'td',
          typeof comment.mMeta.mPublishTs === 'object'
            ? new Date(comment.mMeta.mPublishTs.xint64 * 1000).toLocaleString()
            : 'undefined'
        ),
        m('td', comment.mScore),
        m('td', comment.mUpVotes),
        m('td', comment.mDownVotes),
      ]),
      commentStruct.showReplies
        ? Data.ParentCommentMap[comment.mMeta.mMsgId]
          ? Data.ParentCommentMap[comment.mMeta.mMsgId].forEach(
              (value) =>
                Data.Comments[value.mMeta.mThreadId] &&
                Data.Comments[value.mMeta.mThreadId][value.mMeta.mMsgId]
                  ?
                      m(DisplayComment(
                        Data.Comments[value.mMeta.mThreadId][value.mMeta.mMsgId],
                        identity
                      ))
                  : ''
            )
          : ''
        : '',
    ],
  };
}

I want to know why the recursive call to DisplayComment is not being rendered on the screen in the table below the parent.

2

There are 2 best solutions below

0
Sukhamjot Singh On BEST ANSWER

forEach() does not return anything. Used a map() function instead of forEach() and the replies were displayed. Huge credits to @Barney

3
Barney On

The answer may not be straightforward – I recommend visiting the Mithril chat room if this answer doesn't solve your problem, where I will be happy to assist in detail.

My first guess is that the logical condition at the bottom of your code may not be resolving. We can simplify this code as follows, replacing ternary operations with simpler 'and' (Mithril treats false as an empty node)

         commentStruct.showReplies
      && Data.ParentCommentMap[comment.mMeta.mMsgId]
      && Data.ParentCommentMap[comment.mMeta.mMsgId].forEach(
          (value) =>
               Data.Comments[value.mMeta.mThreadId]
            && Data.Comments[value.mMeta.mThreadId][value.mMeta.mMsgId]
            && m(DisplayComment({
                 commentStruct: Data.Comments[value.mMeta.mThreadId][value.mMeta.mMsgId],
                 identity,
               }))

I have also rewritten the calling signature: commentStruct & identity are now named attributes instead of sequential children (that method should only be used for passing in plain undifferentiated vnodes). This change needs to be persisted to the component functions signature; it is also advisable to query these from the lifecycle function they are invoked in – the view, instead of the closure. It may not matter for this particular component but attributes can change over time, and this method ensures you are always referencing the latest values & not the initial values.

function DisplayComment() {
  return {
    oninit: ({attrs: {commentStruct: {comment}}}) => {
      console.log(comment.mComment);
    },

    view: ({attrs: {commentStruct, identity}}) => {
      const {comment} = commentStruct;

      return [
        m('tr', [
          m('i.fas.fa-angle-right', {
            class: 'fa-rotate-' + (commentStruct.showReplies ? '90' : '0'),
            style: 'margin-top:12px',
            onclick: () => {
              commentStruct.showReplies = !commentStruct.showReplies;
              // console.log(commentStruct.showReplies);
            },
          }),

          m('td', comment.mComment),
          
          m(
            'select[id=options]',
            {
              onchange: (e) => {
                if (e.target.selectedIndex === 1) {
                  // reply
                  util.popupMessage(
                    m(AddComment, {
                      parent_comment: comment.mComment,
                      channelId: comment.mMeta.mGroupId,
                      authorId: identity,
                      threadId: comment.mMeta.mThreadId,
                      parentId: comment.mMeta.mMsgId,
                    })
                  );
                } else if (e.target.selectedIndex === 2) {
                  // voteUP
                  AddVote(
                    util.GXS_VOTE_UP,
                    comment.mMeta.mGroupId,
                    comment.mMeta.mThreadId,
                    identity,
                    comment.mMeta.mMsgId
                  );
                } else if (e.target.selectedIndex === 3) {
                  AddVote(
                    util.GXS_VOTE_DOWN,
                    comment.mMeta.mGroupId,
                    comment.mMeta.mThreadId,
                    identity,
                    comment.mMeta.mMsgId
                  );
                }
              },
            },
            [
              m('option[hidden][selected]', 'Options'),
              util.optionSelect.opts.map((option) =>
                m('option', { value: option }, option.toLocaleString())
              ),
            ]
          ),
          m('td', rs.userList.userMap[comment.mMeta.mAuthorId]),
          m(
            'td',
            typeof comment.mMeta.mPublishTs === 'object'
              ? new Date(comment.mMeta.mPublishTs.xint64 * 1000).toLocaleString()
              : 'undefined'
          ),
          m('td', comment.mScore),
          m('td', comment.mUpVotes),
          m('td', comment.mDownVotes),
        ]),
          commentStruct.showReplies
        && Data.ParentCommentMap[comment.mMeta.mMsgId]
        && Data.ParentCommentMap[comment.mMeta.mMsgId].forEach(
            (value) =>
                Data.Comments[value.mMeta.mThreadId]
              && Data.Comments[value.mMeta.mThreadId][value.mMeta.mMsgId]
              && m(DisplayComment({
                  commentStruct: Data.Comments[value.mMeta.mThreadId][value.mMeta.mMsgId],
                  identity
                }))
          )
      ]
    },
  };
}