Performance benefits to dangerouslySetInnerHTML - javascript

I have a list of items and a user that wants to search those items. When the user’s search value matches the name of an item, I want to render that item with the matching part of the item's name in bold. For example:
const items = [‘milk’, ‘whole milk’, ‘2% milk’]
const searchValue = ‘milk’;
/* outputted HTML */
<ul>
<li>
<Strong>milk<Strong>
</li>
<li>
whole <Strong>milk<Strong>
</li>
<li>
2% <Strong>milk<Strong>
</li>
</ul>
The application is written in React and I wrote the following:
render() {
const { items, searchValue } = this.props;
return (
<ul>
{items.map((item) => {
const parts = item.split(new RegExp('(' + searchValue + ')'));
if (parts.length === 1) return null;
else return <li key={item}>{parts.filter((part) => (part === searchValue ? <strong>{part}</strong> : part))}</li>;
})}
</ul>
);
}
The code above preforms poorly sometimes taking seconds to render. This is unsurprising since a simple searchValue such as "a" can result in hundreds of items being rendered. I did notice however that if I use dangerouslySetInnerHTML, these performance problems disappear:
render() {
const { items, searchValue } = this.props;
return (
<ul>
{items.map((item) => {
if (!item.includes(searchValue)) return null;
const boldedItem = item.replace(new RegExp(searchValue), (match) => '<Strong>' + match + '</Strong>');
return <li key={item} dangerouslySetInnerHTML={{ __html: boldedItem }}></li>;
})}
</ul>
);
}
Does anyone know why dangerouslySetInnerHTML is preforming better here?
While I'll likely solve this problem by limiting the rendered search results (ex: https://github.com/bvaughn/react-window) instead of using dangerouslySetInnerHTML, I still want to know what is happening under the hood here.

Related

Comparing Arrays using .map (ReactJS)

In my project, I have 2 arrays being read from Firestore.
courseList - A list of courses of which a user is enrolled in
courses - A list of all of the courses available in the project
I would like to compare these using a .map so that in my course portal, only the courses of which the user is enrolled in is rendered.
Here is what the arrays look like:
courseList:
courses
I know the arrays work, however, the .map doesn't seem to be working!
Here's my code:
const {courses} = this.state
const {courseList} = this.state
{
courses.length && courses.map (course => {
if (course.courseUrl === courseList.CourseCode) {
return (
<div className = "CourseTile" key = {course.courseName}>
<div className = "CourseTitle">
<h1> {course.courseName}</h1>
</div>
<div className = "CourseDescription">
<p> {course.courseSummary}</p>
</div>
<Link to={`/course/${course.courseUrl}/courseinformation`}> <button className = "LetsGoButton"> Take course</button> </Link>
</div>
)
}
else return null;
}
)
}
If I replace
if (course.courseUrl === courseList.CourseCode)
with
if (course.courseUrl === "websitedesign")
It renders the website design course only, So I believe there's something wrong with this line.
Any help would be appreciated.
You are correct in where the problem lies:
course.courseUrl === courseList.CourseCode
In this case course is a single item from a list, with a property courseUrl. That's fine. But courseList is an array of items, each of which has a CourseCode property. The Array itself does not (although, interestingly, it could).
It seems like what you are trying to do is pull the full course data (from courses) but filtered to only the ones the user has. In this case, you have to loop through one list, looking through the other list for each item. What you want is filter (or, more powerfully, reduce) but probably not map.
const filteredCourses = availableCourses.filter( availableCourse => studentsCourses.some( studentsCourse => studentsCourse.id === availableCourse.id ) );
You'll notice I renamed the variables to make it clear which of the two lists is being used at each part.
The outer function filter will return a new array containing only those items that return 'true' in the callback function.
The inner callback function some loops through another array (the student's enrolled courses) and returns true if it finds any that match the given condition.
So in English, "Filter this list of all the courses, giving me back only the courses that have a matching ID in the list of the student's enrolled courses."
if (course.courseUrl === courseList.CourseCode)
You can filter the courses array by checking if each course is included in the courseList array, matching an URL to a courseList element's CourseCode property. array.prototype.some is used to iterate the course list and check that at least one courseList item matches. Once filtered you can map the filtered result as per normal.
const {courses} = this.state;
const {courseList} = this.state;
...
{courses
.filter(({ courseUrl }) =>
courseList.some(({ CourseCode }) => CourseCode === courseUrl)
)
.map((course) => {
return (
<div className="CourseTile" key={course.courseName}>
<div className="CourseTitle">
<h1> {course.courseName}</h1>
</div>
<div className="CourseDescription">
<p> {course.courseSummary}</p>
</div>
<Link to={`/course/${course.courseUrl}/courseinformation`}>
{" "}
<button className="LetsGoButton"> Take course</button>{" "}
</Link>
</div>
);
})}

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 = ......

How can I give a key in JSX the value of a variable depending on conditions

I'm learning React by implementing a front-end interface for the note app API that I created. I have succeeded in having a list of all the note titles in my database appear. I want to be able to click on a title and have the note expand into the text of the note. The easiest way I've found for this is to give the "key" attribute of the 'li' as a variable and to also declare the same variable in the JSX { } object because they have the same name.
I've been looking for an answer for this for a few days and have been unable to find this exact problem. You can put a variable in a normal JSX expression but I need to do it on the 'li' which means technically in the HTML.
Here's some code to understand what I'm saying.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [open, setOpen] = useState(false);
useEffect(() => {
//AXIOS CALL
setTitles(response.data[0]);
});
}, []);
//^^^^^add the array there to stop the response.data from repeating WAY TOO MANY TIMES
let listTitles = titles.map(titles => (
<li className="noteTitles" key={titles.title}>
{titles.title}
</li>
));
let showText = titles.map(titles => (
<li className="openText" key= {titles.text_entry}>
{titles.text_entry}
</li>
))
let openNote = () => {
setOpen(open => !open);
if (open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{showText}
</ul>
</div>
);
}
if (!open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listTitles}
</ul>
</div>
);
}
};
return { openNote };
};
export default NoteData;
That is the code I currently have. Here's showing a more simplified version of the openNote function that maybe makes more sense and shows what I'm trying to do:
VariableHere = "";
let openNote = () => {
setOpen(open => !open);
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
};
let listNotes = titles.map(titles => (
<li className="noteTitles" key={VariableHere}>
{VariableHere}
</li>
));
return (
<div>
<ul onClick={openNote}>
{listNotes}
</ul>
</div>
);
On click of each element there should be a switch of the key elements so if the element is 'open' the key variable and given variable in the JSX object should be mapped to titles.text_entry and on '(!open)' the key and JSX should be mapped to titles.title.
first of all, you're using a ternary in a weird way:
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
Ternaries are meant to be expressions whose value is conditional, but you're using it like a shorthand if/else. Try something like
VariableHere = open ? titles.text_entry : titles.title;
which is both shorter and more readable.
Second of all, keys in an array of elements are meant to help React determine which elements to update, if an item represents the same object, its key shouldn't change. In this case, regardless of what you're displaying, an item in the array represents the same note. Always using the title as the key should be fine provided items can't have the same title. If they can, use some sort of unique ID instead. If the order of the items doesn't change throughout the life of the component, using the array index as the key is fine.
Lastly, what you seem to want to do is called "conditional rendering". There are many ways to achieve this in react, one such way is to use the pre-cited ternary operator. Here is a minimal working example:
const listNotes = titles.map(note => (
<li className="noteTitles" key={note.title}>
{open ? note.title : note.text_entry}
</li>
));
const openNote = () => {
setOpen(!open);
}
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listNotes}
</ul>
</div>
)
You could also use a ternary in the key expression, but as I talked about above, it's not a good idea to do so.
Given your data-structure, I think you can simplify your code a bit. There is no need to create separate arrays for titles and contents. It sounds like you just want to expand and collapse a note when it is selected.
Here is a really simplified version on how you an do this. I'll use a sample data-set since we don't have access to your API.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [currentNote, setCurrentNote] = useState({});
useEffect(() => {
//AXIOS CALL
// setTitles(response.data[0]);
let data = [
{ id: 1, title: "a", text_entry: "what" },
{ id: 2, title: "b", text_entry: "is" },
{ id: 3, title: "c", text_entry: "up?" }
];
setTitles(data);
}, []);
const handleClick = noteId => {
let selectedTitle = titles.find(title => title.id == noteId);
//"collapse" if already selected
if (noteId === currentNote.id) {
setCurrentNote({});
} else {
setCurrentNote(selectedTitle);
}
};
let listTitles = titles.map(title => (
<li
className="noteTitles"
key={title.title}
onClick={() => handleClick(title.id)}
>
{title.title}
{title.id === currentNote.id && <div>{title.text_entry}</div>}
</li>
));
return (
<div>
Click on link item
<ul>{listTitles}</ul>
</div>
);
};
See working sandbox: https://codesandbox.io/s/old-silence-366ne
The main updates:
You don't need to have an "open" state. To be more succinct and
accurate, you should have a currentNote state instead, which is
set when clicking on a list item.
Have your handleClick function accept a noteId as an argument.
Then use that noteId to find the corresponding note in your titles
state. Set that found note as the currentNote. If the selected
note was already the currentNote, simply set currentNote to an
empty object {}, thus creating our expanding/collapsing effect.
In the JSX, after the title, use a ternary operator to conditionally
display the currentNote. If the note being mapped matches the
currentNote, then you would display a div containing the
text_entry.

Remove duplicates when calling from Github API in React

I have a function where I am calling the list of Filetypes from the gist API of a user by looping through it (https://api.github.com/users/getify/gists) :
const FileTags = ({files}) => {
return(
<div>
{
Object.keys(files).map(function (key) {
return(
<ul>
<li> {files[key].language } </li>
</ul>
)
})
}
</div>
);
}
I am able to successfully call the list of languages but the list is with a lot of duplicates, for example:
Markdown
Markdown
JavaScript
JavaScript
JavaScript
JavaScript
JavaScript
How do I filter out unique languages without them repeating such as
"Markdown Javascript"
?
Maybe something like this:
const FileTags = ( {files } ) =>
<div>
<ul>
{
[...new Set(Object.keys(files).map(key => files[key].language))]
.map( el => <li>{el}</li>)
}
</ul>
</div>
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
The Set object lets you store unique values of any type, whether
primitive values or object references.

How to map a nested array in React.js?

Issue: I can only render one iteration of my array.
My desired result of course is to get the entire length of array objects.
Adding [key] to my rendered object fields is the only method that gives me any output. Without declaring the key in this way, I get nothing
Child Component
...
const Potatoes = ({potatoes}) => {
const PotatoItems = potatoes.map((potato, key) => {
if ([potato] == ''){
return false
} else {
return (
<li key={key}>
<span>{potato[key].name}</span>
<span>{potato[key].flavor}</span>
</li>);
}
});
return (
<div>
<ul>
{PotatoItems}
</ul>
</div>
);
};
Parent Component
...
render () {
const potatoes = new Array(this.props.potatoes);
return (
<section style={divStyle}>
<Potatoes potatoes={potatoes} />
</section>
)
}
Simply removing new Array() from around the potatoes constant fixes your issue.
It seems like you may have created an unnecessary additional array.
Then you can remove those [key] references on your object in the child component and you should be good to go!
Does this fix your issue?

Categories