2

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?

1 Answer 1

3

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>

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much. As always I didn't think about very basic things (forEach, increment/decrement) :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.