ReactJS - embedding custom element in a custom element - reactjs

I just started to learn ReactJS and encountered a problem that I can't solve.
I'm creating a basic application for TV Shows. I have a bootstrap tab for every season of a show and within this tab I want to list all the episodes of the selected season. The problem is that I have to create the tabs in a loop, and within this loop I should have another loop for the episodes.
I'm trying to do something like this:
EpisodeCards(props) {
return (
<div>
This should contain the details of the episodes
</div>
)
}
SeasonTabs(props) {
console.log('Seasons: ', props.seasons)
let tabs = [];
let episodes = [];
let currentSeason = 1;
let id;
let aria;
for(let season of props.seasons) {
episodes = [];
id="nav-season" + currentSeason;
aria = "nav-season" + currentSeason + "-tab";
tabs.push(<div className="tab-pane fade" id={id} role="tabpanel" aria-labelledby={aria}><this.EpisodeCards episodes={season}></this.EpisodeCards></div>);
currentSeason++;
}
return (
<div className="tab-content py-3 px-3 px-sm-0" id="nav-tabContent">
{tabs}
</div>
)
}
For this I am getting the following error:
Unhandled Rejection (TypeError): Cannot read property 'EpisodeCards' of undefined
How can this be done in the 'react way'? Thanks in advance.

Change
SeasonTabs(props)
to
SeasonTabs = (props) =>
You want to access a class property using this but by default its not binded to the function (ES5 only) by creating the functions using arrow () =>(new ES6 syntax) it automatically bind this to the function.
For Example:
class Test extends Component{
constructor(props){
this.testFn= this.testFn.bind(this);
}
testFn(){
console.log(this); //will output
}
testFn2(){
console.log(this); // undefined
}
testFn3 = () =>{
console.log(this); //will output
}
}

Reactjs is all about components, everything is component. If a function return a react element or Custom element then is a component.
You are creating a functional component inside of a class component, while this approach may work but this is not appropriate way to make component.
It is better to use composition, your code will be more clear, easier
to read, better maintainability and you can use component in other
components.
My solution:
function EpisodeCards(props) {
return (
<div>
This should contain the details of the episodes
</div>
)
}
SeasonTabs(props) {
console.log('Seasons: ', props.seasons)
let tabs = [];
let episodes = [];
let currentSeason = 1;
let id;
let aria;
for(let season of props.seasons) {
episodes = [];
id="nav-season" + currentSeason;
aria = "nav-season" + currentSeason + "-tab";
//There is no need for this keyword
tabs.push(<div className="tab-pane fade" id={id} role="tabpanel" aria-labelledby={aria}><EpisodeCards episodes={season}/></div>);
currentSeason++;
}
return (
<div className="tab-content py-3 px-3 px-sm-0" id="nav-tabContent">
{tabs}
</div>
)
}

Related

call function in React functional component

I am trying to call a function from a div like the following
<div id='div_abstract'>
{content.abstract && content.abstract.length ? (
<article id="abstract" onMouseUp={spanSelect}>{content.abstract </article>) : ''}
</div>
My functional component is structured like this
export default function ExternalInfos(props) {
...
function spanSelect() { ... }
return(
...
);
}
And the function I'm trying to call is
let table = [];
function spanSelect() {
var span = document.createElement("span");
span.setAttribute("id","span");
if (window.getSelection()) {
var text = window.getSelection();
if (text.rangeCount) {
var range = text.getRangeAt(0).cloneRange();
range.surroundContents(span);
text.removeAllRanges();
text.addRange(range);
};
};
let object = window.getSelection().toString();
table.push(object);
const annotation = document.getElementById("annotationArea");
annotation.updateObjectAnnotation(table);
}
But nothing happens when I select text from my div and it doesn't return an error.
How do I solve this?
You need to capitalize the event handler prop: onMouseUp.
From the React docs (https://reactjs.org/docs/handling-events.html):
"React events are named using camelCase, rather than lowercase."

Can't dynamically render reactjs elements using .map()

I am trying to dynamically render out ingredient/qty/measure using .map() in reactjs I am following a few tutorials online but unable to properly implement the code in my own code.
Here is the error I am currently getting:
react-dom.development.js:86 Warning: Functions are not valid as a React child.
This may happen if you return a Component instead of <Component /> from render.
Or maybe you meant to call this function rather than return it.
Here is the data I am trying to map over:
recipeIngredients: Array(2)
0: {name: 'lemon', quantity: '100', measure: 'g'}
1: {name: 'butter', quantity: '5', measure: 'cups'}
Here is my code:
import './IngredientsList.css'
let ingredientArray
function mapIngredients() {
ingredientArray.map((item) => (
<div className="ingredient-element">{item}</div>
))
}
function IngredientsList(props) {
console.log(props)
ingredientArray = props.recipeIngredients
return <div className="ingredient-list">{mapIngredients}</div>
}
export default IngredientsList
Basically, trying to render out the following set of divs (recipeIngredients.name has an additional class):
<div className="ingredient-list">
<div className="ingredient-element">recipeIngredients.quantity</div>
<div className="ingredient-element">recipeIngredients.measure</div>
<div className="ingredient-element ingredient-name">recipeIngredients.name</div>
</div>
I notice that the () are missing from the IngredientList function - VSCode keeps deleting them when I save the code so I can't manually add them...
You forgot to invoke the function. Here you're trying to render the function itself:
return <div className="ingredient-list">{mapIngredients}</div>
Instead, invoke the function and render its result:
return <div className="ingredient-list">{mapIngredients()}</div>
Additionally, the function currently doesn't return anything. Add a return statement:
function mapIngredients() {
return ingredientArray.map((item) => (
<div className="ingredient-element">{item}</div>
))
}
I'm seeing two issues here. First, you're not invoking the function(as you said that vs code is not letting you do that). The second is item is an object here. Try this instead:-
function IngredientsList(props) {
console.log(props)
ingredientArray = props.recipeIngredients || [];
return (
<div className="ingredient-list">
{
ingredientArray.map(item => (
<div className="ingredient-element">{item.name}</div>
))
}
</div>
)
}

How to retrieve information on a specific button when clicked?

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 :)

React If / else for picture src

I want the image src for to change based on the value of the "p" element. I have imported all of my images like this:
import food from '../../images/food.png'
I've tried to use if/else statements and wrapping them in a function, then mounting them with componentDidMount when the page loads up, but that doesn't work for me. Then I tried :
images = () => {
const image = document.querySelectorAll('img');
let src = image.attr('src');
if (src.valueOf() === 'Food') image.src = {food};
// Changed src.val() because that didn't work //
}
componentDidMount() {
this.images()
)}
And this does nothing for me either. Any suggestions?
MAIN BODY OF CODE:
(top of code not show)
return (
<GuideDiv>
<div className='top'>
<h1>Guides</h1>
<NewGuide to='/GuideForm'>
<h2> Add <img src={travel} alt='ravel'/> Guide </h2>
</NewGuide>
</div>
{guided.map(guide =>
<GuidePost key={guide.id}>
<div>
<UserIcon>
<img id='guide' alt='type' src={guide.guide_type}/>
</UserIcon>
<label className='under'>Guide Type</label>
<br/>
<p>{guide.guide_type}</p>
<br/>
(rest of code not shown)
Why don't you try an mapping. Something like this if its predefined set
import food from '../../images/food.png'
import car from '../../images/car.png'
let mapping = {
FOOD: food,
CAR: car
}
const images = () => {
const image = document.querySelectorAll('img');
let srcVal = image.attr('src')?.valueOf;
image.src = mapping[srcVal];
//....rest of your code
}
Check this one and let me know whether it helped you
THANK YOU!!!
With a little modification, I did the suggested object map and then:
let imgURL = {
Food: food,
Stay: stay,
Sites: sites,
Tips: tips
}
<img id='guide' alt='user' src={imgURL[guide.guide_type]}/>

React application slow performance

I am trying to make a application in react for rendering a tree like structure which can be expanded and collapsed on user input, although i have managed to get the app working as I want but the performance is quite slow. I am not sure if this is because of the nature of the application, the react component or my ignorance of the framework.
I have done a chrome profiling and here are the screenshots:
Please if you can help me understand through this images what is the bottleneck and if/how it can be solved.
Source :
https://github.com/harsh-a1/react-skeleton/tree/tree
Component :
export function TreeComponent(props){
var instance = Object.create(React.Component.prototype)
var state = {
previousSelected :{},
onSelectCallback : props.onSelectCallback
}
instance.props = props;
var toggle = function(){
instance.setState(state.data)
}
instance.updateState = function(){
instance.setState(Object.assign({},state))
}
if (!props.data){
init(function(ous){
state.data = ous;
instance.setState(state)
});
}
instance.render = function(){
if (!state.data){return <div key = "dummy"></div>}
return <ul key={"ul_"+state.data.id}>
<Tree data={state.data} updateState={instance.updateState} state={state } />
</ul>
}
return instance;
function Tree(props){
var instance = Object.create(React.PureComponent.prototype)
instance.render = function(){
if (!props.data.children || props.data.children.length == 0){
return (
<li key={"li_"+props.data.id}>
<LeafNode data={props.data} updateState = {props.updateState} state={props.state} />
</li>
)
}
return (
<li key={"li_"+props.data.id}><LeafNode data={props.data} updateState = {props.updateState} state={props.state} />
<ul key = {"ul_"+props.data.id} style={props.data.showChildren?{"display":"inline"}:{"display":"none"}}>
{
props.data.children.map(function(child){
return <Tree data={child} key={"tree_"+child.id} updateState = {props.updateState} state={props.state} />
})
}
</ul></li>
)
}
return instance;
function LeafNode(props){
var instance = Object.create(React.PureComponent.prototype)
instance.props = props;
/* instance.shouldComponentUpdate = function(nextProps) {
return (nextProps.data.showChildren !== this.props.data.showChildren);
}
*/
instance.componentDidMount= function(){
console.log("yes")
}
instance.toggle = function(){
props.data.showChildren = !props.data.showChildren;
props.updateState();
}
instance.selected = function(){
props.state.previousSelected.selected = false;
props.data.selected = !props.data.selected;
props.state.previousSelected = props.data;
props.updateState();
props.state.onSelectCallback(Object.assign({},props.data));
}
instance.render = function(){
var toggleImg = "";
if ( props.data.children.length!=0){
toggleImg = props.data.showChildren ?expandIMG:collapseIMG;
}
return (
<div key={"div_"+props.data.id} >
<span key={"span_"+props.data.id} className="toggle" >
<img key={"img_"+props.data.id} width="12" height="12" src={toggleImg} onClick={instance.toggle} />
</span>
<a key={"a_"+props.data.id} onClick = {instance.selected} style={props.data.selected? {color:"yellow"}:{color:"black"}} >{props.data.name}</a>
</div>
)
}
return instance
}
}
}
Thanks
harsh
Have a look at best practices how to create components and component lifecycle at React website. It is a good idea to follow them so it would be easier to identify problems later.
It is also worth looking at react-virtualized components. There are a bunch of components that could be reused including list, grid, tree etc. Also look at their implementation since it is opensource.
Their virtual list component resolved my issue with rendering 500+ items.
Here is an example with 1M+ nodes and good performance. The trick is to use local state and not render the hidden elements.
https://codesandbox.io/s/z6jr6zww4l
Turns out The issue was that I was using the "development" build to check it....i switched to a production library and now it is running not to bad...still not as good as direct DOM but pretty close...although don't know how much it can scale...

Resources