React: replace links in a text - javascript

What is the proper way to replace urls in a string and render them as links with React?
Say I have a string: 'hello http://google.com world', and I want it to render like: hello http://google.com world

Ok, so this is how I done it.
class A extends React.Component {
renderText() {
let parts = this.props.text.split(re) // re is a matching regular expression
for (let i = 1; i < parts.length; i += 2) {
parts[i] = <a key={'link' + i} href={parts[i]}>{parts[i]}</a>
}
return parts
}
render() {
let text = this.renderText()
return (
<div className="some_text_class">{text}</div>
)
}
}

I ran into issues with every answer here, so I had to write my own:
// use whatever you want here
const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9#:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()#:%_\+.~#?&//=]*)/;
const renderText = txt =>
txt
.split(" ")
.map(part =>
URL_REGEX.test(part) ? <a href={part}>{part} </a> : part + " "
);

There are NPM modules to handle this. Both of these depend on linkify-it (repo)
react-linkify (repo)
<Linkify>
<div>react-linkify <span>(tasti.github.io/react-linkify/)</span></div>
<div>React component to parse links (urls, emails, etc.) in text into clickable links</div>
See examples at tasti.github.io/react-linkify/.
<footer>Contact: tasti#zakarie.com</footer>
</Linkify>
At time of writing, the current version is 1.0.0-alpha. It requires React 16. The repo has 14 open tickets and 17 open PRs. So that's not fantastic.
Version 0.2.2 allows much earlier versions but doesn't have link text decoration, etc.
react-native-hyperlink ( repo )
If you are using native (ie a phone app), it looks like the better of the two options. Code samples:
<Hyperlink linkDefault={ true }>
<Text style={ { fontSize: 15 } }>
This text will be parsed to check for clickable strings like https://github.com/obipawan/hyperlink and made clickable.
</Text>
</Hyperlink>
<Hyperlink onLongPress={ (url, text) => alert(url + ", " + text) }>
<Text style={ { fontSize: 15 } }>
This text will be parsed to check for clickable strings like https://github.com/obipawan/hyperlink and made clickable for long click.
</Text>
</Hyperlink>
<Hyperlink
linkDefault
injectViewProps={ url => ({
testID: url === 'http://link.com' ? 'id1' : 'id2' ,
style: url === 'https://link.com' ? { color: 'red' } : { color: 'blue' },
//any other props you wish to pass to the component
}) }
>
<Text>You can pass props to clickable components matched by url.
<Text>This url looks red https://link.com
</Text> and this url looks blue https://link2.com </Text>
</Hyperlink>
References
https://github.com/facebook/react-native/issues/3148
REACT - How to replace URL strings to <a> elements and rendering it properly

Try this library, it does exactly you need:
https://www.npmjs.com/package/react-process-string
An example from there:
const processString = require('react-process-string');
let config = [{
regex: /(http|https):\/\/(\S+)\.([a-z]{2,}?)(.*?)( |\,|$|\.)/gim,
fn: (key, result) => <span key={key}>
<a target="_blank" href={`${result[1]}://${result[2]}.${result[3]}${result[4]}`}>{result[2]}.{result[3]}{result[4]}</a>{result[5]}
</span>
}, {
regex: /(\S+)\.([a-z]{2,}?)(.*?)( |\,|$|\.)/gim,
fn: (key, result) => <span key={key}>
<a target="_blank" href={`http://${result[1]}.${result[2]}${result[3]}`}>{result[1]}.{result[2]}{result[3]}</a>{result[4]}
</span>
}];
let stringWithLinks = "Watch this on youtube.com";
let processed = processString(config)(stringWithLinks);
return (
<div>Hello world! {processed}</div>
);
That will replace all links with or without "http://" protocol. If you want to replace only links with protocol, remove the second object from config array.

First add <a> tag to string:
function httpHtml(content) {
const reg = /(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|&|-)+)/g;
return content.replace(reg, "<a href='$1$2'>$1$2</a>");
}
console.log(httpHtml('hello http://google.com world'))
// => hello http://google.com world
Then render string as html in react:
function MyComponent() {
return <div dangerouslySetInnerHTML={{
__html: httpHtml('hello http://google.com world')
}} />;
}

I wrote a short function to do it:
const RE_URL = /\w+:\/\/\S+/g;
function linkify(str) {
let match;
const results = [];
let lastIndex = 0;
while (match = RE_URL.exec(str)) {
const link = match[0];
if (lastIndex !== match.index) {
const text = str.substring(lastIndex, match.index);
results.push(
<span key={results.length}>{text}</span>,
);
}
results.push(
<a key={results.length} href={link} target="_blank">{link}</a>
);
lastIndex = match.index + link.length;
}
if (results.length === 0) {
return str;
}
if (lastIndex !== str.length) {
results.push(
<span key={results.length}>{str.substring(lastIndex)}</span>,
);
}
return results;
}

late to the party but here's a slightly modified version :
export const linkRenderer = (string: string):ReactNode => {
const linkExp = /^https?:\/\/[a-z0-9_./-]*$/i
return <>{
string.split(/(https?:\/\/[a-z0-9_./-]*)/gi).map((part, k) => <React.Fragment key={k}>
{part.match(linkExp) ? <a
href={part}
onFocus={(e) => { e.stopPropagation() }}
target="_blank"
rel="noreferrer"
>{part}</a>
: part}
</React.Fragment>)
}</>
}
Interesting things to note there:
it doesn't split on space or blank space so preserving existing spaces
It create less chunks by only splitting parts where link are not every word or so
the regexp passed to split must have capturing parenthesis if you want to have your links as part of the resulting array.
the noreferrer attribute is required with target blank for security reason on older browsers
Hope this help.

Based on the OP's own answer I came up with the one-liner:
{text
.split(/[-a-zA-Z0-9#:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9#:%_\+.~#?&//=]*)?/gi)
.map((part, index) => index % 2 === 0 ? part : {part}
}

for me, i managed to solve it this way
const ActiveProblem = () => {
const { activeProblem } = useProblems();
const id = new Date().toString();
const match = activeProblem.replace(
urlPattern,
(matched) => id + matched + id
);
return (
<Typography align="center" variant="body1" color="white">
{match.split(id).map((str, idx) => {
if (str.match(urlPattern))
return (
<Box component="a" href={str} key={id + idx}>
{str}
</Box>
);
return str;
})}
</Typography>
);
};
I'm using Material UI with React

Related

Bolding all words in string between certain characters

I am wanting to take a string and get the JSX to replace all the words in between brackets to be bold. I got it to work with this, but just wondering if there is a better way of going about this?
const jsxArray = [];
let unformattedString = "[name] Hi there, my name is [name], I am [age] years old, and I work in the field of [profession]";
const boldStrings = unformattedString.match(/(?<=\[).+?(?=\])/g);
const notBoldStrings = unformattedString.split(/\[.+?\]/g);
let j = 0;
if (boldStrings !== null) {
notBoldStrings.forEach((notBoldString, index) => {
if (index === 0 && notBoldStrings === "") {
// the first word should be bolded
jsxArray.push(<b>{boldStrings[j]}</b>);
} else {
jsxArray.push(notBoldString);
jsxArray.push(<b>{boldStrings[j]}</b>);
}
j++;
});
} else {
jsxArray.push(notBoldStrings[0]);
}
The expected output would be:
name Hi there, my name is name, I am age years old, and I work in the field of profession
You can use this code:
export default function App() {
let unformattedString =
"[name] Hi there, my name is [name], I am [age] years old, and I work in the field of [profession]";
const boldString = (str, substr) =>{
str.replaceAll(substr, `<b>${substr}</b>`);
}
const strArr = unformattedString.split(" ");
const formattedString = strArr
.map((item) => {
let strBold = item.match(/(?<=\[).+?(?=\])/g);
if (strBold) {
return boldString(item, strBold);
} else {
return item;
}
})
.join(" ");
return (
<div className="App">
<div dangerouslySetInnerHTML={{ __html: formattedString }} />
</div>
);
}
To see its result: codesandbox.io
But about dangerouslySetInnerHTML, Setting HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS) attack. And recommended using the html-react-parser package in your React project.
npm install html-react-parser
To use:
import parse from 'html-react-parser'
const yourHtmlString = '<h1>Hello</h1>'
and in return part:
<div>
{parse(yourHtmlString)}
</div>

Highlight characters in a String in React

I am trying to build an autocomplete component where the relevant characters are highlighted in the search suggestions as the user types.
The way I have come up to highlight the characters is this JSX
{suggestion.substring(0, ind) +
<mark>{suggestion.substring(ind, val.length)}</mark> +
suggestion.substring(val.length, suggestion.length)}
but it renders an `[Object Object] instead of the string.
How can I correct this?
Here's my whole code
searchSuggestions
.filter((suggestion) => {
const lowerSuggestion = suggestion.toLowerCase();
const val = eventValue.toLowerCase();
console.log("lowersuggestion is", lowerSuggestion, val);
return lowerSuggestion.indexOf(val) > -1;
})
.map((suggestion) => {
const lowerSuggestion = suggestion.toLowerCase();
const val = eventValue.toLowerCase();
const ind = lowerSuggestion.indexOf(val);
console.log("ind is", ind, typeof suggestion);
return (
<div>
{suggestion.substring(0, ind) +
<mark>{suggestion.substring(ind, val.length)}</mark> +
suggestion.substring(val.length, suggestion.length)}
</div>
);
})
Assuming searchSuggestions is an array of string. How should I go about it?
<mark is a JSX element - which is an object. When you interpolate it (or most objects) into a string, you'll get [object Object].
Use an array instead, so that it gets interpolated into the JSX, rather than interpolated into a string.
<div>
{[
suggestion.substring(0, ind),
<mark>{suggestion.substring(ind, val.length)}</mark>,
suggestion.substring(val.length, suggestion.length)
]}
</div>
There was a problem with the JSX itself, I solved it by changing it to
{suggestion.substring(0, ind)}
<mark style={{ backgroundColor: "#f7e7d0" }}>
{suggestion.substring(ind, ind + val.length)}
</mark>
{suggestion.substring(ind + val.length, suggestion.length)}

how to display values from 2 arrays in paragraphs in ES6/React

I am working with bigger arrays in React, and want the following display like this: image/name image/name image/name. I have the following the code but I don't know how I can map over the images array to so it shows it's image. Thank you
function showProtocolsNames() {
if (supportedVaults) {
let arr = supportedVaults
.map((item) => item.protocolName)
.filter((item, index, arr) => {
return arr.indexOf(item) == index;
});
let arrImages = supportedVaults
.map((item) => item.protocolKey)
.filter((item, index, arr) => {
return arr.indexOf(item) == index;
});
let protocolsName = [...new Set(arr)];
let protocolsImages = [...new Set(arrImages)];
console.log(protocolsName, protocolsImages);
return protocolsName.map((vault) => {
return (
<>
{' '}
<img
src={getVaultIcon(vault)}
width="42px"
height="42px"
style={{
marginRight: '12px',
}}
/>
<p className="vaults-protocol">{vault}</p>
</>
);
});
}
return null;
}
Solved: By creating an array of the images and names together and just mapping over it like DBS suggested in comments.
I believe there is a much simpler solution to your problem than your current approach. For example, you could use the supportedVaults data immediately while mapping the image/name components, like this:
function showProtocolsNames() {
// added check to ensure there is data inside supportedVaults
if (supportedVaults.length) {
// removed the two mapped arrays
// added index which is generated by map function
return protocolsName.map((vault, index) => {
// added div instead of <> in order to include a key, which is required in a map function
return (
<div key={`${index}-${vault?.protocolKey}`}>
{" "}
<img
src={getVaultIcon(vault?.protocolKey)} // here we pass protocolKey to the getVaultIcon function
width="42px"
height="42px"
style={{
marginRight: "12px",
}}
/>
{/* here we add protocolName inside the paragraph */}
<p className="vaults-protocol">{vault?.protocolName}</p>
</div>
);
});
}
return null;
}
This logic above is based on your description of the issue, assuming protocolKey is what you need to pass to get the vault icon in getVaultIcon function and protocolName is the value you need to show as the name. If my perception is wrong, please edit your question to reflect more info on what exact data you need to get from the supportedVaults array, or what format supportedVaults has.

How to handle String mechanism in react js

I have written a small piece of code inside the return. for example
const Demo = (props) => {
return (
<div>
props.map((val, index) => (
<h2>{val.fileName}</h2>
))
</div>
)
}
The output is coming like this:
F:\test\form\student.html
But inside I don't want this type of output. I want to modify the output like: F:\test\form\form.pdf
The last student.html will remove and the form must become 2 times will repeat and lastly, the extension is pdf
original output: F:\test\form\student.html
desired output: F:\test\form\form.pdf
can you help how to solve this problem?
Hope this will help as per my understanding of your question
const Demo = (props) => {
const processedFileNames = props.map((val, index) = {
const splitFileName = val.fileName.split('\\');
splitFileName[splitFileName.length - 1] = splitFileName[splitFileName.length - 2];
return splitFileName.join('\\') + '.pdf';
})
return (
<div>
processedFileNames.map((val, index) => (
<h2>{val}</h2>
))
</div>
)
}
Please let me know if you need any other help.

How to make search bar using React-Redux?

So guys, I have this component:
const Iphone = ({phones,searchQuery}) => {
const filterIphone = phones.map((p, index) =>
(<div className="model" key={index}>
<NavLink to={'/p/' + p.id}>{p.body.model}</NavLink>
</div>
))
return (
<div>
{filterIphone}
</div>
);
};
export default Iphone;
phones - array with objects using which I return model(title of phones) from an object.
searchQuery- value which i get from input. (from Redux)
So, I want to make Search Bar, but I don't know how in this situatuon i can filter " filterIphone " beause i have used map before. I need to make function which filters my titles (model).
Try this,
const phones = searchQuery && searchQuery.trim().length ? phones.filter(p => {
if(p && p.body && p.body.model && p.body.model.toLowerCase().includes(searchQuery.toLowerCase())){
return p
}
}) : phones
#const filterIphone = ......

Categories