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>
Related
So currently I'm going for the effect of trying to display formatted code or in this case a graphql operation in my react app. Triggered by state, I want to display or remove certain variables.
const testing = `query {
getBooks {
${value.bookId?"id" : ""}
${value.bookEntry?"entry" : ""}
${value.bookTitle?"title" : ""}
}
}`
...
<div className="output">
<pre>
<code>{testing}</code>
</pre>
</div>
I'm stuck rendering something that looks like this!
There's probably a better way to go around this, but it's worth asking!
Add a filter to remove whitespaces before rendering.
Check my solution below:
// Solution:
function clearQueryString(queryString){
const result = []
const splitted = queryString.split('\n')
console.log({splitted})
for (let index = 0; index < splitted.length; index++) {
const element = splitted[index];
// regex credit: https://stackoverflow.com/questions/10261986/how-to-detect-string-which-contains-only-spaces/50971250
if (!element.replace(/\s/g, '').length) {
console.log(`#${index} string only contains whitespace!`);
} else {
result.push(element)
}
}
return result.join('\n')
}
// Usage:
const value = {}
const testing = `query {
getBooks {
${value.bookId?"id" : ""}
${value.bookEntry?"entry" : ""}
${value.bookTitle?"title" : ""}
author
otherfield
}
}`
document.getElementById('codeOutput').innerHTML = clearQueryString(testing);
<div className="output">
<pre>
<code id="codeOutput"></code>
</pre>
</div>
I've got a typescript app that I've been asked to help with. At the last minute the website owner wanted to change the front end so the links are clickable. Only issue is they data is entered in the backend as a normal anchor href tag. So I've been working on a work around because i can't use that innerhtml method for security reasons.
switch (note.kind) {
case "text":
return (
<div>
{" "}
{note.text.map((content: string, idx: number) => {
const result = [];
const matches = content.match(
/(.*?)(<a href=")?(https?|www)((".*?>.*?<\/a>)|[^\s>]?)*(.*?)/gi
);
if (!!matches) {
matches.forEach((match) => {
let link;
if (/href="/i.test(match)) {
const url = match
.match(/<a href="(.*?)(?=">)/i)![0]
.replace('<a href="', "");
const linkText = match
.match(/(?:<a href=".*?">)(.*)(?=<\/a>)/)![0]
.replace(/(?:<a href=".*?">)/i, "");
link = <a href={url}>{linkText}</a>;
} else {
const url = match.match(/(https?|www)[^\s]*/gi)!.join("");
link = <a href={url}>{url}</a>;
}
const splitter = match.match(
/(<a href=")?(https?|www)[^\s]*(.*<\/a>)?/gi
)![0];
const paredPlainText = match.split(new RegExp(splitter));
result.push(paredPlainText[0]);
result.push(link);
result.push(paredPlainText[1]);
});
} else {
result.push(content);
}
console.log(result);
return <p>{result}</p>;
})}
</div>
);
This is my code but the only issue is I'm running into TypeError: Cannot read property '0' of null on the .match methods. Any help would be much appreciated Thank you!
You should make sure there was a match from regex and it isn't null
const splitter = match.match(/(<a href=")?(https?|www)[^\s]*(.*<\/a>)?/gi);
if (splitter && splitter.length) {
const paredPlainText = match.split(new RegExp(splitter[0]));
const [firstParedPlainText, secondParsedPlainText] = paredPlainText || [null, null];
if (firstParedPlainText) result.push(firstParedPlainText);
result.push(link);
if (secondParsedPlainText) result.push(secondParsedPlainText);
}
I want to create a hangman game in React.js, with this code when the user click on a letter, it will search the letter in the word and display her. It work correctly when the word contain only one same letter.
I would like this code work with word like 'PROGRAMMING' with 2 'M'.
handleChooseLetter = (index) => {
const usedWord = [...this.state.usedWord];
const chosenLetter = this.state.letters[index].letter;
var letterPosition = usedWord.indexOf(chosenLetter);
if (letterPosition >= 0) {
hiddenWord.splice(letterPosition, 1, chosenLetter);
this.setState({hiddenWord: hiddenWord});
}
}
I already try a while loop but it not work in my case:
var indices = [];
while(letterPosition >= 0) {
const hiddenWord = [...this.state.hiddenWord];
indices.push(letterPosition);
letterPosition = usedWord.indexOf(chosenLetter, letterPosition + 1);
hiddenWord.splice(letterPosition, 1, chosenLetter);
this.setState({hiddenWord: hiddenWord});
}
For me, the result is that find the letter and display them always for the last letter of the word.
I think my problem is with the splice method who splice the wrong letterPosition
Here my chooseWord function:
state = {
wordList: [
{ id: 1, word: 'PROGRAMMING'},
],
usedWord: [],
hiddenWord: [],
}
chooseWord() {
const wordList = [...this.state.wordList];
const listLength = wordList.length;
const randomWord = this.state.wordList[Math.floor(Math.random() * listLength)].word;
const splittedWord = randomWord.split("");
const arr = new Array(randomWord.length + 1).join("_").split("");
this.setState({
usedWord: splittedWord,
hiddenWord: arr
});
}
The simplest way is replace, not using an array:
const usedWord = "programming";
const chosenLetter = "m";
const hiddenWord = usedWord.replace(new RegExp("[^" + chosenLetter + "]", "g"), "_");
console.log(hiddenWord);
As the user adds more letters, you can add them to the character class:
const usedWord = "programming";
const chosenLetters = "mp";
const hiddenWord = usedWord.replace(new RegExp("[^" + chosenLetters + "]", "g"), "_");
console.log(hiddenWord);
React Example:
class Hangman extends React.Component {
constructor(...args) {
super(...args);
this.state = {
availableLetters: "abcdefghijklmnopqrstuvwxyz",
chosenLetters: "",
word: this.props.word
};
this.chooseLetter = this.chooseLetter.bind(this);
}
chooseLetter({target: {tagName, type, value}}) {
if (tagName === "INPUT" && type === "button") {
this.setState(prevState => ({chosenLetters: prevState.chosenLetters + value}));
}
}
render() {
const {word, chosenLetters} = this.state;
const hiddenWord = word.replace(new RegExp("[^" + chosenLetters + "]", "g"), "_");
return <div>
<div>Word: <span className="hiddenWord">{hiddenWord}</span></div>
<div onClick={this.chooseLetter} style={{marginTop: "8px"}}>
{[...this.state.availableLetters].map(
letter => <input type="button" value={letter} disabled={chosenLetters.includes(letter)} />
)}
</div>
</div>;
}
}
ReactDOM.render(
<Hangman word="programming" />,
document.getElementById("root")
);
.hiddenWord {
font-family: monospace;
letter-spacing: 1em;
font-size: 18px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
For single English alphabet letters, you don't have to worry about using new RegExp(chosenLetter, "g") because none of the English alphabetic letters has special meaning in a regular expression. If you did have characters with special meaning (., $, etc.), you'd escape the character before passing it to the RegExp constructor; see this question's answers for ways to do that.
I've added letter input <input onChange={this.handleChooseLetter} value={letter} /> and changed your handleChooseLetter function to iterate through letters of used word if at least 1 letter is found (because your usedWord.indexOf(chosenLetter) always returns 1 index only), so I decided to iterate entire word and check for chosen letter, if letter on that index exists, I just insert that letter to the hidden word on the same index - because hidden and used words have the same length:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
hiddenWord: "___________",
usedWord: "PROGRAMMING",
letter: ""
};
}
handleChooseLetter = e => {
const usedWord = [...this.state.usedWord];
const chosenLetter = e.target.value.toLocaleUpperCase();
let letterPosition = usedWord.indexOf(chosenLetter);
if (letterPosition > -1) {
this.setState(prevState => {
const hiddenWord = [...prevState.hiddenWord];
for (let i = 0; i < usedWord.length; i++) {
if (usedWord[i] === chosenLetter) {
hiddenWord[i] = chosenLetter;
}
}
return { hiddenWord, letter: "" };
});
return;
}
this.setState({ letter: "" });
};
render() {
const { hiddenWord, letter } = this.state;
return (
<div className="App">
{[...hiddenWord].map((letter, index) => (
<span key={index}>{letter} </span>
))}
<input onChange={this.handleChooseLetter} value={letter} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Aha! I had to do a similar project for my class once. Heres the github repo for your reference:
https://github.com/LordKriegan/WraithFood
Essentially, what I did was I created an object with several strings and a few functions. When the game loads, it picks a random word from my list and sets 2 of the strings. One is the full word (lets call this property fullWord), the other is basically a string of the same length with all letters converted to underscores (let's call this one guessedWord, also see setWord() in the game.js file).
But you are interested in checking the letters! Thats easy enough. Javascript strings have a built-in method called .includes(), which takes a string as a parameter. Since I had my word saved in the object, I simply ran a .includes() on that word with the letter I wanted to check, then if it passed all my validation (letter already guessed, whether it is or isnt in the word, etc), I ran a for loop on guessedWord with another String method called .charAt(i). This method simply returns the character at position i in the string its called on. Since I have to strings in my object, which I KNOW to be the same length, I can execute a for loop on fullWord, check every letter at position i, and then reset the guessedWord property with .substr(). This method returns partial strings based on what parameters you pass it. Essentially I set guessedWord to + + . This was the checkLet() function in the game object.
I would reccomend reading up on string methods at https://www.w3schools.com/js/js_string_methods.asp
I just spent hours trying to understand what broke my sample app before I found this:
Working example:
let cards = this.props.cards.map(
(card) => {
return <Card id = {card.id}
title = {card.title}
description = {card.description}
color = {card.color}
tasks = {card.tasks} />
});
Broken example:
let cards = this.props.cards.map(
(card) => {
return
<Card id = {card.id}
title = {card.title}
description = {card.description}
color = {card.color}
tasks = {card.tasks} />
});
Should the JSX syntax be this sensitive or am I missing something else?
In your broken example result will be
[undefined, ...., undefined]
because JS interpreter understands it like this(note ; after return),
let cards = this.props.cards.map(
(card) => {
return;
<Card id = {card.id}
title = {card.title}
description = {card.description}
color = {card.color}
tasks = {card.tasks} />
});
if you add new line after return - JS interpreter inserts semicolon automatically, and this is not problem with JSX
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