react-truncate-markup: Error: Maximum update depth exceeded

I can’t ever seem to get past this error:

screen shot 2018-09-13 at 6 49 53 pm

Currently the library seems unusable =/ Not sure how to guard against it within the componentDidUpdate lifecycle hook.

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 4
  • Comments: 22 (9 by maintainers)

Most upvoted comments

hi @patrik-piskay ,

Here is the test data I used to replicate the issue:

Screen Shot 2019-04-19 at 16 58 04

            <TruncateMarkup lines={3}>
                <div style={{width: '150px'}}>
                  <div style={{display: 'inline'}}>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}>
                    <img src='someimage' alt=''></img>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}>
                    <img src='someimage' alt=''></img>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}> 
                    <img src='someimage' alt=''></img>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}> 
                    <img src='someimage' alt=''></img>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}> 
                    <img src='someimage' alt=''></img>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}> 
                    <img src='someimage' alt=''></img>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}> 
                    <img src='https://material.io/tools/icons/static/icons/baseline-donut_large-24px.svg' alt=''></img>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}> 
                    <img src='https://material.io/tools/icons/static/icons/baseline-donut_large-24px.svg' alt=''></img>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}> 
                    <img src='someimage' alt=''></img>
                    <span>hello</span>
                  </div>
                  <div style={{display: 'inline'}}> 
                    <img src='someimage' alt=''></img>
                    <span>hello</span>
                  </div>
                </div>
              </TruncateMarkup>
            </div>

The issue with refs is that it returns actual DOM nodes instead of React elements. So instead of

{
  type: 'div',
  props: {
    children: [
      'Hello',
      {
        type: 'span',
        props: {
          children: [
            'world',
            '!'
          ]
        }
      }
    ]
  }
}

we get

 <div>Hello <span>world!</span></div>

That means we’d need to transform the returned DOM nodes back into React elements again (or any tree structure so we can truncate it and have it rendered again), which might be doable, but again not reliable. For example, I don’t think events (click etc.) would work. Also, we’d lose access to any React components after the initial render (because we’d continue working with the initially returned DOM elements), so any effects happening in their lifecycle methods would be lost.

It’s probably best to keep it simple for now and use DOM elements only. For the future, who knows, maybe with Hooks there will be something we can do to support React components, I’ll keep my eyes on it.

So here is what I found out…

Issue: react-truncate-markup doesn’t support React components as its children. Not just HOC/render props as mentioned in https://github.com/parsable/react-truncate-markup/issues/12#issuecomment-430308275, but all components.

Solution: tl;dr: there is none that I am aware of that works for every case, so in dev mode we’ll warn if React components are used as <TruncateMarkup /> children and skip any truncation, displaying the whole content instead. <TruncateMarkup /> will work fine when using inlined DOM elements only.


I’ve spent a few hours trying to get React components (both classes and functions) to work. The reason it’s not trivial is because of the way the truncate functionality works here. General approach is to take the whole element tree structure (props.children of the parent component) and work with that. This tree might look like this:

<TruncateMarkup>
  <div>
    Hello <span>world{'!'}</span>
  </div>
</TruncateMarkup>

↓↓↓

{
  type: 'div',
  props: {
    children: [
      'Hello',
      {
        type: 'span',
        props: {
          children: [
            'world',
            '!'
          ]
        }
      }
    ]
  }
}

which is easy to work with as we have the whole final “render” tree available, which means we can potentially end up with this result (after truncation):

<div>
  Hello <span>wo</span>...
</div>

But if we were to move a part of the tree to a separate component, it could look like this:

const Person = ({ person }) => (
  <span>{person || 'world'} </span>
);

<TruncateMarkup>
  <div>
    Hello <Person />!
  </div>
</TruncateMarkup>

↓↓↓

{
  type: 'div',
  props: {
    children: [
      'Hello',
      {
        type: 'function Person()',
        props: {}
      },
      '!'
    ]
  }
}

which hides what’s getting rendered in <Person /> so we cannot use that in the current truncation algorithm. We could truncate only after Hello or after Hello <span>{person}</span>, but we’d never be able to truncate anything inside the <span>{person}</span> because it’s not initially visible to us, only after React renders it into the DOM.

One idea I had to solve this was to “unwrap” the whole tree before starting any truncation work by “evaluating” these nested components:

if (typeof node.type === 'function') {
  if (// class component) {
    const instance = new node.type(node.props);
    return instance.render();
  } else {
    // function component
    return node.type(node.props);
  }
}

This actually worked and I was able to get nested components to work, until…it didn’t.

Some components I used for testing used context, which this approach didn’t support. Even before trying this approach I felt it’s probably not the best idea to start emulating React itself by rendering the tree on our own, and when I experienced the context issues, I decided it’s (probably) nothing we can solve reliably, so the best thing we could do is to officially not support it - we’ll warn if React components are detected in the <TruncateMarkup /> children and will skip any truncation work, rendering the whole content instead.

<TruncateMarkup /> will continue to work fine when using inlined DOM elements as its children like in the original case:

<TruncateMarkup>
  <div>
    Hello <span>world{'!'}</span>
  </div>
</TruncateMarkup>