How to implement a component so that when I pass text to it.
All the hyperlinks (https://somelink.com) in the text should become tappable.
All the emails(example#example.com) in the text should open the default mail app on the device
Assuming you want a ready-to-go implementation, you could use react-string-replace following the Multiple replacements on a single string section of the docs:
const text = 'Hey #ian_sinn, check out this link https://github.com/iansinnott/ Hope to see you at #reactconf';
let replacedText;
// Match URLs
replacedText = reactStringReplace(text, /(https?:\/\/\S+)/g, (match, i) => (
<a key={match + i} href={match}>{match}</a>
));
// Match #-mentions
replacedText = reactStringReplace(replacedText, /#(\w+)/g, (match, i) => (
<a key={match + i} href={`https://twitter.com/${match}`}>#{match}</a>
));
Adjust the regexes used and components rendered to match your use case.
Related
I'm making a film review site where users can search for the title of a film and when entered, a list of films matching the entered title will appear.
I made it so that each film and its corresponding information is contained within a button. I want to add an event listener to the film buttons that will listen to when the button is clicked and retrieve an image link of the movie poster from that specific button that was clicked and plug it into a text field with the ID of "imageSrc". The problem is that currently, it only retrieves the image link of the last film button that is displayed on the page rather than retrieving a specific film button I clicked and the last image link is plugged into the text field "imageSrc" right after entering the search (no film buttons were clicked, it just automatically plugs in the image link associated with the last film result displayed).
function FilmCard({ film }) {
function displaySearchResults({ film }) {
let results = [];
for (let i = 1; i < 5; i++) {
if (film.results[`${i}`]) {
results +=
`<button class='singleFilmCards' onclick=${(document.getElementById(
"imageSrc"
).value = film.results[`${i}`].image)}>` +
"<div class='film-wrapper'>" +
`<img src="${film.results[`${i}`].image}">` +
"<h2>" +
film.results[`${i}`].title +
"</h2>" +
"<h3>" +
film.results[`${i}`].description +
"</h3>" +
"<div>" +
"</button>";
}
}
return results;
}
if (!film) return <></>;
return (
<>
<div
className="FilmList"
dangerouslySetInnerHTML={{
__html: displaySearchResults({ film }),
}}
>
{/* <img alt="Movie Posters" src={film.results[0].image} />
<h2>{film.results[0].title}</h2>
<p>{film.results[0].description}</p> */}
</div>
</>
);
}
This happens because the index variable i in your loop will always have value of 5 in all the onclicks. There is very good reason why setting a pure html in react is done with the dangerouslySetInnerHTML prop - to let the developer know that he/she's doing something that shouldn't be done (there are cases when it's necessary, extremely rare though...)
What you should do, is to make the button into it's own component and handle the events via react event callbacks.
You could try something like this:
function FilmButton({ result, onImageChange }) {
return (
<button
className="singleFilmCards"
onClick={() => onImageChange(result.image)}
>
<div className="film-wrapper">
<img src={result.image} />
<h2>{result.title}</h2>
<h3>{result.description}</h3>
</div>
</button>
);
}
function FilmCard({ film }) {
const handleImageChange = (imageUrl) => {
document.getElementById("imageSrc").value = imageUrl;
};
if (!film) {
return <></>;
}
return (
<div className="FilmList">
{film.results.map((result, index) => (
<FilmButton
key={index}
result={result}
onImageChange={handleImageChange}
/>
))}
</div>
);
}
...in fact, you definitely might want to consider also rewriting the document.getElementById("imageSrc").value = imageUrl in such way, that you won't be manipulating the DOM directly. Because this completely bypasses React.js. There are many ways you rewrite this to use React - from passing the new imageUrl via prop callback into higher component (same as the onImageChange in my example), to using a React context (context would probably be an overkill though :)
I want to manage the content of the page from a content editor where I am getting page content from the API.
Check this screenshot.
I used two different react modules for this react-html-parser and react-string-replace but it is still not working.
Here is my code.
let pageData = '';
pageData = ReactHtmlParser(page.content);
// replacing contact us form with a contact us form component
pageData = reactStringReplace(pageData, '{CONTACT_US_FORM}', (match, i) => (
<ContactUsForm />
));
return <div>{pageData}</div>;
react-html-parser -> It is used to parse HTML tags which are in string format into tree of elements.
react-string-replace -> It is used to replace a string into react a component.
Note: If I use react-html-parser or react-string-replace individually then it works fine but it does not work together.
Any suggestion?
Depends on the expected structure of page.content. If it contains HTML you are right in using react-html-parser, which has a replace option.
import parse from 'html-react-parser';
const macro = '{CONTACT_US_FORM}';
const replaceContactUsForm = (domhandlerNode) => {
if (domhandlerNode.type === 'text' && domhandlerNode.data.includes(macro))
return <ContactUsForm />;
};
// ...
const elements = parse(page.content, { replace: replaceContactUsForm });
return <div>{elements}</div>;
Additionally, If the string {CONTACT_US_FORM} is embedded in text you could use react-string-replace to keep the rest of the text intact:
const replaceContactUsForm = (domhandlerNode) => {
if (domhandlerNode.type === 'text' && domhandlerNode.data.includes(macro))
return <>{reactStringReplace(domhandlerNode.data, macro, () => (<ContactUsForm />))}</>;
};
If page.content does not contain HTML you do not need react-html-parser. But judging from your screenshot some markup is probably contained.
I have a scenario like showing text as well adding class togathered. it look like i require to add multiple times with same elements nearly. what would be the correct approach for this kind of scenarios?
here is my template:
<span><a className={this.state.showMore ? 'active' : ''} onClick={this.showMore}>{this.state.showMore ? 'read less' : 'read more'}</a></span>
i have added the state showMore both a tag and the text inside. is there any simple way to handle same conditions across page?
Thanks in advance.
I'd create a component to handle read-more, and pass the props from where it's used if there's any, So same functionality is same across my application and if there's any improvements I can handle by it in one single place.
here is a demo
EX: functional component
export const ReadMore = ({ text, truncateLength = 10 }) => {
const [showMore, setShowMore] = useState(false);
const getText = () => {
if (showMore) {
return text;
}
const truncatedText = text.substring(0, truncateLength);
if (text.length > truncateLength) {
return `${truncatedText}...`;
}
return truncatedText;
};
return (
<span>
{getText()}
<a
className={showMore ? "active" : ""}
onClick={() => setShowMore(!showMore)}
>
{text.length > truncateLength && (showMore ? "read less" : "read more")}
</a>
</span>
);
};
and use it like this props could be:
text: is the text that should be read-less or more.
truncateLength: is the length that should show if the text length is
greater, and optional prop, if this isn't provided ReadMore
component will set the value to 10 by default, (check the props of
ReadMore)
<ReadMore
text="this is the text that should do the react-more and read-less"
truncateLength={10}
/>
{this.state.showmore ?
<span><a className={'active'} onClick={this.showMore}>read less</a></span>
:
<span><a onClick={this.showMore}>read more</a></span>
}
should be a more readable and clearer way of doing this. Basically when you have >1 thing depending on the same condition, take the condition outside would be my way to go!
I want to render an HTML checkbox whose checked state is controlled by data.
Give a stateless component that receives an item type { label: string, checked: bool},
Like so:
let component = ReasonReact.statelessComponent("TodoItem");
let make = (~item, _children) => {
render: _self => {
<li> <input type_="checkbox" {/*looking for something like this*/ item.checked ? "checked" : "" /* doesn't compile */}/> {ReasonReact.string(item.label)} </li>
}
}
How do I add the presence of the attribute checked to the input tag based on the item.checked == true condition?
As #wegry said in a comment, it seems to fit your use case better to just pass the value directly since item.checked already is a boolean, and checked takes a boolean.
But to answer more generally, since JSX attributes are just optional function arguments under the hood, you can use a neat syntactic trick to be able to explicitly pass an option to it: Just precede the value with ?. With your example:
let component = ReasonReact.statelessComponent("TodoItem");
let make = (~item, _children) => {
render: _self => {
<li> <input type_="checkbox" checked=?(item.checked ? Some(true) : None) /> {ReasonReact.string(item.label)} </li>
}
}
Or, to give an example where you already have an option:
let link = (~url=?, label) =>
<a href=?url> {ReasonReact.string(label)} </a>
This is documented in the section titled Explicitly Passed Optional on the Function page in the Reason docs.
Consider this react code:
subTopicOptions = curSubTopics.map((topic) => {
return (
<option value={topic.id}>{topic.name}</option>
)
});
The topic.name output Environment & Forest value from rest API to display.
How can I display Environment & Forest in the select field?
You could use the browser's native parser as described in the top answer to Decode & back to & in JavaScript.
Using the DOMParser api, you can do the following:
const strToDecode = 'Environment & Forest';
const parser = new DOMParser();
const decodedString = parser.parseFromString(`<!doctype html><body>${strToDecode}`, 'text/html').body.textContent;
console.log(decodedString);
What you're doing there is using the DOMParser to create a HTMLDocument by appending our string to decode to the end of '<!doctype html><body>'. We can then access the decoded value of our string in the textContent of the body of that newly created HTMLDocument.
If creating the topic is under your control, you can use dangerouslySetInnerHTML
subTopicOptions = curSubTopics.map((topic) => {
return (
<option value={topic.id} dangerouslySetInnerHTML={{__html: topic.name }} />
)
});
You shoukd be aware that use of the innerHTML can open you up to a cross-site scripting (XSS) attack, so use it wisely.
const getDecodedString = (str) => {
const txt = document.createElement('textarea');
txt.innerHTML = str;
return txt.value;
};