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

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} /> )

Related

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>
));
...
}

Need to slice cards after page rendering react class component

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:

NextJS React, nested array in UseState not rendering on update inside of javascript

I have a hard time trying to understand "useState" and when a render is triggered. I want to make a drag and drop system where the user can drag elements from one box to another.
I have successfully done this by using pure javascript, but as usual, it gets messy fast and I want to keep it clean. I thought since I'm using react I should do it using UseState, and I've got the array to update the way I want it to but the changes don't render.
Shouldn't I use useState in this way? What should I use instead? I don't want to solve it the "hacky" way, I want it to be proper.
const [allComponents, updateArray] = useState([])
function arrayMove(array, closest) {
let moveChild = array[parentIndex].children[childIndex]
array[parentIndex].children = array[parentIndex].children.filter(function(value, index, arr){ return index != childIndex;});
array[closest].children = [...array[closest].children, moveChild];
return array;
}
var lastClosest = {"parent": null, "child": null};
var parentIndex = null;
var childIndex = null;
function allowDrop(ev, index) {
ev.preventDefault();
if(allComponents.length > 0) {
let closest = index;
if((parentIndex == lastClosest.parent && childIndex == lastClosest.child) || (closest == parentIndex)) {
return;
}
lastClosest.parent = parentIndex;
lastClosest.child = childIndex;
updateArray(prevItems => (arrayMove(prevItems, closest)));
}
}
function dragStart(pI, cI, ev) {
parentIndex = pI;
childIndex = cI;
}
function drop(ev) {
ev.preventDefault();
}
function addNewSpace() {
updateArray(prevItems => [...prevItems, {"name": "blaab", "children": [{"child": "sdaasd", "type": "text"}]}]);
}
return (
<div className={styles.container}>
<button onClick={addNewSpace}>Add</button>
<div className={styles.spacesWrapper}>
{allComponents.map(({name, children}, index) => (
<div key={"spacer_" + index} onDragOver={(event) => allowDrop(event, index)} onDrop={(event) => drop(event)} className={styles.space}>
{children.map(({type, child}, index2) => (
<div id ={"movable_" + index + ":" + index2} key={"movable_" + index2} className={styles.moveableOne}>
<div key={"draggable_" + index2} draggable="true" onDrag={(event) => onDrag(event)} onDragStart={(event) => dragStart(index, index2, event)}>
</div>
</div>
))}
</div>
))}
</div>
</div>
)
}
Here is the problem I'm experiencing with the nested array not updating properly:
A react page re-renders every time the state changes. So every time a setState is called, the react component re-renders itself and updates the page to reflect the changes.
You can also see how the state is updating if you install the react dev tools chrome extension.
Try to modify your code with the below code and see if it solves your issue,
import React, { useState, useEffect } from "react";
let newAllComponents = [...allComponents];
useEffect(() =>
{
newAllComponents = [...allComponents]
}, [allComponents]);
/* div inside return*/
{newAllComponents.map(({ name, children }, index) => (
<div
key={"spacer_" + index}
onDragOver={(event) => allowDrop(event, index)}
onDrop={(event) => drop(event)}
>

How to remove an specific element by clicking on it in react js

Here i have a mutiple customers and i want to reject the call of one of user , but here i don't know how to get the value of specific user to remove.. rejectCall function is removing all the customers while clciking the button ..Can anyone help me thanks in advance
const [names, setNames] = React.useState([]);
socket.on('start_call', async (customerName) =>{
setNames(names => [...names, customerName])
const localElement = document.querySelector('div#customerRequest');
var acceptButton = document.createElement('input');
acceptButton.setAttribute("type", "button");
acceptButton.setAttribute("value", "Accept Video");
acceptButton.onclick = initiateCall;
var rejectButton = document.createElement('input');
rejectButton.setAttribute("type", "button");
rejectButton.setAttribute("value", "Reject Video");
rejectButton.style.marginLeft ='20px'
acceptButton.onclick = rejectCall;
localElement.appendChild(acceptButton);
localElement.appendChild(rejectButton);
})
async function rejectCall(){
var element = document.getElementById('customerRequest')
element.remove()
}
<div className="md-form" id="customerRequest">
{ names.length > 0 && names.map((customer) => {
return (
<div style={{margin :5}}>
<button value={customer} id="customer" onClick={initiateChat}>{customer}</button>
</div>)
})}
Here is the image for better understanding
You can pass in the names array index as the second argument to names.map, and then pass that index to your initiateChat or rejectCall method:
{ names.length > 0 && names.map((customer, index) => {
return (
<div style={{margin :5}}>
<button value={customer} id="customer" onClick={()=> initiateChat(index)}>{customer}</button>
</div>)
})}
Inside of the initiateChat or rejectCall method, you can use the index to modify the names array, probably by using something like:
updatedNames = [...names].splice(index, 1);
setNames(updatedNames);
I see you are manually building up these accept / reject buttons with vanilla JS, but you are using react - why not use all react?
let [incomingCallers, setIncomingCallers] = React.useState([]);
socket.on('start_call', (customerName) => {
setIncomingCallers([...incomingCallers, customerName]);
});
// This is rough, but follow me here
const rejectCall = (customer) => {
//// reject call
}
const acceptCall = (customer) => {
//// accept call
}
<div className="md-form" id="customerRequest">
{ names.length > 0 && names.map((customer) => {
return (
<div style={{margin :5}}>
<button value={customer} id="customer" onClick={initiateChat}>{customer}</button>
<div class={incomingCallers.includes(customer.name) ? 'show' : 'hide'}>
<button onClick={() => acceptCall(customer)}>Accept</button><button onClick={() => rejectCall(customer)}>Reject</button>
</div>
</div>)
})}
I started the same way with React (manually building things with vanilla JS). But I quickly learned: If I'm building views with Vanilla JS in React, I should re-evaluate what it is I'm trying to do. Because it is probably much easier to do. Only in rare cases should I need to use vanilla JS (I've never found a case for this, yet).

repeated data array .map () react

can someone tell me why my .map () array is returning the same data several times on thescreen? I would like to show it only once but I don't know why it is repeating,
Can someone help me?
React code below:
console.log('>>> [HistoricosAtendimento] props: ', props);
const viewData = props.historicos || [];
const convertToArray = Object.values(viewData);
return (
<>
<SC.Container>
<SC.Item>
{convertToArray.map((item) => {
console.log('convertToArray', convertToArray);
return (
<SC.Item key={item.protocolo}>
<SC.Description>{item.textNotas}</SC.Description>
</SC.Item>
);
})}
</SC.Item>
Just remove the outer wrapping component:
<SC.Container>
// <SC.Item> <= remove
{convertToArray.map((item) => {
// console.log('convertToArray', convertToArray); // should move out of JSX
return (
<SC.Item key={item.protocolo}>
<SC.Description>{item.textNotas}</SC.Description>
</SC.Item>
);
})}
// </SC.Item> <= remove

Resources