codebased
codebased

Reputation: 7073

convertFromHTML returns a null response | draftjs

I am sure I am doing something wrong. However, a simply empty html tag is creating a problem with convertFromHTML call.

convertFromHTML("<p>  </p>"); // returns null

Where as:

convertFromHTML("<p>empty</p>"); // returns a valid object.

Any thought son why it might be happening?

Upvotes: 0

Views: 1581

Answers (2)

Abhishek
Abhishek

Reputation: 1

I faced the similar problem and solved it using draft-js v0.10.4. Hope this will work for you as well :)

Upvotes: 0

vapurrmaid
vapurrmaid

Reputation: 2307

convertFromHTML("<p> </p>"); // returns null

I'll assume "returns null" is meant literally in which case updating to the most recent draft-js version should fix the problem.

Here's my example running

  • create-react-app v1.5.2
  • draft-js v0.10.5

console.log(convertFromHTML('<p> </p>'))

console log of convertFromHTML

There is a "valid" object, but the contentBlocks property is null, which means we cannot create a valid ContentState as you'll get the following:

TypeError: Cannot read property 'contentBlocks' of null

Any thoughts on why it might be happening?

Short Answer: The HTML node needs text.

In-Depth Answer:

All of the following code is adapted from the most recent version of draft-js source at the time of this writing (commit 4c4465f). Some sections of methods are left out for brevity.

The method convertFromHTMLtoContentBlocks is what's called by this module and it has a few main steps:

  1. process the html string
  2. create a DOM from the string
  3. produce blocks from the DOM
  4. Mapping the blocks to the contentBlocks object

1) Processing the string to a DOM The html gets processed by a method getChunkForHTML the output from which the contentBlocks is created.

const convertFromHTMLtoContentBlocks = (
  html: string,
  DOMBuilder: Function = getSafeBodyFromHTML,
  blockRenderMap?: DraftBlockRenderMap = DefaultDraftBlockRenderMap,
): ?{contentBlocks: ?Array<BlockNodeRecord>, entityMap: EntityMap} => {

  const chunkData = getChunkForHTML( // processing our html string into chunkData
    html,
    DOMBuilder,
    blockRenderMap,
    DraftEntity,
  );

  // use the chunkData to create contentBlocks
  const {chunk, entityMap} = chunkData;
  const contentBlocks = convertChunkToContentBlocks(chunk);

  return {
    contentBlocks,
    entityMap,
  };
};

Inspecting getChunkForHTML, we see that the white-space is trimmed to <p></p> and passed to DOMBuilder. The DOM created by DOMBuilder is converted into blocks by genFragment, prettied and returned as chunk.

const getChunkForHTML = (
  html: string,
  DOMBuilder: Function,
  blockRenderMap: DraftBlockRenderMap,
  entityMap: EntityMap,
): ?{chunk: Chunk, entityMap: EntityMap} => {
  html = html  // the string is trimmed
    .trim()
    .replace(REGEX_CR, '')
    .replace(REGEX_NBSP, SPACE)
    .replace(REGEX_CARRIAGE, '')
    .replace(REGEX_ZWS, '');

  const safeBody = DOMBuilder(html); // create DOM from trimmed html
  if (!safeBody) {
    return null;
  }

  const fragment = genFragment( 
    entityMap,
    safeBody,   // use DOM to create blocks in genFragment
    OrderedSet(),
    'ul',
    null,
    workingBlocks,
    -1,
    blockRenderMap,
  );


  let chunk = fragment.chunk;
  const newEntityMap = fragment.entityMap;

  // rest of method
  return {chunk, entityMap: newEntityMap};
};

2) Create DOM from string If we inspect the DOMBuilder method (alias for getSafeBodyFromHTML) (source) in the debugger, we see that we get a DOM node with these properties:

innerHTML: "<p></p>"
innerText:""

3) Generating Blocks from the DOM The output of DOMBuilder is an argument to genFragment as safeBody. This method processes the DOM tree into blocks. Given that our DOM contains no text, the object returned from genFragment has the property: text: "".

genFragment chunk

4)Mapping the blocks to contentBlocks

The last call in the convertFromHTMLtoContentBlocks is

const contentBlocks = convertChunkToContentBlocks(chunk);:

const convertChunkToContentBlocks = (chunk: Chunk): ?Array<BlockNodeRecord> => {
  if (!chunk || !chunk.text || !Array.isArray(chunk.blocks)) {
    return null;
  }
  // rest of method
}

clearly, at this point chunk.text returns false and thus contentBlocks is null.

Upvotes: 7

Related Questions