ReactJS: create DOM on the fly - reactjs

How to transform this:
{dataFormat: 'hello my [friend=https://en.wikipedia.org/wiki/Friendship]'}
to this:
<div>
hello my <a onClick={...} href="https://en.wikipedia.org/wiki/Friendship">friend</a>
</div>
I need to somehow be able to scan a string and create links on the fly. Any idea?
The dataFormat can contain more than one link with unknown order between "regular" text and links.

Ended up using regex which did the job.
JSBin: https://jsbin.com/yogepa/edit?js,output
Code:
renderSpan(content) {
return <span>
{content}
</span>
}
renderLink(content) {
const parts = content
.replace(/\[|\]/g, '')
.split('=');
return <a style={ styles.link } onClick={ alert }>
{parts[0]}
</a>
}
renderFormat() {
let { dataFormat } = this.state;
const regex = /(\[[^\]]+])*([^\[]+)(\[[^\]]+])*(\[[^\]]+])*([^\[]+)(\[[^\]]+])*(\[[^\]]+])*([^\[]+)(\[[^\]]+])*/;
const matches = regex.exec(dataFormat);
return matches.reduce((output, match, index) => {
if (match && index >= 2) {
output.push(match.indexOf('[') >= 0 ?
this.renderLink(match) :
this.renderSpan(match)
);
}
return output;
}, []);
}
I probably can improve the Regex expression though.

Related

React Loop Through FileList and Display

I am trying to loop through a FileList in React and not having any luck.
I have read this article on Can't use forEach with Filelist and with it's help I am able to print to the console. And this Loop inside React JSX article to help with the loop part however I am not able to display any results.
renderEligibilityDocs(e) {
var proctorDocChanges = this.state.proctorDocChanges;
var files = proctorDocChanges[313];
console.log("files", files);
if (files) {
var post = Array.from(files).forEach(file => {
return (
<div key={file.name}>
<h2>file: {file.name}</h2>
</div>
);
});
Array.from(files).forEach(file => console.log("Print to Console " + file.name));
return <div>
{post}
</div>;
} else {
return <div>
<span>No Files Uploaded</span>
</div>;
}
}
What is the concept that I am missing to display the files in the H tag?
If you want to capture or render the output you should use map instead of forEach.
forEach executes a function for each element but it doesn't do anything with the return values, whereas map builds an array from them.
if (files) {
return Array.from(files).map(file => {
return (
<div key={file.name}>
<h2>file: {file.name}</h2>
</div>
);
});
}
else {
...
}
The forEach method doesn't return anything. This is fine for your second loop where you just want to do a console.log, but your first loop needs to return something - you should use map there.
You can also move the map statement into the return statement:
if (files) {
return (
<div>
{Array.from(files).map(f => (
<h2 key={f.name}>file: {f.name}</h2>
)}
</div>
)
}

OnMouseOut Fails to fire React

<div data-layer="02537886-ed86-42d3-8636-2eb14be0f1d6" className="hiThereImEthanaspiringDeveloperAndStudentInSherwoodOregonFluentInC"><span className="hiThereImEthanaspiringDeveloperAndStudentInSherwoodOregonFluentInC-0">Hi there, I'm Ethan.<br /></span><span className="hiThereImEthanaspiringDeveloperAndStudentInSherwoodOregonFluentInC-21">Aspiring developer and student in Sherwood, Oregon. Fluent in C# and C++. Check out my latest classwork and projects.</span></div>
<div data-layer="7ada00ae-6dad-4f77-ade5-bf2f75c0d775" className="navbar">
<div data-layer="59cc0bc3-2bf9-4f79-be78-a1ca20e7795e" className="rectangle2"></div>
<div data-layer="09a61ec3-8712-441c-904c-fc3bbb3b6909" className="component34">
<div data-layer="4318e043-754b-448e-b68b-4ab40f134b0f" className="contact" onMouseOver={this.changeBackground} onMouseOut={this.resetBackround}>Contact</div>
</div>
<Link data-layer="3deeca70-3656-43d9-be1d-40e60e8c9302" className="classwork3bfce124" to='/Classwork'>
<div data-layer="4eddfb21-ca6f-44bd-9651-ca6034965f18" className="classwork" onMouseOver={this.changeBackground} onMouseOut={this.resetBackround}>Classwork</div>
</Link>
<Link data-layer="a905b090-38a3-40ae-9b94-39cabb23994b" className="homeLink" to='/'>
<div data-layer="3959a03d-437c-4201-b91d-ca43e4d547b5" className="home" onMouseOver={this.changeBackground} onMouseOut={this.resetBackround}>Home</div>
</Link>
</div>
Here is some code from a client's website that will not reset the text color after the mouse leaves the text!
constructor(props) {
super(props);
this.state = {
};
}
changeBackground(e) {
e.target.style.color = 'red';
}
resetBackground(e) {
e.target.style.color = 'white';
}
handleClick(target) {
if (this.props.onClick) {
let name;
let id;
let index = -1;
if (target.search("::") > -1) {
const varCount = target.split("::").length;
if (varCount === 2) {
name = target.split("::")[0];
id = target.split("::")[1];
} else if (varCount === 3) {
name = target.split("::")[0];
index = parseInt(target.split("::")[1]);
id = target.split("::")[2];
}
} else {
name = target;
}
this.props.onClick({ type: 'button', name: name, index: index, id: id });
}
}
This image shows how it turns red when the mouse is over it but won't return to white after the mouse leaves. I am trying to make this website have some fancier hover animations. If there is a easier way to make a transition fade in and out then tell me! I just had read this was the easiest way. :( I don't know about that one!

Map function with no return in React

In one of my react apps I have to loop through an array.
function ActionTags({tags}) {
let thisTagsHtml = (tags);
//thisTagsHTML is a simple string, separated by ##
//string1##string2##string3##string4
let tagsArray = thisTagsHtml.split('##');
console.log(tagsArray);
return (
<div>
{tagsArray.map(function(item, i){
<span key = {i}>{item}</span>
})}
</div>
);
}
This looks pretty simple. However, nothing is returned from the function. Any idea where my mistake is? Thank you.
you missed return keyword before the statement <span key = {i}>{item}</span>
Like this :
function ActionTags({tags}) {
let thisTagsHtml = (tags);
//thisTagsHTML is a simple string, separated by ##
//string1##string2##string3##string4
let tagsArray = thisTagsHtml.split('##');
console.log(tagsArray);
return (<div>
{
tagsArray.map(function(item, i) {
return <span key={i}>{item}</span>
});
}
</div>);
}

Calling a function to change css within render() in a React Component

I am returning a set of information from Spotify in a React Component and want to interrogate the JSON that is returned and highlight the original search term within the artist name. so for example, if you search 'bus' and one of the artists returned is Kate Bush, then this would be highlighted green in 'Kate BUSh'. At the moment I am calling a function from within render(). However, what I get rendered is:
Kate <span style="color:green">Bus</span>h
How do I get render() to read the HTML as HTML (so that Bus would just be green) rather than rendering as text? Relevant code from the React Component below:
// Called from within render() to wrap a span around a search term embedded in the artist, album or track name
underlineSearch(displayString) {
let searchTerm = this.props.searchTerm;
if (displayString.indexOf(searchTerm) !== -1) {
displayString = displayString.replace(searchTerm, '<span style="color:green">'+searchTerm+'</span>');
}
return displayString;
}
render() {
return (
<div className="Track" id="Track">
<div className="Track-information">
<h3>{this.underlineSearch(this.props.trackName)}</h3>
<p>{this.underlineSearch(this.props.artistName)} | {this.underlineSearch(this.props.albumName)}</p>
</div>
</div>
);
}
Your underlineSearch function needs to return React Elements, but right now it is returning a string. You could use a Fragment to make it work:
// Called from within render() to wrap a span around a search term embedded in the artist, album or track name
underlineSearch(displayString) {
const searchTerm = this.props.searchTerm;
const indexOfSearchTerm = displayString.indexOf(searchTerm);
let node;
if (indexOfSearchTerm === -1) {
node = displayString;
} else {
node = (
<React.Fragment>
{displayString.substr(0, indexOfSearchTerm)}
<span style={{color: 'green'}}>
{displayString.substr(indexOfSearchTerm, searchTerm.length)}
</span>
{displayString.substr(indexOfSearchTerm + searchTerm.length)}
</React.Fragment>
);
}
return node;
}
To make your solution even more reusable you can make underlineSearch and wrapper with your styles for highlighting into 2 separate components. Even more, you can search for multiple occurrences of your searchTerm with regex. Found a similar SO question here. I slightly adapted one of the answers there according to your needs, but all credit goes to this amazing and neat solution for highlighting matches of a string in longer texts. Here is the code:
const Match = ({ children }) => (
<span style={{'color':'green'}}>{children}</span>
);
const HighlightMatches = ({ text, searchTerm }) => {
let keyCount = 0;
let splits = text.split(new RegExp(`\\b${searchTerm}\\b`, 'ig'));
let matches = text.match(new RegExp(`\\b${searchTerm}\\b`, 'ig'));
let result = [];
for (let i = 0; i < splits.length; ++i) {
result.push(splits[i]);
if (i < splits.length - 1) {
result.push(<Match key={++keyCount}>{matches[i]}</Match>);
}
}
return (
<p>{result}</p>
);
};
Then in your main component where you render everything you can do this:
render() {
<div className="Track" id="Track">
<div className="Track-information">
<h3>
<HighlightMatches text={this.props.trackName} searchTerm={this.props.searchTerm}/>
</h3>
<p>
<HighlightMatches text={this.props.artistName} searchTerm={this.props.searchTerm} /> |
<HighlightMatches text={this.props.albumName} searchTerm={this.props.searchTerm} />
</div>
</div>
}
To me this seems like the most react-like approach to solve the problem :)
While you can use dangerouslySetInnerHTML (), as the name suggests it is extremely dangerous, since it is prone to XSS attacks, for example:
{artist: "Kate Bush<script> giveMeAllYourCookies()</script>"}
You can split the displayString into an array and render it.
Please note that that my implementation of underlineSearch is buggy, and will not work if there are more than one match.
class Main extends React.Component {
underlineSearch(displayString) {
let searchTerm = this.props.searchTerm;
var index = 0;
var results = [];
var offset = 0;
while(true) {
const index = displayString.indexOf(searchTerm, offset);
if(index < 0) {
results.push(<span>{displayString.substr(offset)}</span>);
break;
}
results.push(<span> {displayString.substr(offset, index)}</span>);
results.push(<strong style={{color: 'green'}}> {displayString.substr(index, searchTerm.length)}</strong>);
offset = index + searchTerm.length;
}
return results;
}
render() {
return <div>
<h3>{this.underlineSearch(this.props.trackName)}</h3>
<p>{this.underlineSearch(this.props.artistName)} | {this.underlineSearch(this.props.albumName)}</p>
</div>
}
}
ReactDOM.render(<Main
trackName="Magic Buses"
artistName="Kate Bush"
albumName="Kate Bush Alubm"
searchTerm="Bus"
/>, document.getElementById('main'))
<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>
<div id='main'></div>

Inner Map function not working - ReactJS

I want to display 8 sports in a list and display it's status participated when user already participated. Display a button when user not participated.But i didn't succeed it with the inner map function
{this.state.sports.map(function(sport, index){
return(
<div>
<li key={index} style={{ listStyle:'none' }}>
<h4>{ sport.name }</h4>
</li>
{this.state.join_sports.map(function(sport1, index){
return (
<div>
{sport._id === sport1.s_id ?
<span>Participated</span> :
<button className="btn btn-info btn-xs">Participate</button>
}
</div>
)
}.bind(this))}
</div>
);
}.bind(this))}
I'm going to take a stab at this and simplify it with some ES6 syntax and some pseudo code markup
I think your map function looks like this:
mapSportsToUser() {
const sports = ['a', 'b', ... 'z']//pseudo data
const user = ['b', 'c'] //we should only show sport c and c
return (
<container> //not needed in 15.5+ I think
{ sports.map( (sport, sportIndex) => ( //this will generate one title for each sport
<title>{sport.name}</title>
//your second map iterates over the list...
//(probably not the easiest way to do this but it should work)
{ user.map( (sportUser, userIndex) => {
if (sportUser == sport) {
return <participate />
} else {
return null //don't render anything
}
} }
)) }
</container>
)
}
I think you're making this really complex by looping over the everything rather than building the data in a way that makes sense from the start.
You might consider something like this:
structureData(listOfSports, listUserHasParticipatedIn) {
let sportsLookup = {} //create a map
for(let i = 0; i < listOfSports.length; i++) {//or whatever iterator you like
const sport = listOfSports[i]
sportsLookup[sport.id] = {
sport: sport,
participation: false,
}
}
for(let i = 0; i < listUserHasParticipatedIn.length; i++) {
const user_sport = listUserHasParticipatedIn[i]
//you might want to do a null check here
sportsLookup[user_sport.id].participation = true;
}
return sportsLookup
}
Then you have something easier to map which looks like:
[{ sport: 'a', participation: false}, { sport: 'b', participation: true} ...]
....
sportsLookup.map( lookup => { //much simpler
return <container> {lookup.sport.name} {lookup.participation ? 'Yes' : 'No'}</container>
})
I got succeeded in the inner map looping technique. Just take a variable make it false initially and make it true when the condition is true. That's all. Thank you.
{ this.state.sports.map((sport,index)=>{
let is_participate = false;
this.state.join_sports.map((j_spt,index)=>{
if(sport._id === j_spt.s_id[0]){
is_participate = true;
}
});
return(
<li>{is_participate? <span>{sport.name}-- participated</span>:<span>{sport.name}-- participate </span>}</li>
)
})
}

Resources