Mag
Mag

Reputation: 63

Javascript/react - highlight matching parenthesis

today I faced a problem with highlighting matching parenthesis in React. This is example text:

(my text (is here and) I want (to highlight (something here)))

And I want it to look like in code editor, I mean: image attached

I tried to use react-process-string:

processString([
      {
        regex: /\(|\)/g,
        fn: (key, result) => this.colorBracket(key, result[0])
      }
])(value);

colorBracket = (key, result) => {
    const { usedColors } = this.state;

    const bracketColors = ['red', 'green', 'yellow', 'blue', 'purple'];

    let newColor = '';
    if (result === '(') {
      newColor =
        usedColors.length
          ? bracketColors[usedColors.length]
          : bracketColors[0];

      if (!usedColors.includes(newColor)) {
        this.setState({ usedColors: [...usedColors, newColor] });
      }
    } else {
      newColor = usedColors.length
        ? usedColors[usedColors.length - 1]
        : bracketColors[0];

      if (usedColors.length) {
        this.setState({ usedColors: usedColors.filter(e => e !== newColor) });
      }
    }

    return <span style={{ color: newColor }}>{result}</span>;
  };

but I faced problem with react maximum update depth.

Is it possible to do it more simple, without updating state and so on?

Upvotes: 2

Views: 773

Answers (1)

AKX
AKX

Reputation: 168841

Sure thing, it's not really hard at all once you know the right tools.

Here's a CodeSandbox example I whipped up, and a snippet of the same below (slightly adjusted for Stack Overflow's ancient Babel version).

The idea is:

  • use string.split's regexp mode to split the string into fragments that are either brackets or aren't
  • iterate over those fragments to keep track of the nesting level for the brackets (to properly colorize pairs)
  • finally, return a React fragment with those children (this way we don't have to deal with array keys)

The colors in this example are only 3 levels deep, but you could easily add more, or make the colorization loop over N colors using the modulus operator.

function BracketHighlighter({ text }) {
  const children = React.useMemo(() => {
    const out = [];
    let level = 0;
    text.split(/([()])/).forEach((bit) => {
      if (bit === "(") {
        level++;
        out.push(<span className={"l" + level}>{bit}</span>);
      } else if (bit === ")") {
        out.push(<span className={"l" + level}>{bit}</span>);
        level--;
      } else {
        out.push(bit);
      }
    });
    return out;
  }, [text]);
  return React.createElement(React.Fragment, {}, ...children);
}

function App() {
  const [text, setText] = React.useState(
    "(my text (is here and) I want (to highlight (something here)))"
  );
  return (
    <div className="App">
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <br />
      <BracketHighlighter text={text} />
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
.l1 {
  background-color: orange;
}

.l2 {
  background-color: lightgreen;
}

.l3 {
  background-color: cyan;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 3

Related Questions