Need to slice cards after page rendering react class component - reactjs

I have a page where i need to show 9 cards initially and on click of show more i need to show more cards until API call has data. I used slice method and I am able to achieve same thing but because of slice SEO performance is impacted, these divs r not crawled by google, so now the problem is I need to render all div cards intially on the page and need to show only 9 by some other method. I have no clue how to achieve this. Can anyone please help me with this using class component in react.
import React from 'react';
import WebinarList from './WebinarList';
import Webinars from './Webinars';
export default class WebinarData extends React.Component{
constructor(...props) {
super(...props);
this.state = {
isVisible: 9,
setFlag: true,
totalValue:0
};
this.showmoreWebinar = this.showmoreWebinar.bind(this);
this.mathCal = this.mathCal.bind(this);
}
componentWillReceiveProps(nextProps) {
const webinarList = this.props.webinarListInfo ?
this.props.webinarListInfo: null;
const total = webinarList? webinarList.length : 0;
if(this.state.isVisible >= total){
this.setState((old)=>{
return {
isVisible: 9,
}
})
}
}
showmoreWebinar(offset, total){
this.setState((old)=>{
return {
isVisible: old.isVisible + offset,
}
})
}
mathCal(n){
if(n > 0)
return Math.ceil(n/3.0) * 3;
else if( n < 0)
return Math.floor(n/3.0) * 3;
else
return 3;
}
render(){
// console.log("in web");
// console.log(this.props.webinarListInfo);
const webinarList = this.props.webinarListInfo ?
this.props.webinarListInfo: null;
const webinarBanner = this.props.webinarBanner &&
this.props.webinarBanner.webinarLandingItems ?
this.props.webinarBanner.webinarLandingItems[0]: null;
let offset = webinarBanner && webinarBanner.PageSize?
webinarBanner.PageSize : 9;
offset= this.mathCal(offset);
const showMore = webinarBanner && webinarBanner.LoadMoreButtonText?
webinarBanner.LoadMoreButtonText: "Show more" ;
const list = webinarList ? webinarList.map((item, index) => (
<Webinars info={item} index={index} labelInfo=
{this.props.webinarList}/>
)
) : null;
const total = webinarList? webinarList.length : 0;
return(
<div>
<div className="webinarData">
<WebinarList labelInfo={this.props.webinarList} totalList=
{webinarList}/>
{list}
</div>
<div className="l-show-more-btn">
{webinarBanner ? (<button className="c-button showmore-webinar
is-desktop is-medium is-upper-case"
style={{ display: this.state.isVisible >= total ?
'none' : 'block' }}
onClick={() => this.showmoreWebinar(offset, total)} >
{showMore}
</button>) : null}
</div>
{total <= 0 ? (<div className=""><p style={{textAlign:'center'}}>
{this.props.webinarList && this.props.webinarList.ResultNotFound?
this.props.webinarList.ResultNotFound: null}</p></div>):null}
</div>
);
}
}
Now i need to remove this slice method so everything is displayed on page load then need to slice it(basically show and hide kind of thing in UI).Following is the structure how it should look like in UI:

Related

How to actually do SEO-friendly pagination the right way

I want to build pagination the proper way that is SEO-friendly and liked by google. From doing my research, it seems that there is a lot of incorrect content out there that, while it works, is poor for SEO. I assume that, because of my SEO-friendly requirement, I cannot use any state to manage the next/prev page links and so on, and I want the page to use URL params to determine the page number (e.g. /something?page=2). It sounds like /something/page/2 might also be acceptable, but I prefer URL params, especially as Facebook seems to use it so it must be a good way of doing so.
The problem is that my solution seems to have to use <a/> tags, which obviously just inefficiently reload the entire page when clicked on. When I try to replace the a tags with Link tags, the pagination stops working, and the component does not re-render when clicking on the prev/next links (but strangely, according to the react dev tools, re-renders all of the other components not in this demo, so something key must be wrong).
Here is my paginator component:
interface PaginatorProps {
currentPage: number;
itemCount: number;
itemsPerPage?: number;
path: string;
}
export const Paginator: FC<PaginatorProps> = ({
currentPage,
itemCount,
itemsPerPage,
path,
}) => {
const totalPages: number = Math.ceil(itemCount / itemsPerPage);
const disablePrev: boolean = currentPage <= 1;
const disableNext: boolean = currentPage >= totalPages; // is last page (or 'above')
if (totalPages <= 1 || !totalPages || !itemsPerPage || currentPage > totalPages) {
return null;
}
let next: string = path,
prev: string = path;
if (currentPage + 1 <= itemCount) {
next = `${path}?page=${currentPage + 1}`;
}
if (currentPage - 1 >= 1) {
prev = `${path}?page=${currentPage - 1}`;
}
return (
<div>
<Link to={prev} className={`${disablePrev ? 'disabled' : ''}`}>
Prev
</Link>
<span className={'paginationStyles.currentPage'}>
Page {currentPage} of {totalPages}
</span>
<Link to={next} className={`${disableNext ? 'disabled' : ''}`}>
Next
</Link>
</div>
);
};
And an example component that uses it:
export const DataList = () => {
const itemsPerPage: number = 3;
const urlParams: URLSearchParams = new URLSearchParams(window.location.search);
const currentPage: number = Number(urlParams.get('page')) || 1;
const skip: number = (currentPage - 1) * itemsPerPage;
const { dataCount, data, loading, error } = useGetDataList({ limit: itemsPerPage, skip });
if (loading) return <Loading />;
if (error) return <Error msg={error} />;
if (!data?.length) return <p>No data found...</p>;
return (
<>
<ul>
{data.map(({ email }) => (
<li key={email}>{email}</li>
))}
</ul>
<Paginator
currentPage={currentPage}
itemCount={dataCount}
itemsPerPage={itemsPerPage}
path='/user/profile/affiliate/data-list'
/>
</>
);
};
Does anyone know how to make it re-render the component and paginate properly? Or is this the wrong approach per se?
Replace pagination's buttons to a link, so maybe it will work)

Applying state change to specific index of an array in React

Yo there! Back at it again with a noob question!
So I'm fetching data from an API to render a quizz app and I'm struggling with a simple(I think) function :
I have an array containing 4 answers. This array renders 4 divs (so my answers can each have an individual div). I'd like to change the color of the clicked div so I can verify if the clicked div is the good answer later on.
Problem is when I click, the whole array of answers (the 4 divs) are all changing color.
How can I achieve that?
I've done something like that to the divs I'm rendering :
const [on, setOn] = React.useState(false);
function toggle() {
setOn((prevOn) => !prevOn);
}
const styles = {
backgroundColor: on ? "#D6DBF5" : "transparent",
};
I'll provide the whole code of the component and the API link I'm using at the end of the post so if needed you can see how I render the whole thing.
Maybe it's cause the API lacks an "on" value for its objects? I've tried to assign a boolean value to each of the items but I couldn't seem to make it work.
Thanks in advance for your help!
The whole component :
import React from "react";
import { useRef } from "react";
export default function Quizz(props) {
const [on, setOn] = React.useState(false);
function toggle() {
setOn((prevOn) => !prevOn);
}
const styles = {
backgroundColor: on ? "#D6DBF5" : "transparent",
};
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
let answers = props.incorrect_answers;
const ref = useRef(false);
if (!ref.current) {
answers.push(props.correct_answer);
shuffleArray(answers);
ref.current = true;
}
const cards = answers.map((answer, key) => (
<div key={key} className="individuals" onClick={toggle} style={styles}>
{answer}
</div>
));
console.log(answers);
console.log(props.correct_answer);
return (
<div className="questions">
<div>
<h2>{props.question}</h2>
</div>
<div className="individuals__container">{cards}</div>
<hr />
</div>
);
}
The API link I'm using : "https://opentdb.com/api.php?amount=5&category=27&type=multiple"
Since your answers are unique in every quizz, you can use them as id, and instead of keeping a boolean value in the state, you can keep the selected answer in the state, and when you want render your JSX you can check the state is the same as current answer or not, if yes then you can change it's background like this:
function Quizz(props) {
const [activeAnswer, setActiveAnswer] = React.useState('');
function toggle(answer) {
setActiveAnswer(answer);
}
...
const cards = answers.map((answer, key) => (
<div key={key}
className="individuals"
onClick={()=> toggle(answer)}
style={{background: answer == activeAnswer ? "#D6DBF5" : "transparent" }}>
{answer}
</div>
));
...
}

How to properly update the screen based on state variables

I'm new to react and I'm learning how to fetch data from an api once the user clicks on a button. Somehow, I've gotten everything to work, but I don't think I'm using the library properly.
What I've come up with:
class App extends React.Component {
state = {
recipe: null,
ingredients: null
}
processIngredients(data) {
const prerequisites = [];
const randomMeal = data.meals[0];
for(var i = 1; i <= 20; i++){
if(randomMeal['strIngredient' + i]){
prerequisites.push({
name: randomMeal['strIngredient' + i],
amount: randomMeal['strMeasure' + i]
})
}
}
this.setState({
recipe: data,
ingredients: prerequisites,
})
console.log(prerequisites[0].name)
}
getRecipes = () => {
axios.get("https://www.themealdb.com/api/json/v1/1/random.php").then(
(response) => {
this.processIngredients(response.data);
}
)
}
render() {
return (
<div className="App">
<h1>Feeling hungry?</h1>
<h2>Get a meal by clicking below</h2>
<button className="button" onClick={this.getRecipes}>Click me!</button>
{this.state.recipe ? <Recipe food={this.state.recipe}
materials={this.state.ingredients} /> : <div/>}
</div>
);
}
}
The way I'm checking the value of state.recipe in render() and invoking the Recipe component, is it correct? Or does it seem like hacked together code? If I'm doing it wrong, what is the proper way of doing it?
It's really a minor nit, but in this case you can use an inline && logical operator since there's nothing to render for the "false" case:
{this.state.recipe && <Recipe food={this.state.recipe} materials={this.state.ingredients} />}
Checkout https://reactjs.org/docs/conditional-rendering.html for more info.

TypeError: Cannot read property 'addEventListener' of undefined

I got this Error message: "TypeError: Cannot read property 'addEventListener' of undefined" and I don't know why. I think it has to do with the fact that variable "el" loads the first time with the value of zero but I am not sure and I dont know how to solve that issue.
EDIT:
I updated the code with the help of the answers. Now I got a new error message "TypeError: Cannot set property 'value' of undefined" https://jsfiddle.net/Van_Gram/4vofz6jh/7/
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { addToCart } from './actions/cartActions'
import StackGrid from "react-stack-grid"
import Tilt from 'react-tilt'
// normally these would be `import` statements bundled by webpack
const { ReactDOM } = window;
class Home extends Component{
constructor(props) {
super(props);
this.unlockStartHandler = this.unlockStartHandler.bind(this);
this.unlockEndHandler = this.unlockEndHandler.bind(this);
this.animateHandler = this.animateHandler.bind(this);
this.successHandler = this.successHandler.bind(this);
this.maxValue = 150;
this.speed = 12;
this.currValue = 0;
this.rafID = null;
}
/*componentDidMount() {
// bind events
this.inputRange.addEventListener('mousedown', this.unlockStartHandler, false);
this.inputRange.addEventListener('mousestart', this.unlockStartHandler, false);
this.inputRange.addEventListener('mouseup', this.unlockEndHandler, false);
this.inputRange.addEventListener('touchend', this.unlockEndHandler, false);
}*/
// listen for unlock
unlockStartHandler(e) {
// clear raf if trying again
window.cancelAnimationFrame(this.rafID);
// set to desired value
this.currValue = +e.target.value;
}
unlockEndHandler(e) {
// store current value
this.currValue = +e.target.value;
// determine if we have reached success or not
if(this.currValue >= this.maxValue) {
this.successHandler();
this.sendData();
}
else {
this.rafID = window.requestAnimationFrame(this.animateHandler);
}
}
// handle range animation
animateHandler() {
// update input range
this.inputRange.value = this.currValue;
// determine if we need to continue
if(this.currValue > -1) {
window.requestAnimationFrame(this.animateHandler);
}
// decrement value
this.currValue = this.currValue - this.speed;
}
// handle successful unlock
successHandler = (prevState) => {
// alert('unlocked');
// reset input range
this.inputRange.value = 0;
};
sendData = () => {
this.props.parentCallback(null);
}
callbackFunction = (childData) => {
/*this.setState((prevState) => ({
counter: prevState.counter + 1,
}));*/
}
handleClick = (id)=>{
this.props.addToCart(id);
}
render(){
let itemList = this.props.items.map(item=>{
return(
<Tilt className="Tilt" options={{ max : 10 }, { perspective : 3000 }, {scale : 0.97}} >
<div className="card" key={item.id}>
<div className="card-image">
<img src={item.img} alt={item.title}/>
</div>
<div className="card-content">
<h2 className="card-title">{item.title}</h2>
<p>{item.desc}</p>
<div className="slider">
<h3>Swipe um zu bestellen</h3>
<input
type="range"
defaultValue={0}
min={0}
max={150}
className="pullee"
/*ref={(el) => { this.inputRange = el; }}*/
/>
</div>
<span to="/" className="btn-floating halfway-fab waves-effect waves-light red" onClick={()=>{this.handleClick(item.id)}}><i className="material-icons">add</i></span>
</div>
</div>
</Tilt>
)
})
return(
<StackGrid
columnWidth="33.3%"
gutterWidth={20}
gutterHeight={20}
duration={500}
className="stackGrid"
>
{itemList}
</StackGrid>
)
}
}
const mapStateToProps = (state)=>{
return {
items: state.items
}
}
const mapDispatchToProps= (dispatch)=>{
return{
addToCart: (id)=>{dispatch(addToCart(id))}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Home)
It is a race condition. It is because the ref isn't assigning this.inputRange soon enough before componentDidMount, so it is undefined when you try and add event listeners to the input.
You can try and add conditionals and various strategies, but that isn't the "React" way of doing this. It is no coincidence that the React way is also the most straightforward. Change those event listeners to be a synthetic event that React handles for you, by making it an inline prop inside the input element.
Take out the ref unless you need it for other things and add the event listeners inline.
<input
onMouseDown={this.unlockStartHandler}
onMouseStart={this.unlockStartHandler}
// and so on
/>
React event listener prop names are always the normal JS name with on appended to the front (and retaining camelCase naming).

React hooks show more/less text like in youtube comments section

I have some array of comments like in youtube comments section and i wan't to make it show less/more button for long comment. I've stuck a little on a way to do it locally on item without total rerender (rebuild of block) or any states values.
function commentsGenerate() {
let block = comments.map((comment, i) => {
let splitLength = 400
let shortDesc = comment || '' // for null vals
let shortened = false
if (shortDesc.length > splitLength) shortened = true
shortDesc = shortDesc.substring(0, splitLength)
return (
<div key={i}>
<div>{`${shortDesc}${shortened ? '...' : ''}`}</div>
{shortened && <Button onCLick={ () => {'how?'} >More</Button>}
</div >
)
})
setSectionBlock(block)
}
You can't/shouldn't do this sort of thing without using state somewhere. In this case I suggest that you separate your comment code and state into a separate component which can handle its own expanded state. You can then use the state to adjust the rendering and styling of your output:
import React, { useState } from 'react'
// ... //
const Comment = ({comment:shortDesc = ''})=>{
let splitLength = 400
let shortened = false
const [isExpanded, setIsExpanded] = useState(false)
if (shortDesc.length > splitLength) shortened = true
shortDesc = shortDesc.substring(0, splitLength)
const handleToggle = ()=>{
setIsExpanded(!isExpanded)
}
return (
<div key={i}>
{!isExpanded ? <div>{`${shortDesc}${shortened ? '...' : ''}`}</div> : null}
{shortened && <Button onClick={handleToggle}>{isExpanded?'Less':'More'}</Button>}
</div >
)
}
Use this component in your mapping like this:
let block = comments.map((comment, i) => <Comment comment={comment} key={i} /> )

Resources