highlightjs / highlight.js

JavaScript syntax highlighter with language auto-detection and zero dependencies.
https://highlightjs.org/
BSD 3-Clause "New" or "Revised" License
23.69k stars 3.6k forks source link

Using highlight.js with React.js #925

Closed kaushik94 closed 9 years ago

kaushik94 commented 9 years ago

I am trying to configure highlightjs in a reactjs application to detect and highlight json. But it doesn't work, any workaround ? I need it on the browser side btw.

Sannis commented 9 years ago

To suggest any "workaround" we need to know what was the problem :-) Can you provide any example that does not work?

kaushik94 commented 9 years ago

@Sannis thanks for the quick response, my bad. In my index.html

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

In my react component

var Pretty = React.createClass({
    render: function() {
        return <pre className="custom-json-body"><code className="json">{JSON.stringify(this.props.json, null, 2)}</code></pre>;
    }
});

But it isn't highlighting.

kaushik94 commented 9 years ago

I tried with class and className as well. No luck.

Sannis commented 9 years ago

hljs.initHighlightingOnLoad(); higlights code on page once after page is loaded. If you need highlight dinamically loaded content - this is your responsability. We provide hljs.highlight() method for that, so you can include it to React.js render callback. I also think that React.js should provide some post-render events that you can listen and highlight rendered block there.

kaushik94 commented 9 years ago

hljs.initHighlightingOnLoad(); higlights code on page once after page is loaded.

@Sannis thanks a lot. Its solved now. In fact I called hljs.initHighlightingOnLoad(); when the page has been loaded but I did not wait for that particular component to load.

I also think that React.js should provide some post-render events that you can listen and highlight rendered block there.

React provides this, just for future reference

var Pretty = React.createClass({
    componentDidMount: function(){
        hljs.initHighlightingOnLoad();
        var current = React.findDOMNode(this);
        hljs.highlightBlock(current);
    },
    render: function() {
        return <pre className="custom-json-body"><code className="json">{JSON.stringify(this.props.json, null, 2)}</code></pre>;
    }
});

Let me know if there is a better way to do this. Thanks a lot.

Sannis commented 9 years ago

Great! Unfortunately, I'm not familiar with react to suggest something better. But it looks like you do double work in componentDidMount.

kaushik94 commented 9 years ago

Ohh okay I just realised that for some reason hljs.initHighlightingOnLoad(); doesn't do anything at all. So finally its

componentDidMount: function(){
    var current = React.findDOMNode(this);
    hljs.highlightBlock(current);
 }
isagalaev commented 9 years ago

Ohh okay I just realised that for some reason hljs.initHighlightingOnLoad(); doesn't do anything at all.

What it does is binds itself to the onload event of the browser which by that moment has probably already been fired. highlightBlock() is the main method anyway.

vonkanehoffen commented 7 years ago

For anyone (like me) who finds this issue: https://github.com/akiran/react-highlight :-)

Savinien92 commented 6 years ago
import React from 'react'
import hljs from 'highlight.js'
import javascript from 'highlight.js/lib/languages/javascript'
import '../../node_modules/highlight.js/styles/darcula.css'
hljs.registerLanguage('javascript', javascript)

export class Highlighter extends React.PureComponent {

  componentDidMount(){
    hljs.highlightBlock(this.node)
  }

  render() {
    let { children } = this.props
    return (
      <pre
        ref={(node) => this.node = node}
      >
        <code className="javascript">
          {children}
        </code>
      </pre>
    )
  }
}

export default Highlighter
<Highlighter>
     {'function(){let test = [\'test\', \'test 2\']}'}
</Highlighter>

You can customize this component with various props like language

Farhad33 commented 6 years ago

For anyone (like me) who finds this issue: https://github.com/akiran/react-highlight :-)

this library has a lot of side effects.

CodyFitzpatrick commented 5 years ago

For anyone (like me) who finds this issue: https://github.com/akiran/react-highlight :-)

this library has a lot of side effects.

@Farhad33 Like what? I'm very interested to know, being that I just incorporated this package into my project.

eastrd commented 5 years ago

For anyone (like me) who finds this issue: https://github.com/akiran/react-highlight :-)

this library has a lot of side effects.

@Farhad33 Like what? I'm very interested to know, being that I just incorporated this package into my project.

I have already experienced unstable DOMExceptions with this library and seen these errors posted as issues but unanswered...

twistezo commented 5 years ago

For everyone who didn't find any working answer above and have no success with initHighlightingOnLoad and others builtin functions.

React: 16.8.2 working example:

import hljs from "highlight.js";
import "./dracula.css";

class Preview extends Component {
  componentDidMount() {
    this.updateCodeSyntaxHighlighting();
  }

  componentDidUpdate() {
    this.updateCodeSyntaxHighlighting();
  }

  updateCodeSyntaxHighlighting = () => {
    document.querySelectorAll("pre code").forEach(block => {
      hljs.highlightBlock(block);
    });
  };

  render() {
    return (
    <div
        className="content"
        dangerouslySetInnerHTML={{ __html: this.props.parsedText }}
    />
    );
  }
}

Note that updateCodeSyntaxHighlighting should be in componentDidMount and componentDidUpdate methods in every component which use <pre><code>... tags.

vittoriozamboni commented 5 years ago

I have a slightly different solution that registers only the language you actually need by importing them once.

React 16.8.2:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import hljs from 'highlight.js/lib/highlight';   // import hljs library
import 'highlight.js/styles/hopscotch.css';      // import your preferred style

const registeredLanguages = {};   // keep a record of registered languages

export class CodeHighlight extends Component {
    constructor(props) {
        super(props);                
        // do not show anything until language is loaded
        this.state = { loaded: false };
        // create a ref to highlight only the rendered node and not fetch all the DOM
        this.codeNode = React.createRef();
    }

    componentDidMount() {
        const { language } = this.props;
        if (language && !registeredLanguages[language]) {
            try {
                const newLanguage = require(`highlight.js/lib/languages/${language}`);
                hljs.registerLanguage(language, newLanguage);
                registeredLanguages[language] = true;
                this.setState(
                    () => { return { loaded: true }; },
                    () => { this.highlight(); }
                );                
            } catch (e) {
                console.error(e);
                throw Error(`Cannot register and higlight language ${language}`);
                // We can alternatively set loaded to true and show an error message in the
                // code block instead of children, or just show the children without highlight.
                // This would be an improvement or an optional behavior given a special prop.
            }
        } else {
            this.setState({ loaded: true });
        }
    }

    componentDidUpdate() {
        this.highlight();
    }

    highlight = () => {
        this.codeNode && this.codeNode.current && hljs.highlightBlock(this.codeNode.current);
    }

    render() {
        const { language, children } = this.props;
        const { loaded } = this.state;
        if (!loaded) return ''; // or show a loader

        return <pre>
            <code ref={this.codeNode} className={language}>{children}</code>
        </pre>;
    }
}

CodeHighlight.propTypes = {
    children: PropTypes.node.isRequired,
    language: PropTypes.string,
};
// optionally set the language you think will use most as a default value
// if you don't set this, I would encourage to make language prop required, 
// or at least improve the "else" statement in "componentDidMount"
CodeHighlight.defaultProps = {
    language: 'javascript',
};

Usage:

// Use the default language, if set
<CodeHighlight>
    {JSON.stringify(myObject, null, 4)}
</CodeHighlight>
// Specify a language
<CodeHighlight language="python">
    {JSON.stringify(myObject, null, 4)}
</CodeHighlight>
gusbemacbe commented 4 years ago

Witaj @twistezo and ciao @vittoriozamboni

Do your codes support third-party languages like https://github.com/highlightjs/highlightjs-cypher?

joshgoebel commented 4 years ago

Sure. Just import and register the 3rd party grammar after you import the main library.

gusbemacbe commented 4 years ago

WItaj @twistezo, ciao @vittoriozamboni and hello @yyyc514 !

Thank you, I have just updated the repo's README, adding React instruction.

Sayuki0x commented 3 years ago

After struggling for some time with these solutions, I stumbled across the highlight and highlightAuto methods. They seem designed for generating the highlights server-side, but work very nicely in a functional component.

import React from "react";
import hljs from "highlight.js"; // import hljs library

export function Highlighter(
    content: string,
    language?: string,
): JSX.Element {
    const highlighted = language
        ? hljs.highlight(language, content)
        : hljs.highlightAuto(content);

    return (
        <pre className="hljs">
            <code
                className="hljs"
                dangerouslySetInnerHTML={{ __html: highlighted.value }}
            />
        </pre>
    );
}
joshgoebel commented 3 years ago

Or highlight if the component was aware of the language, etc... those are the two API calls for doing slightly more "manual" integrations, like building a component for a framework.

Sayuki0x commented 3 years ago

Thanks for making me aware of the highlight method, it's much faster. I updated the component.

joshgoebel commented 3 years ago

it's much faster.

It should be, but most shouldn't notice unless you're highlighting a LOT of code.

subwaymatch commented 3 years ago

@ExtraHash Thanks for the brilliant solution! I was looking for a simple hljs component to use with Next.js and your snippet did the trick. 🙏 I removed the className="hljs" attribute on <code> since the <pre> tag already has hljs className.

import React from "react";
import hljs from "highlight.js";

interface HighlighterProps {
  content: string;
  language?: string;
}

export default function Highlighter({
  content,
  language,
}: HighlighterProps): JSX.Element {
  const highlighted = language
    ? hljs.highlight(language, content)
    : hljs.highlightAuto(content);

  return (
    <pre className="hljs">
      <code dangerouslySetInnerHTML={{ __html: highlighted.value }} />
    </pre>
  );
}