conditional rendering not acting uniformly in reactjs? - json

I have used conditional rendering in the past but for some reason, it's working on one element here but not on another although they are both using the same JSON phrasing and the same type of conditional rendering?
JSON array
//Adobe component data
export default[
{
workName: 'Switch up your "Grub" Motiongraphic',
workDescription: 'This is a motion graphic comparing the environmental and health effects of consuming animal products compared to insects as dietary source of protein.',
workTech: ['Adobe After effects'],
videoAddress: ['https://youtu.be/cloat51hzDY'],
Images: []
},
{
workName: 'Simplyfit concept poster',
workDescription: 'This is a poster developed explaining the concept of Simplyfit, a fitness application developed as part of my final year project.',
workTech: ['Adobe Illustrator'],
videoAddress: [],
Images: ['SFPoster.jpg'],
},
{
workName: 'Switch up your "Grub" Infographic',
workDescription: 'This is an infographic developed explaining the benefits of consuming insects as a source of protein.',
workTech: ['Adobe Illustrator'],
videoAddress: [],
Images: ['insectMotiongraphic.png'],
},
{
workName: 'Crunchy Nut Advert spoof',
workDescription: 'This video was made as a comedic spoof of a Crunchy Nut advert',
workTech: ['Adobe Premier Pro'],
videoAddress: ['https://youtu.be/y8S2RUYrLN8'],
Images: [],
},
{
workName: 'Icons and Designs',
workDescription: 'These are a selection of logos and icons created by me.',
workTech: ['Adobe Premier Pro'],
videoAddress: [],
Images: ['Mylogo.png'],
},
]
The problem I'm having is with the 'videoAdress' and the 'Images' I've tried setting null values undefined etc for them both but for the images the only thing that stops them from rendering is setting the value as [] but this doesn't work for the videoAdress the iframe is still rendered?
React js code
{Data.map((Projects, index) => {
return <div className='Cards'>
<Carousel showThumbs={false} infiniteLoop={true} swipeable={false} emulateTouch={false} showStatus={false} autoPlay={slideShow} dynamicHeight={false}>
{Projects.Images && Projects.Images.map((Image, index) => { return <div className='image-iframeContainer'><img src={require("../assets/Port-images/Adobe/" + Image)} /></div> })}
{Projects.videoAddress && <div className='image-iframeContainer'><ReactPlayer url={Projects.videoAddress} muted={false} controls={false} onPlay={autoplayChange} onPause={autoplayChange} onEnded={autoplayChange} /></div>}
</Carousel>
{Projects.webAddress && <div className='webButton'><LinkIcon onClick= { () => {window.open(Projects.webAddress);}}/></div>}
<h1>{Projects.workName}</h1>
{Projects.workTech.map((Tech, index) => { return <p className='techList'>{Tech}</p> })}
<div className='descriptionContainer'>
<p className='description'>{Projects.workDescription}</p>
</div>
</div>
})}
The function I would like is for the Images and Videos only to render if there is a stored address I'm sure I'm missing something very silly but still, I've been stuck on this for awhile.

Conditional rendering works by casting the condition to a truthy value. For example:
Projects.Images && <Component />
is equal to this:
!!Project.Images && <Component />
Now if you do this for an empty array [], the truthy value is TRUE. This means that <ReactPlayer /> is rendered with [] value and the <img /> is not rendered because [].map() doesn't run on an empty array.
To fix this do this instead:
{Projects.Images && Projects.Images.length > 0 && Projects.Images.map((Image, index) => {
return <div className='image-iframeContainer'>
<img src={require("../assets/Port-images/Adobe/" + Image)} />
</div>
})}
{Projects.videoAddress && Projects.videoAddress.length > 0 &&
<div className='image-iframeContainer'>
<ReactPlayer url={Projects.videoAddress[0]} muted={false} controls={false} onPlay={autoplayChange} onPause={autoplayChange} onEnded={autoplayChange} />
</div>}
I noticed that your videoAddress doesn't use the map() method, but I guess it's a typo

Related

Not being able to test a HTML view including an async variable in Angular

I am writing a simple test for my game component. Just checking if all child components are getting loaded in right. They all seem to work except WordFormComponent. I am guessing this is because I only render it when a async variable has been set to True. This happens only when all variables have been set.
My game.component.html looks like this:
<div class="u-w-full lg:u-w-[70%] u-mx-auto">
<a routerLink="/gameList" class="lg:u-bg-white hover:u-bg-gray-100 u-text-[13px] u-font-medium u-py-1 u-px-3 u-border u-border-gray-300 u-rounded u-flex u-items-center" style="max-width: fit-content">
<mat-icon aria-label="Submit word" class="u-h-[50%] u-text-base">keyboard_backspace</mat-icon>
Games overview
</a>
<div class="lg:u-grid u-grid-cols-3 u-gap-y-[2rem]">
<div class="u-col-span-full u-p-6 u-w-full u-bg-white u-rounded u-mt-1 u-border u-border-gray-200">
<app-final-word-form (onGuessFinalWord)="submitFinalWord($event)"></app-final-word-form>
</div>
<div clas="u-col-span-1">
<app-dashboard (onNextRound)="nextRound($event)"></app-dashboard>
</div>
<div class="u-col-span-2 u-row-span-2 lg:u-ml-[2rem]">
<div *ngIf="dataLoaded | async; then thenBlock else elseBlock"></div>
<ng-template #thenBlock>
<!-- Does not show up in test -->
<app-word-form [game]="game" [word]="word" [gameWord]="gameWord" (onGuessWord)="submitWord($event)"></app-word-form>
</ng-template>
</div>
</div>
</div>
And my test looks like this:
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ GameComponent, FinalWordFormComponent, DashboardComponent, WordFormComponent ],
imports: [ ToastrModule.forRoot() ]
})
.compileComponents();
gameFixture = TestBed.createComponent(GameComponent);
gameComponent = gameFixture.componentInstance;
gameService = TestBed.inject(GameService);
spyOn(gameService, 'createGame').and.returnValue(of({ 'Game': game, 'Gameword': gameWord, 'Word': word, 'Finalword': finalWord }));
gameFixture.detectChanges();
});
fit('should display titles of all child components', waitForAsync(() => {
gameFixture.detectChanges();
expect(gameFixture.nativeElement.querySelector('a').textContent).toContain('Games overview'); // Works
expect(gameFixture.nativeElement.querySelector('p').textContent).toContain('How to win: guess the finalword correctly.'); // Works
expect(gameFixture.nativeElement.querySelector('#wordheader').textContent).toContain('Game word.'); // Failed: Cannot read properties of null (reading 'textContent')
}));
Whenever I log this.dataLoaded when running my test it does return true. So that should not be the problem. It seems like the view does not pick up on it. Anyone knows how to make this work?

How can I get a list of dinos from an Excel Spreadsheet and insert them into specific positions in an HTML page? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed last month.
Improve this question
Hello there I could use some help!
My 14 year old niece has a science project. She has a numbered list of animals - dinosaurs - in an Excel spreadsheet. She asked me to help her, and I could not say no, but I am only a very lame beginner html programmer.
So I need to display the list of animals in the html page in two ways: In numerical order AND in classification order (carnivorous / herbivorous etc) together with a small picture of each animal.
The objective is to show them as in the annexed picture, one page in alphabetical order, and the other page in classification order (feeding habits, land or maritime, etc.)
enter image description here
I know how to create a html block, but then I would have to insert the dinos one by one, and my / her deadline is next week. The list has over two hundred dinos, so I can´t do it by hand. Then it ocurred to me there must be a way to get the data from the Excel and display it in html in the desired order (alphabetical or by characteristic), but I don´t know how to do this. I know this is basic stuff for you, but can anybody PLEASE help me? Thank you in advance!
Oh yes, the html blocks I am making are like this:
32 - TriceratopsTriassicoTriceratops
final result
instructions
open your excel file, and save it.
find the file of the excel in your desktop/folders.
go to the https://tableconvert.com/excel-to-json
drag the excel file to the website input to get the JSON
JSON is the simple key-value format, that we can use in javascript.
so we use this website to translate the excel to an easier format :)
here is the example excel I created (these are fake values because I am not an expert on dinos, but you can use your own values)
after pasting the excel/or dragging it, you will get the results in JSON format.
like you see below
copy the JSON code of the results.
paste the JSON inside a javascript file and assign it to a variable.
// this is an example
const dinoData = [{...}, {...}, {...}, {...}];
in the HTML we will create a wrapper container
<div id="container"></div>
inside the card, we will add the card divs
<div id="container">
<div class="card">...</div>
<div class="card">...</div>
<div class="card">...</div>
</div>
but with javascript.
so we can reuse the HTML by wrapping the code inside the <template>
<template>
<div class="card">...</div>
</template>
inside the cards, we will add some HTML tags that we add them with the info in JS
<template>
<div class="card">
<img class="image" />
<span class="name"></span>
<span class="type"></span>
<span class="weight"></span>
</div>
</template>
now we can get do a loop on the array by using forEach
dinoData.forEach((dino) => {
// do this test first, to check if all the things are correct
console.log(dino);
});
copy the card in javascript using .cloneNode(true)
const card = document.querySelector("#card-template").content;
dinoData.forEach((dino) => {
let newCard = card.cloneNode(true);
}
change the text inside the card by using .textContent
newCard.querySelector(".name").textContent = dino.name;
newCard.querySelector(".type").textContent = dino.type;
newCard.querySelector(".weight").textContent = dino.weight;
newCard.querySelector(".image").src = dino.image;
at the end add the card to the HTML by using .appendChild()
const container = document.querySelector("#container") ?? document.body;
container.appendChild(newCard);
we can implement now sorting code.
alphabetically
function sortAlfabetic(cards) {
const sorted = [...cards].sort((a, b) => {
return `${a.querySelector(".name").textContent}`.localeCompare(
`${b.querySelector(".name").textContent}`,
);
});
sorted.forEach((sortedCard, index) => {
sortedCard.classList.add(`order-${index}`);
});
}
As you see we used .sort() to order them alphabetically
also, another thing, don't use cards parameter directly but use [...cards] instead because this makes a copy and does not change the original one.
also for sorting strings, we used .localCompare() which returns us an index of the string character order.
example:
"a", "a" = 0
"a", "b" = -1
"a", "c" = -2
"c", "a" = 2
"d", "b" = 2
show only carnivorous
function showOnlyCarnivorous(cards) {
const sorted = sortAlfabetic(cards).filter((card) => {
return card.querySelector(".type").textContent == "carnivorous";
});
cards.forEach((card, index) => {
card.classList.add(`hidden`);
});
sorted.forEach((sortedCard, index) => {
sortedCard.classList.remove(`hidden`);
});
}
show only Herbivorous
function showOnlyHerbivorous(cards) {
const sorted = sortAlfabetic(cards).filter((card) => {
return card.querySelector(".type").textContent == "herbivorous";
});
cards.forEach((card, index) => {
card.classList.add(`hidden`);
});
sorted.forEach((sortedCard, index) => {
sortedCard.classList.remove(`hidden`);
});
}
complete code.
I added some styling using a framework but you can delete it if you want and style it like you want.
const dinoData = [{
name: "my dino",
type: "carnivorous",
weight: "1000kg",
image: "https://picsum.photos/559",
},
{
name: "another dino",
type: "herbivorous",
weight: "350kg",
image: "https://picsum.photos/560",
},
{
name: "alex dino",
type: "carnivorous",
weight: "500kg",
image: "https://picsum.photos/561",
},
{
name: "hello dino",
type: "carnivorous",
weight: "10kg",
image: "https://picsum.photos/562",
},
{
name: "minecraft dino",
type: "carnivorous",
weight: "957kg",
image: "https://picsum.photos/563",
},
{
name: "js dino",
type: "herbivorous",
weight: "486kg",
image: "https://picsum.photos/564",
},
{
name: "fish dino",
type: "carnivorous",
weight: "400kg",
image: "https://picsum.photos/565",
},
];
const container = document.querySelector("#container") ?? document.body;
const card = document.querySelector("#card-template").content;
dinoData.forEach((dino) => {
const newCard = card.cloneNode(true);
newCard.querySelector(".name").textContent = dino.name;
newCard.querySelector(".type").textContent = dino.type;
newCard.querySelector(".weight").textContent = dino.weight;
newCard.querySelector(".image").src = dino.image;
container.appendChild(newCard);
});
let cardArray = document.querySelectorAll(".card");
/* alfabetic button */
document.querySelector("#btn-alf").addEventListener("click", () => {
sortAlfabetic(cardArray);
});
/* carnivorous button */
document.querySelector("#btn-carn").addEventListener("click", () => {
showOnlyCarnivorous(cardArray);
});
/* herbivorous button */
document.querySelector("#btn-herb").addEventListener("click", () => {
showOnlyHerbivorous(cardArray);
});
function sortAlfabetic(cards) {
const sorted = [...cards].sort((a, b) => {
return `${a.querySelector(".name").textContent}`.localeCompare(
`${b.querySelector(".name").textContent}`,
);
});
sorted.forEach((sortedCard, index) => {
sortedCard.classList.remove("hidden");
sortedCard.classList.add(`order-${index}`);
});
return sorted;
}
function showOnlyCarnivorous(cards) {
const sorted = sortAlfabetic(cards).filter((card) => {
return card.querySelector(".type").textContent == "carnivorous";
});
cards.forEach((card, index) => {
card.classList.add(`hidden`);
});
sorted.forEach((sortedCard, index) => {
sortedCard.classList.remove(`hidden`);
});
}
function showOnlyHerbivorous(cards) {
const sorted = sortAlfabetic(cards).filter((card) => {
return card.querySelector(".type").textContent == "herbivorous";
});
cards.forEach((card, index) => {
card.classList.add(`hidden`);
});
sorted.forEach((sortedCard, index) => {
sortedCard.classList.remove(`hidden`);
});
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50">
<div class="w-full p-4 grid md:grid-cols-2 gap-4">
<button id="btn-alf" class="bg-blue-500 p-4 w-full rounded-md text-lg font-bold text-white hover:bg-blue-700 transition">🔤 sort by alfabeth</button>
<div class="flex gap-4">
<button id="btn-carn" class="bg-blue-500 p-4 w-full rounded-md text-lg font-bold text-white hover:bg-blue-700 transition">🍖 carnivorous</button>
<button id="btn-herb" class="bg-blue-500 p-4 w-full rounded-md text-lg font-bold text-white hover:bg-blue-700 transition">🍏 herbivorous</button>
</div>
</div>
<div id="container" class="grid sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-4 p-4"></div>
<template id="card-template">
<div
class="card bg-white grid shadow-lg rounded-md p-4 transition hover:-translate-y-1"
>
<img class="image w-full h-auto rounded-md" />
<span class="name">text1</span>
<span class="type">text2</span>
<span class="weight">text3</span>
</div>
</template>
<script src="./script.js"></script>
</body>
</html>

ReactJs App's mapped list of buttons from Json file only have the last value of json array

I am making list of button that will lead to different YouTube videos on ReactJs page. The issue is that when I map the links from json file (which contains video links), all the buttons get the last link of the mapped array. I need a way that all the rendered buttons will get their respective links. My code is below, I am using react ModalVideo to show my YouTube video.
<ul className="list-unstyled">
{datalist.lectures.map((value, index) => {
return (
<li key={index}>
<ModalVideo channel='youtube' autoplay isOpen={isOpen} videoId={value} onClose={() => setOpen(false)} />
<button className="play-icon" onClick={()=> setOpen(true)}> <i className="las la-play"></i> Lecture: {index+1}</button>
</li>
)})}
</ul>
All the links are coming from Json file where the code looks like this
"lectures": [
"BT4YihNXiYw",
"NSFLhnv2pQI",
"sEPxqpFZit8",
"fU8QhoUJ_sE",
"r3XFYOtvUng",
"MZAkMddxhpg"
]
I think the problem I have right now is that after the page render and mapping ends it only remembers what the last link is store in value, because of that no matter which button i click it shows me the last value of the mapped array
Just some quick ideas looking at the minimal snippets available.
let's not to render multiple ModalVideo component like above, move it out from the map.
Use another state to keep track the change of the youtube videos' ID.
For example
const [isOpen, setOpen] = useState(false);
const [videoId, setVideoId] = useState(null);
const playVideo = (vid) => {
setOpen(true);
setVideoId(vid);
};
return (
<div>
<ModalVideo
channel='youtube'
autoplay
isOpen={isOpen}
videoId={videoId}
onClose={() => setOpen(false)}
/>
<ul className="list-unstyled">
{
datalist.lectures.map((value, index) => {
return (
<li key={index}>
<button className="play-icon" onClick={() => playVideo(value)}>
<i className="las la-play"></i>
Lecture: {index + 1}
</button>
</li>
)
})
}
</ul>
</div>
);

React Render HTML object from JSON object

I have to render html object Array in React JS
Can anyone guide me how to use renderHTML function.
output of the object is something like this:
"
const items = this.state.Data.map(item => (
<div key={item._id}>{renderHTML("{item.albComEn}")}</div>
another variation i tried
const items = this.state.Data.map(item => (
<div key={item._id}>{renderHTML("item.albComEn")}</div>
));
output i get => "item.albComEn"
or
{item.albComEn}
You can try with template strings. More info
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
const items = this.state.Data.map(item => (
<div key={item._id}>{renderHTML(`${item.albComEn}`)}</div>
You can also use short syntax of React Fragments i.e. '<> </>'. Use these to bracket to write the html code. When rendered the html code will successfully compiled.
Example:
const service = [
{
htmlCode: <>
<div>
<h2>Application Screening</h2>
<br />
<br />
What you can expect from us:<br />
- Your resume will be written by a team of seasoned experts<br />
- They will make sure that your Resume presents your strong points,
achievements & key skills in a recruiter-friendly format.<br />
</div>
</>
},
]
Use inside render method as
...
render(
<div>
{service[0].htmlCode}
<div>
)
}

angular.js passing function into an object

I think it may be useful if I show the wider scope :) below is my html:
<div class="resSection2 rel">
<div class="proceed rel">
<div ng-repeat="record in records">
<div class="rel fptsans {{record.className()}}">Balkans<i class="icon-check icon-2x posIco"></i></div>
</div>
</div>
</div>
Key factor here is the {{record.className()}} binding which depending on its value determines the behaviour of the record, whether it gets a proper styling or not. as you can see it is a reference to a function. And here is the JS:
var antroApp = angular.module('antroApp', []);
$scope.records = [
{id:0,
className: $scope.probieren,
recordName:$scope.alpeic.length
},
{id:1,
className: $scope.probieren,
recordName:$scope.alpeic.length
}
];
$scope.probieren = function(){
if($scope.records.recordName > 10){
$scope.records.className == 'special'
}
else{
$scope.records.className == 'normal'
}
}
}
antroApp.controller('dialogWindows', dialogWindows);
When I set up the className statically ("special" or "normal") it renders perfectly
but when it comes to a function, it all just gets stuck. really feel helpless about this. any tips appreciated.
You've set it up fine except they have to be defined in the other order, but in your dom when you use it call a function like this:
<div data-ng-repeat="record in records">
<div class="{{ record.className() }}">{{ record.recordName }}</div>
</div>
There's a few more issues with how you're referencing $scope.records.className instead of some index of the array for records but my answer should answer the specific question you have.