reactjspreact

String.replace but returning fragments


Here is something that I'd like to achieve:


const symbolRegex = /[a-zA-Z_$][a-zA-Z_$0-9]*/g;

export function CodeSymbol(props: {code: string; symbols: SymbolMap}) {
  return (
    <code>
      {props.code.replace(symbolRegex, (symbol): any => {
        const packageName = props.symbols.get(symbol);
        if (packageName) {
          return <a href={`/api/${packageName}/${symbol}`}>{symbol}</a>;
        }
        return <>{symbol}</>;
      })}
    </code>
  );
}

Where SymbolMap is basically a Map<string, string>.

Of course this doesn't work because, the preact fragment is converted to string and I get something like: Observable<[object Object]<any>> from the Observable<HttpEvent<any>> input (HttpEvent is defined in the map, but Observable isn't) .

What would you suggest?


Solution

  • Here's one way to achieve this by using .split + .map with a regex that has one single capturing group that captures the whole matching part. With a regex like that, every even index in the array will be a non matching substring and every odd one will be a matching substring.

    import { render } from 'preact';
    
    const symbolRegex = /([a-zA-Z_$][a-zA-Z_$0-9]*)/;
    
    function CodeSymbol(props: {code: string; symbols: SymbolMap}) {
      return (
        <code>
          {props.code.split(symbolRegex).map((symbol, index) => {
            // Every even index is a non-match when the regex has 1 capturing group
            if (index % 2 === 0) return symbol;
            const packageName = props.symbols.get(symbol);
            if (packageName) {
              return <a href={`/api/${packageName}/${symbol}`}>{symbol}</a>;
            }
            return symbol;
          })}
        </code>
      );
    }
    
    
    render(<CodeSymbol code="foo bar" symbols={new Map([["foo", "foopackage"]])} />, document.getElementById('app'));
    
    

    REPL