Multiple readmore buttons working at the same time - javascript

Good Morning.
I'm trying to use "readmore" buttons on texts coming from the firestore
<section className="escritos">
{escritos.map(escrito => {
return (
<div>
<p>
Autor: <strong>{escrito.autor}</strong>
</p>
<p>
{" "}
Título: <strong>{escrito.titulo}</strong>
</p>
<div id="obras">
{" "}
{readmore
? parse(escrito.escrito)
: parse(escrito.escrito.substring(0, 200))}
<button id="readmore" onClick={() => setReadmore(!readmore)}>
{readmore ? "ler menos" : "ler mais"}
</button>
<Link to="#beginning">
<BsFillArrowUpCircleFill />
</Link>
</div>
<hr />
</div>
);
})}
</section>
It's working. But it works at the same time for all buttons.
I've already tried to use react-read-more, but there was an error because I have to use parse(escrito.escrito) to remove the html tags. Removing the parse, it works but with the tags showing.
It has something to do with the map I believe, but I haven't been able to solve it yet.
Here's the site and here's the repository if it helps.
Thank you in advance for your attention and your time.

Since you want the readmore state to be applied to the individual boxes you could store an array in the state and check, if the id is set, but I would go with the (in my opinion) simpler way:
Extract your block into its own component, which has its own state readmore like so:
<section className="escritos">
{escritos.map(escrito => (
<Escrito escrito={escrito} key={escrito.id} />
))}
</section>
// Escrito.jsx
const Escrito = ({escrito}) => {
const [readmore, setReadmore] = useState(false);
return (
<div>
<p>
Autor: <strong>{escrito.autor}</strong>
</p>
<p>
{" "}
Título: <strong>{escrito.titulo}</strong>
</p>
<div id="obras">
{" "}
{readmore
? parse(escrito.escrito)
: parse(escrito.escrito.substring(0, 200))}
<button id="readmore" onClick={() => setReadmore(!readmore)}>
{readmore ? "ler menos" : "ler mais"}
</button>
<Link to="#beginning">
<BsFillArrowUpCircleFill />
</Link>
</div>
<hr />
</div>
);
}
export default Escrito;
Since each component now has its own state, this should be the behavior you want.

Parent element returned from the escritos.map(escrito => {JSX}) should contain a key attribute with unique value. Read more here

Related

Button doesn't work if I use template literals to toggle CSS classes in React

I have a CSS module called styles and this React code. It's a div with two buttons inside. I use the isMenuActive state to verify if the menu is active or not. If it is active, the CSS class 'active' gets in and the menu appears, otherwise not.
<div className={`${styles.customerOptionsMenu} ${isMenuActive ? styles.active : null}`}>
<button onClick={() => { console.log('hi') }}>
<span className="material-icons">edit</span>Editar
</button>
<button onClick={() => {console.log('hi')}}>
<span className="material-icons">delete</span>Deletar
</button>
</div>
When I click the buttons, nothing happens.
But If I store the button as a global variable in developer tools and run button.click() it works fine if I remove the template literals:
<div className={styles.customerOptionsMenu + styles.active}>
<button onClick={() => { console.log('hi') }}>
<span className="material-icons">edit</span>Editar
</button>
<button onClick={() => {console.log('hi')}}>
<span className="material-icons">delete</span>Deletar
</button>
</div>
It works fine.
Why??? And what should I do to keep changing the classes when isMenuActive changes?
Edit: Full code with the button that changes isMenuActive
const [isMenuActive, setIsMenuActive] = useState(false)
const onBlur = () => { setIsMenuActive(!isMenuActive)}
return(
<td>
<button onBlur={onBlur} className={styles.customerOptions} onClick={() => setIsMenuActive(!isMenuActive)}>
<span className="material-icons">more_horiz</span>
</button>
<div className={`${styles.customerOptionsMenu} ${isMenuActive ? styles.active : null}`}>
<button onClick={() => { console.log('hi') }}>
<span className="material-icons">edit</span>Editar
</button>
<button onClick={() => {console.log('hi')}}>
<span className="material-icons">delete</span>Deletar
</button>
</div>
</td>
)
New edit: The answer is in the comments by Pandaiolo. The problem was the onBlur={onBlur} code, when I removed it from the button everything worked fine!
To handle changing classnames you can use the classnames package to do something like this:
import cx from "classnames";
<div className={cx(styles.customerOptionsMenu, { styles["active"]: isMenuActive })}>
<button onClick={() => { console.log('hi') }}>
<span className="material-icons">edit</span>Editar
</button>
<button onClick={() => {console.log('hi')}}>
<span className="material-icons">delete</span>Deletar
</button>
</div>
I think the space is fine in your template literal, because it references two different class names. You probably want to use ${isMenuActive ? styles.active :''} otherwise null becomes the string "null", which is probably harmless unless you have a class .null that applies some styles, but that is basically not what you want.
But maybe the onBlur is called after the click?
click
button becomes focus
button blurs ?
Not sure. But in that case it would toggle the state two times, cancelling its effect. Maybe try with just the onClick and without the onBlur at first?
And you can add a console.log('isMenuActive', isMenuActive) before the return statement to see it's value along rerenders, see if it matches what you expect.
The isMenuActive is not defined. Place it in as a parameter as so:
export default function App(isMenuActive) {
return (
<div className="App">
<div className={`${styles.customerOptionsMenu} ${isMenuActive ? styles.active : null}`}>
<button onClick={() => { console.log('hi') }}>
<span className="material-icons">edit</span>Editar
</button>
<button onClick={() => {console.log('hi')}}>
<span className="material-icons">delete</span>Deletar
</button>
</div>
</div>
);
}

react bootstrap carousel Items "Each child in a list should have a unique "key" prop"

Bootsrap carousel Items unique key error.i added the right place my key.but error won't go away.
here is my error.
when i inspect in elements,i think i added to the right place.but react giving me warning over and over again.
this is my code.
return (
<>
<Carousel activeIndex={index} onSelect={handleSelect} keyboard slide touch wrap pause={false}>
{movie.map((m) =>
<Carousel.Item key={m.id}>
<img
src={m.background_image_original}
alt={m.title_english}
/>
<Carousel.Caption>
<CaptionDiv >
<h4>suggestions</h4>
<h2>{m.title}</h2>
<CaptioninfoDiv className='mr-auto'>
<p className='mr-3'>{m.year}</p>
{m.genres.map((g) =>
<p className='mr-1'>{g}</p>
)}
<p className='ml-3'>{m.runtime} min</p>
</CaptioninfoDiv>
<CaptionInfoMain >
<p>{m.summary.slice(0, 200)}<Readmore> Read more...</Readmore> </p>
<a href={`https://www.youtube.com/watch?v=${m.yt_trailer_code}`} target="_blank" rel="noreferrer" className='btn btn-danger btn-lg'>Watch Trailer</a>
</CaptionInfoMain>
</CaptionDiv>
</Carousel.Caption>
</Carousel.Item>
)}
</Carousel>
</>
)
Your movie.map((m) => is giving keys to its children, but your m.genres.map((g) => is not. Assuming that there aren't any duplicate generes, you can use
{
m.genres.map((g) =>
<p className='mr-1' key={g}>{g}</p>
)
}

Reactjs filter where result is zero

I want to have text on the page change when no items from a mapped array are showing.
I am showing a list of questions, but if a question is "answered" (a boolean of answered: true), it is not mapped.
I'm trying to figure out the best way to determine if every item in the array has answered: true, and then change text on the page accordingly. Is it possible to use filter for this when I need a function that checks for no results coming back?
The page shows a list to the left, and the text "please click on a question" to the right. When a question is selected, it replaces "please click on a question" with information about the selected question. But when there are no questions, I want "please click on a question" to show something like "There are currently no questions to answer".
this.state = {
questions: [],
currentQuestion: null,
currentIndex: -1,
searchQuestion: "",
wanttodelete: false
};
{questions &&
questions.map((question, index) => (
<div key={index}>
{!question.answered &&
<li
className={
"list-group-item " +
(index === currentIndex ? "active" : "")
}
onClick={() => this.setActiveQuestion(question, index)}
key={index}
>
<p>
{question.question}
</p>
</li>
}
</div>
))}
</ul>
</div>
<div className="col-lg-8 question-area">
{currentQuestion ? (
<div>
<h4>
<FormattedMessage
id="question-list.questionHeader"
defaultMessage="Question"
description="Question header"/>
</h4>
<div>
<label>
<strong>Question:</strong>
</label>{" "}
{currentQuestion.question}
</div>
<div>
<label>
<strong>Category:</strong>
</label>{" "}
{currentQuestion.categoryId}
</div>
<div>
<label>
<strong>Status:</strong>
</label>{" "}
{currentQuestion.answered ? "Answered" : "Awaiting answer"}
</div>
<Link
to={"/questions/" + currentQuestion.id}
className="badge badge-warning"
>
Answer this question
</Link>
</div>
) : (
<div>
<p>Please click on a Question...</p>
</div>
)}
</div>
</div>
</div>
</div>
);
}
}
const nothingToAnswer = questions.every(question => question.answered);

ReactJS is not able to differentiate between elements

Basically, Here I am mapping data coming from backend but when I am trying to get textContent of content of h5 tag through postData function, it only gives textContent of first card element not 2nd and so forth..
Is it has something to do with key while mapping in ReactJS ?
Can anyone tell me where is the problem ?
import React, { Component } from "react";
import "./App.css";
class Cards extends Component {
constructor(){
super()
this.state = {
cData:[]
}
}
postData(){
let ff = document.getElementById('ccc')
let dd = ff.textContent
console.log(dd);
let data = { course: dd}
axios.post('http://localhost:9000/api', data)
.then(res => {
console.log(res);
})
.catch(err =>{
console.log(err);
})
}
render() {
return (
<div>
{ this.state.cData.map(el => (
<div className="card mb-3 jj" key={Math.random()}>
<div className="row g-0">
<div className="col-md-4">
<img
src={el.Image}
alt="..."
className="img-fluid ii"
/>
<button
type="button"
className="btn-sm btn-info btt"
onClick={this.dropDetails}
>
<small>More Details </small>
</button>
<a href="/form" >
<button type="button" className="btn-sm btn-info" **onClick={this.postData}**>
<small> Book Now </small>
</button>
</a>
</div>
<div className="col-md-1"> </div>
<div className="col-md-6">
<div className="card-body">
**<h5 className="card-title" id="ccc">{el.Course}</h5>**
<b> {el.FFS} </b> <br />
<span className="card-text">
<small className="text-muted">{el.FPS}</small>
</span>
<br /> <br />
<span>
By : {el.CP} <br />
Age : {el.Age} <br />
{el.TIW} <br />
{el.ON} | {el.Cen} <br />
{el.Skills}
</span>{" "}
<br /> <br />
) )} );
}
}```
You are using same id inside your map. IDs should be unique in a page.
If you have multiple IDs in a page getElementById will return only the first one.
You could change this line
<h5 className="card-title" id="ccc">{el.Course}</h5>
to
<h5 className="card-title ccc" >{el.Course}</h5>
and get the elemnts array like
let ff = document.querySelectorAll('.ccc');
I don't want to get all the elements at once, I need to differentiate
between the elements so that I can get the textContent of each h5 tag
element
ff.forEach((element) => {
console.log(element.textContent); // prints the textContent of each element.
})
I need one textContent per click, as per your solution when I am clicking on one card its showing me the textContent of all the cards
at once
You need to pass the index to postData and get the corresponding node.
Your map have a second parameter called index.
this.state.cData.map((el,index) => (
Pass this index to postData like this
onClick={()=>this.postData(index)}
Get the corresponding node in postData based on the index.
postData(index){
let ff = document.querySelectorAll('.ccc');
const clickedNode = ff[index];
const clickedNodeTextContent = clickedNode.textContent;

How to use reactive search to achieve 'Load More' function?

I want to build a searching page with Reactive search, and when users scroll the search results to the bottom, they can find a "Load More" button, by clicking it, the next page will be loaded. I found that in the document, there is a callback function called "handleLoadMore()", but I don't know how to use it.
The code I wrote is just like this.
<ReactiveList
className={classes.content}
dataField={"description"}
componentId={"SearchResult"}
react={{ and: ["DataSearch"] }}
pagination={false}
scrollOnChange={true}
stream={true}
showResultStats={true}
renderResultStats={stats => {
return (
<div>
<p className={classes.stats}>
{stats.numberOfResults} results available based on your location
</p>
<img
className={classes.logo_color}
src={require("../../images/logo-color.png")}
alt="logo-color"
/>
</div>
);
}}
paginationAt="bottom"
size={12}
infiniteScroll={true}
loader="Loading Results.."
>
{({ data, handleLoadMore }) => {
return (
<ResultCardsWrapper className={classes.cardsWrapper}>
{data.map((element, index) => {
console.log(data);
return (
<ResultCard className={classes.card} key={index} target={"_self"}>
<Link
className={classes.wrapper}
to={`/shop/${element._index}/${element._id}`}
>
<ResultCard.Image
className={classes.image}
src={getLogo(element.logo)}
/>
<ResultCard.Title className={classes.title}>
{element.title}
<div className={classes.shopIcons}>
{console.log(element.opening_hour[curDay].open)}
{curTime <= element.opening_hour[curDay].close &&
curTime >= element.opening_hour[curDay].open ? (
<img
src={require("../../images/Icon ionic-md-time.svg")}
alt="open"
/>
) : (
<img
src={require("../../images/Icon ionic-md-time-closed.svg")}
alt="open"
/>
)}
<img
src={require("../../images/Icon material-local-offer.svg")}
alt="offer"
/>
</div>
</ResultCard.Title>
</Link>
</ResultCard>
);
})}
<button onClick={handleLoadMore}>Load More</button>
</ResultCardsWrapper>
);
}}
</ReactiveList>;
you can replace the handleLoadMore to loadMore, it works for me.

Categories

Resources