How to add a skeleton load for generated items - reactjs

I'm working on a slider of items similar to Netflix but with movies, using the info recovered from the tmdb API.
I would like to add a skeleton load for each dynamically generated item using map to improve the user experience.
First of all for the skeleton loading I got a lot of inspiration from this code available on pen code that I adapted afterwards.
https://codepen.io/mxbck/pen/EvmLVp
I try a solution first here but it does not work, I voluntarily remove code that did not relate to my problem for clarity:
Slider component
import React, {PureComponent , Component } from "react";
import style from './Caroussel.css';
import MovieItem from "../components/MovieItem";
import MovieItemContainer from '../components/MovieItem/MovieItemContainer';
class Slider extends Component {
constructor(props){
super(props);
this.handleOnLeftArrowClick = this.leftArrowClick.bind(this);
this.handleOnRightArrowClick = this.rightArrowClick.bind(this);
this.state = {
sliderItems: [],
}
}
componentDidMount() {
this.updateSliderState();
this.setState({
totalItems: this.props.movieList.length,
sliderItem: this.props.movieList
})
}
componentWillMount(){
if(typeof(window) !== 'undefined') {
$(window).on('resize', this.updateSliderState.bind(this))
}
}
render(){
const { sliderItem} = this.state;
const sliderClass = cx ({
sliderMask:true,
moving
})
return(
<div className="wrapper">
<div className={style.slider}>
<div className={sliderClass} ref="slider">
{this.state.sliderItem ?
sliderItem.map((element, index) => (
<MovieItemContainer>
<MovieItem
key={index}
title={element.title}
id={element.id}
release_date={element.release_date}
url={element.backdrop_path}
/>
</MovieItemContainer>
))
:
sliderItem.map((element, index) => {
null
})
}
</div>
{
click &&
<div className={style.leftArrow} ref="leftArrow">
<IosArrowBack onClick{this.handleOnLeftArrowClick} color="black" />
</div>
}
<div className={style.rightArrow} ref="rightArrow">
<IosArrowForward onClick={this.handleOnRightArrowClick} color="black" />
</div>
</div>
</div>
);
}
}
export default Slider;
MovieItem Container component
import React, {Component} from "react";
import '../../../style/card.scss';
class MovieItemContainer extends React.Component {
render() {
return (
<div className="card">
{this.props.children}
</div>
);
}
}
export default MovieItemContainer;
MovieItem component
import React from 'react';
import Moment from 'react-moment';
import style from './MovieItem.css';
import {Link} from 'react-router';
const MovieItem = ({ url, title, release_date, id }) => {
let link ='https://image.tmdb.org/t/p/w300/'+url;
const text_truncate = (str, length, ending) => {
if (length == null) {
length = 100;
}
if (ending == null) {
ending = '...';
}
if (str.length > length) {
return str.substring(0, length - ending.length) + ending;
} else {
return str;
}
};
return (
<div className={style.sliderItem}>
<div style={{ borderBottomLeftRadius: 8, borderBottomRightRadius:8 }} className={style.sliderItemInner}>
<img style={{ borderRadius: 8 }} className={style.cover} src={link} />
<div className={style.shadow}></div>
<div className={style.titles}>
<span className={style.title}>
<Link className={style.title} to={`film/${id}`}>
{text_truncate(title,18)}
</Link>
</span>
<span className={style.release_date}>
<Moment format="YYYY">
{release_date}
</Moment>
</span>
</div>
</div>
</div>
)
}
export default MovieItem;
The MovieItem component only makes the item a movie with a background image, the title of the movie and the year of release.
In the example above, the loading is done for only one card, I would just adapt this code for each of the generated items (10 items).
I thank you in advance for your help and your answers.

Related

How to load a specific photo with dynamic URL with react.js

I have a component where a list of pictures is rendered and it works perfectly fine :
import { Component} from 'react'
import Header from '../Home/Header'
import Footer from '../Home/Footer'
import PhotoItems from './objet'
class Photos1930 extends Component {
render() {
return (
<div>
<Header />
<h2 className='titre bloc'>Photos 1930</h2>
<div className='bloc bloc__photo'>
{PhotoItems.map((val, key) => {
let id = val.id
let url = val.url
let lienImage = "/galerie/:" + (val.id)
return <div key={id}>
<a href={lienImage}>
<img className='photo' alt='Photo Charles-Quint' src={url}></img>
</a>
</div>
})}
</div>
<Footer />
</div>
)
}
}
export default Photos1930
I want to create an other component where i can load a specific picture when user click on a picture from the precedent list. I use the same logic but for some reason the picture doesn't load. I don't have any errors in my console but on my page i just have the standard icon for image with my alt.
All the pictures are on public folder.
I just don't understand why is it working on one component but not on the other one.
import { Component } from 'react'
import Header from '../Home/Header'
import Footer from '../Home/Footer'
import PhotoItems from './objet'
const url = window.location.pathname
const justId = parseInt((url.split(':')[1]))
function specificId(photo) {
return photo.id === (justId)
}
let justUrl = (PhotoItems.find(specificId).url)
console.log(justUrl)
class PickPhoto extends Component {
render() {
return (
<div>
<Header />
<div>
<h1>{justId}</h1>
<img className="bigPhoto" alt="Charles-Quint" src={justUrl}></img>
</div>
<Footer />
</div>
)
}
}
export default PickPhoto
EDIT1 : Here's my github repo : https://github.com/FranMori/CharlesQuint
and here's my netlify link : https://stoic-bohr-810e13.netlify.app/
You can click on "Galerie Photos" and then click on any picture to see the problem.
in your repo, this.justUrl is undefined. You need to add justUrl in the component's state and update it dynamically inside componentDidMount like below. I also added a / in src={/${this.state.justUrl}}
import { Component } from 'react'
import Header from '../Home/Header'
import Footer from '../Home/Footer'
import PhotoItems from './objet'
class PickPhoto extends Component {
constructor() {
super()
this.state = { justUrl: "" };
}
componentDidMount() {
const url = window.location.pathname
const justId = parseInt((url.split(':')[1]))
function specificId(photo) {
return photo.id === justId
}
let justUrl = (PhotoItems.find(specificId).url)
console.log(justUrl)
this.setState({justUrl})
}
render() {
return (
<div>
<Header />
<div>
<h1>{this.justId}</h1>
<img className="bigPhoto" alt="Charles-Quint" src={`/${this.state.justUrl}`}></img>
</div>
<Footer />
</div>
)
}
}
export default PickPhoto

React: Onclick Event new component is not returned

I am new to React. I am trying to display new component in the content on click of menu from Navbar. But the return of component on click function doesnot work. So return doesnot work. Here are my files. Click function "getItemsData" works but the component is not returned. I am trying to create a application with restaurant menu and it relevant content
App.jsx
import React from 'react';
import "./style.css";
import Slider from "./carousel.js"
class App extends React.Component{
render(){
return(
<div>
<Header/>
</div>
);
}
}
class Header extends React.Component{
constructor(props){
super(props);
this.state = {
sections: [],
menu: []
};
}
componentDidMount(){
fetch('http://localhost:3001/api/sections')
.then(res => res.json())
.then(sections => this.setState({ 'sections': sections }))
fetch('http://localhost:3001/api/menu')
.then(res => res.json())
.then(menu => this.setState({ 'menu': menu }))
}
render(){
return(
<div id="header-bar">
<div id="header-logo">
<img src={require('./images/1200px-Burger_King_Logo.png')} alt="Logo" id="logo-class"></img>
</div>
<Slider sections={this.state.sections} menu={this.state.menu} />
</div>
);
}
}
export default App;
carousel.js
import React , { Component } from 'react';
import contentCards from "./contentcards.js";
const Slider = (data) => {
console.log(data)
var Menuoptions = data.menu.options
var Sectionsref = data.sections
const getItemsData = (sectionData) => {
sessionStorage.setItem("sectionData" , JSON.stringify(sectionData));
return <contentCards />;
}
return(
<div className="sectionNavBar">
{(()=> {
if (Menuoptions !== undefined && Sectionsref !== undefined) {
if (Menuoptions.length > 0 && Sectionsref.length > 0){
let rowmenu = []
for(let i=0; i< Menuoptions.length ; i++){
for(let j=0; j<Sectionsref.length; j++){
if(Menuoptions[i]._ref === Sectionsref[j]._id){
// console.log(Menuoptions[i]._ref+ "==" +Sectionsref[j]._id)
let imageId = Sectionsref[j].carouselImage.asset._ref;
imageId = imageId.slice(6)
imageId = imageId.replace("-png", ".png")
//console.log(Sectionsref[j])
rowmenu.push(<div key={j} className="listNavBar" onClick = {() => getItemsData(Sectionsref[j])}> <img src={window.location.origin + '/images/'+imageId} className= "navBar-image" /> <br/>{Sectionsref[j].name.en} </div>)
}
}
}
return rowmenu
}
}
})()}
</div>
)
}
}export default Slider
contentCards.js
import React , { Component } from 'react';
class contentCards extends React.Component{
render(){
return (
<div className = "cardColumn"> Menu1 </div>
)
}
}
export default contentCards
Custom components names must be uppercased or React will treat them as DOM elements.
React treats components starting with lowercase letters as DOM tags.
// not <contentCards />
<ContentCards />

SPFx React FileViewer for Sharepoint Custom List

I am new to SPFx and React. The requirement is to read a Sharepoint Custom List and render the list columns with custom look and feel. This is achieved and read all the attachments (more than one, for a single list item).
How to open all the attachments with React FileViewer (more than one) in browser?
public render(): React.ReactElement<ICirDetailsProps> {
this._renderListAsync();
return(
<div className="${ styles.cirDetails }">
<div className="${ styles.container }">Circulars<br/>
<div id="cirDetails" className="${ styles.details}"></div>
</div>
</div>
);
}
attachments.forEach((afile: any) => {
let fileUrl = hostURL + afile.ServerRelativeUrl;
let fileExt = this.getFileExtension(afile.ServerRelativeUrl);
attFiles += `<FileViewer fileType='${fileExt}' filePath='${afile.ServerRelativeUrl}' /><br/>`;
});
const listContainer: Element = document.querySelector("#cirDetails") as HTMLElement;
listContainer.innerHTML=html;
It shows no error, but the attachments are not opened within browser. I could see the React FileViewer tags are rendered as HTML Content using F12. What is the mistake am I doing here?
Sample demo:
import * as React from 'react';
import styles from './MyReactFileViewer.module.scss';
import { IMyReactFileViewerProps } from './IMyReactFileViewerProps';
import { escape } from '#microsoft/sp-lodash-subset';
import FileViewer from 'react-file-viewer';
import * as CSS from 'csstype';
import { sp } from "#pnp/sp";
import { IAttachmentInfo } from "#pnp/sp/attachments";
import { IItem } from "#pnp/sp/items/types";
import "#pnp/sp/webs";
import "#pnp/sp/lists/web";
import "#pnp/sp/items";
import "#pnp/sp/attachments";
interface IPnpstate {
attachments: any[]
}
export default class MyReactFileViewer extends React.Component<IMyReactFileViewerProps, IPnpstate> {
constructor(props: IMyReactFileViewerProps, state: IPnpstate) {
super(props);
this.state = {
attachments: []
};
}
public componentDidMount() {
let item = sp.web.lists.getByTitle("MyList").items.getById(18);
// get all the attachments
item.attachmentFiles.get().then((files:IAttachmentInfo[]) => {
var attachs=[];
files.map(file=>{
var fileType=file.FileName.split('.').pop();
var fileUrl=file.ServerRelativeUrl;
attachs.push({"fileType":fileType,"fileUrl":fileUrl});
})
console.log(attachs);
this.setState({attachments:attachs});
});
}
public render(): React.ReactElement<IMyReactFileViewerProps> {
return (
<div className={styles.myReactFileViewer}>
<div className={styles.container}>
<div className={styles.row}>
<div className={styles.column}>
<span className={styles.title}>Welcome to SharePoint!</span>
<p className={styles.subTitle}>Customize SharePoint experiences using Web Parts.</p>
<p className={styles.description}>{escape(this.props.description)}</p>
<a href="https://aka.ms/spfx" className={styles.button}>
<span className={styles.label}>Learn more</span>
</a>
{(this.state.attachments || []).map((item, index) => (
<div key={item.ID}>
<FileViewer
fileType={item.fileType}
filePath={item.fileUrl}
/>
</div>
))}
</div>
</div>
</div>
</div>
);
}
}
I used IFrame tag, based on the file types, the iframe renders differently.

Changes are not reflected in UI in react

When a user like a post, then the count is incremented in ui. Bu when a user remove the like then count ui is not changing though it is changed in server.How to solve it?
When a user like a post, then the count is incremented in ui. Bu when a user remove the like then count ui is not changing though it is changed in server.How to solve it?
import React from 'react';
import moment from 'moment';
import CommentForm from './CommentForm';
import CommentList from './CommentList';
import CommentModal from './CommentModal';
import { connect } from 'react-redux';
import { startAddComment, startAddLike, startRemoveLike } from
'../actions/post';
import { Link } from 'react-router-dom';
import UserInfo from './UserInfo';
class PostListItem extends React.Component{
constructor(props){
super(props);
this.state = {
isliked: false,
commentM: undefined,
likes: this.props.likes
}
}
componentDidMount(){
if(this.props.likes.includes(this.props.user.uid)){
this.setState(() => ({isliked:true}));
}
}
onClickedLike = () =>{
if(this.state.isliked === false){
this.props.dispatch(startAddLike(this.props._id));
this.setState(()=>{
console.log(this.props);
return{
isliked:true
}
});
} else{
this.props.dispatch(startRemoveLike(this.props._id));
this.setState(()=>({isliked:false}));
}
}
openModal = () =>{
this.setState({commentM: this.props.comments});
}
closeModal = () =>{
this.setState(({commentM: undefined}));
}
render(){
return(
<div className="post">
<div className="post__header">
<UserInfo user={this.props.author}
time={this.props.createdAt}/>
{
(this.props.user.uid === this.props.author.uid)?
<Link to={`/edit/${this.props._id}`}
className="post__edit">
Edit</Link>:''
}
{/* <p className="post__time">
{moment(this.props.createdAt).fromNow()}</p> */}
</div>
<div className="post__caption">{this.props.caption}</div>
<img src={this.props.content} className="post__content"/>
<div className="post__extra">
<div className="post__lc">
<button className="post__button"
onClick={this.onClickedLike}
>{this.state.isliked? <i className="fas fa-futbol"></i>
: <i className="far fa-futbol"></i>}
</button>
<button className="post__button"
onClick={this.openModal}><i className="far fa-
comment"></i>
</button>
</div>
{this.props.likes.length !== 0 && <p className="post__like">
{this.props.likes.length} {this.props.likes.length === 1? 'like':'likes'}
</p>} // likes count is not changing while removing the like(ui only)
<CommentModal
commentM={this.state.commentM}
closeModal={this.closeModal}/>
<CommentForm onSubmit={(comment) => {
this.props.dispatch(startAddComment(this.props._id,
comment));
}} />
{this.props.comments && <CommentList comments=
{this.props.comments}/>}
</div>
</div>
);
}
};
const mapStateToProps = (state) => {
return{
user: state.auth
}
}
export default connect(mapStateToProps)(PostListItem);

React: componentWillReceiveProps is not triggered when props change

I am new to react and its life-cycles, so currently following some tutorials and I am stuck with a problem that componentWillReceiveProps life-cycle method is not working the way I expect.
The thing is that in App component I am passing prop isActive to Card component, and it is changing its value when input checkbox checked/unchecked, so I expect componentWillReceiveProps life-cycle method to be triggered. However, this is not working at all. Maybe anything you can advice me on that case? As well as I am open for the best practice advice. Thank you for your time in advance.
Components code:
//App.js
import React, {Component} from 'react';
import Ticker from "./Ticker/Ticker";
import currencies from './currencies';
import Card from "./Card/Card";
import uuid from "uuid";
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
class App extends Component {
state = {
activePairs: []
};
handleCheckbox = (rateId, event) => {
const {checked} = event.target;
this.setState(({activePairs}) => {
let pairs = [...activePairs];
if (checked) {
if (!pairs.includes(rateId)) {
pairs.push(rateId);
}
} else {
let index = pairs.findIndex(rate => rate === rateId);
pairs.splice(index, 1);
}
return {
activePairs: pairs
};
});
};
render() {
return (
<div className="App">
<Ticker handleCheckbox={this.handleCheckbox.bind(this)}/>
<div className="container">
<div className="row">
{currencies.map(pair => <Card key={"card-" + uuid.v4()} currency={pair}
isActive={this.state.activePairs.includes(pair)}/>)}
</div>
</div>
</div>
);
}
}
export default App;
//Card.js
import React, {Component} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import '../App.css';
class Card extends Component {
state = {
value: 0
};
componentWillReceiveProps(nextProp) {
console.log(nextProp);
if (!this.props.isActive && nextProp.isActive) {
this.updateExchangeRate();
this.interval = setInterval(this.updateExchangeRate, 3000);
} else if (this.props.isActive && !nextProp.isActive) {
clearInterval(this.interval);
this.setState({
value: 0
})
}
}
updateExchangeRate = () => {
return fetch(`https://www.cbr-xml-daily.ru/daily_json.js`).then(r => r.json()).then(res => {
let exRate = res["Valute"][this.props.currency.toUpperCase()]['Value'] + (Math.random() * (0.99 - 0.01 + 1) + 0.01);
let maximum = exRate + 5.00;
let minimum = exRate - 5.00;
this.setState({
value: (Math.floor(Math.random() * (maximum - minimum + 1)) + minimum).toFixed(2)
});
});
};
render() {
return (
<div className="col-md-3 col-sm-6 mb-3">
<div className="card text-center text-white bg-info">
<div className="card-header bg-info">{this.props.currency.toUpperCase() + " to RUB"}</div>
<div className="card-body">
<h5 className="card-title">Current exchange pair:</h5>
<p className="card-text">{this.state.value}</p>
</div>
</div>
</div>
);
}
}
export default Card;
//Ticker.js
import React, {Component} from 'react';
import currencies from "../currencies";
export default class Ticker extends Component {
state = {
currencies: currencies
};
render() {
return (
<div id="wrapper">
<div id="sidebar-wrapper">
<ul id="sidebar-ul" className="sidebar-nav">
{this.state.currencies.map(currency => {
return <li key={currency}>
<input id={currency + "-input"} type="checkbox" onChange=
{
this.props.handleCheckbox.bind(this, currency)
}/>
<label htmlFor={currency + "-input"} className="text-info"
role="button"> {currency.toUpperCase()} rate</label>
</li>
})}
</ul>
</div>
</div>
);
}
}
//currencies.js
export default ["aud", "azn", "gbp", "bgn", "usd", "eur"];
Well, I finally found what was causing the problem here. In App component I was using uuid module as a key prop for every Card component, so because of it that was always rendering a new Card component each time isActive props were updating.
Solution: use a constant id instead as a key prop.

Resources