2

I have a use case where I need to identify phone numbers in a string and convert them to a clickable link. Using an external library is out of scope currently.

Here is what I've done so far:

String: "This is my phone number - 1234567890" I can extract 1234567890 as a phone number using a regex pattern.

I now want to replace it in the original string, so this is what I did:

const string = "This is my phone number - 1234567890"
const number = "1234567890"
const newString = '<a href="#">' + number + '</a>'
number = number.replace(number, newString);

When I do this, instead of getting the phone number as a hyperlink, my output is like this:

This is my phone number - <a href="#">1234567890</a>

If I create my newString without quotes, like this

const newString = <a href="#">number</a>

my output is like this:

This is my phone number - [object Object]

How do I make this a clickable link?

6
  • 1
    It's not clear how you're getting your output. Are you putting number into a JSX return value somewhere? Please show that code. Commented Oct 18, 2019 at 20:20
  • Possible duplicate of How to highlight matches within a string with JSX? Commented Oct 18, 2019 at 20:23
  • While the duplicate candidate focuses on highlighting words, the concept is exactly the same to replace part of a string and render valid JSX without dangerouslySetInnerHTML. Commented Oct 18, 2019 at 20:26
  • Possible duplicate of String as html in reactjs Commented Oct 18, 2019 at 20:33
  • 1
    Also, maybe just const newString = <>This is my phone number - <a href="#">{number}</a></>;? Commented Oct 18, 2019 at 20:33

3 Answers 3

2

You'll need to replace to wrap the phone numbers with links, and then use dangerouslySetInnerHTML to add to React components.

Note: It's called dangerouslySetInnerHTML because you open yourself to XSS attacks from user generated content. You should sanitise the strings first.

const Linkify = ({ text, pattern, formatter }) => {
  const __html = text.replace(pattern, formatter);

  return <div dangerouslySetInnerHTML={{ __html }} />;
};

const text = 'This is my phone number - 1234567890';
const pattern = /\d+/g;
const formatter = str => `<a href="#${str}">${str}</a>`;

ReactDOM.render(
  <Linkify text={text} pattern={pattern} formatter={formatter} />,
  root
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Without using dangerouslySetInnerHTML the main problem is breaking the string into links/non links strings. This can be done by using the code in this answer. I've updated the code to mark string as matches, so you can format them accordingly.

const Linkify = ({ text, pattern, formatter }) => {
  const arr = getSegments(pattern, text);

  return arr.map(formatter);
};

const text = 'This is my phone number - 1234567890, and his is 34234123142';
const pattern = /\d+/g;
const formatter = ({ text, match }) => match ? <a href={`#${text}`}>{text}</a> : text;

ReactDOM.render(
  <Linkify text={text} pattern={pattern} formatter={formatter} />,
  root
);

function getSegments(rex, str) {
  const segments = [];
  let lastIndex = 0;
  let match;
  rex.lastIndex = 0; // In case there's a dangling previous search
  while (match = rex.exec(str)) {
    if (match.index > lastIndex) {
      segments.push({ text: str.substring(lastIndex, match.index) });
    }
    segments.push({ text: match[0], match: true });
    lastIndex = match.index + match[0].length;
  }
  if (lastIndex < str.length) {
    segments.push({ text: str.substring(lastIndex) });
  }
  return segments;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>

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

1 Comment

thanks for the explanation @OriDrori! this was essentially what I was attempting to achieve.
1

You don't really want to set a 'string' to const newString, I would call this more an element. You can do it like this:

const phoneEl = (
  <a href="#">
    {number}
  </a>
);

Where number is the phone number stored in a variable called number.

Then you would write:

<p>My phone number is - {phoneEl}</p>

I'd also suggest

<href={`tel:${number}`}>

if you wanted people to be able to click the link and dial.

2 Comments

This is the best answer which takes React's best practices into account. Only missing the brackets: href={`tel:${number}`} (upvoted anyway)
@EmileBergeron yeah, oversight haha, I've fixed it. Not sure why it's been downvoted with no comment :/
-1

const string = document.querySelector("body");
const text = string.innerText;

const textArr = text.split(" ").map(item => {
  if(item.match(/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im)){
    item = `<a href="tel:${item}">${item}</a>`;
  }
  return item;
});

const newText = textArr.join(" ");

console.log({ newText })

string.innerHtml = newText;
<p class="body">
  This is my phone number - 555-555-5555
</p>

2 Comments

This is a React question, manipulating the DOM directly is a bad practice.
Ahhh. Didn't see the reactjs tag. Only saw javascript in the title.

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.