React.js close all the previous containers at the same time - reactjs

I got the container with children coming from props.[Container][1]
[1]: https://i.stack.imgur.com/3Y7Qm.png . When i click the arrow button it shows the content of the container. [Content][1]
[1]: https://i.stack.imgur.com/A8eZH.png . When i open the content container i want other containers to close . For now i can only close them with clicking the arrow button again.[Open Content][1]
[1]: https://i.stack.imgur.com/REh57.png .Here is my code `
import { useState } from "react";
export default function Question(props) {
const [clicked, setClicked] = useState(false);
function clickedElement() {
return setClicked(!clicked);
}
return (
<div className="question-cont">
<div className="question-cont-inner">
<h3>{props.head}</h3>
<button onClick={() => clickedElement()}>
{clicked ? (
<img src={props.img2} />
) : (
<img src={props.img} />
)}{" "}
</button>
</div>
{clicked ? <p>{props.description}</p> : ""}
</div>
);
}
Here is the my parent component
import Question from "../components/Question";
import questions from "../components/Questions";
export default function Sorular() {
const questionList = questions.map((question) => {
return (
<Question
key={question.id}
id={question.id}
head={question.head}
description={question.description}
img={question.img}
img2={question.img2}
/>
);
});
return (
<div className="sorular-container">
<div className="sorular-top">
<div className="sorular-top-back-img">
<a href="/">
<img
src="./images/right-arrow-colorful.png"
id="right-arrow-img"
/>
</a>
</div>
<div className="sorular-top-head">
<img src="./images/conversation.png" />
<h4>Sıkça Sorulan Sorular</h4>
</div>
</div>
<div className="sorular-bottom">{questionList}</div>
</div>
);
}
`

You need to remove your const [clicked, setClicked] = useState(false); state variable from the component itself and move it into parent:
In parent add this at the beggining and modify questionList:
const [clickedElementId, setClickedElementId] = useState(null);
const questionList = questions.map((question) => {
return (
<Question
key={question.id}
id={question.id}
head={question.head}
description={question.description}
img={question.img}
img2={question.img2}
isOpened={question.id === clickedElementId}
onClickedElement={() => setClickedElementId(
question.id === clickedElementId ? null : question.id
)}
/>
);
});
And in the Question.jsx, swap button for the following:
<button onClick={() => props.onClickedElement()}>
{props.isOpened ? (
<img src={props.img2} />
) : (
<img src={props.img} />
)}{" "}
</button>
// and later:
{props.isOpened ? <p>{props.description}</p> : ""}
This works by your app holding id of only one, currently open question, and swap it based on clicked element.
Note that questionId should be unique amongst all Question components, but you probably use .map to render them so you should use the same variable as you are passing into Question's key prop while rendering.

Related

SlateJS React trying to get a textfield to work inside an Element

I have a Slate element that looks like this:
import React, {useState} from 'react'
import "./App.css"
export default function Accordion(props) {
const [closed, setClosed] = useState(false) //todo eventually fetch starting position from savefile
const [heightClass, setHeightClass] = useState("")
const handleToggle = () => {
if(closed === false){
setHeightClass("h-0")
}
else{
setHeightClass("")
}
setClosed(!closed)
}
return (
<>
<div {...props.attributes}>
{/* title and button */}
<div className='flex justify-between '>
<div className='font-semibold'>
<DefaultElement {...props}/> //the title of the accordion
</div>
<div
className={`px-2 cursor-pointer font-bold font-mono select-none ${closed ? "rotate-180" : ""}`}
onClick={() => {
handleToggle()
}}>
V
</div>
</div>
{/* ${closed ? "h-0" : ""} */}
<div className={`rounded border-l px-2 overflow-hidden accordionTransition `}
style={{height: closed ? "0px" : ""}}>
{props.children}
</div>
</div>
</>
)
}
const DefaultElement = props => {
console.log(props)
return <p {...props.attributes}>
{props.children}
</p>
}
Which is used by the Editor in App.js:
const App = () => {
const [editor] = useState(() => withReact(createEditor()))
// Define a rendering function based on the element passed to `props`. We use
// `useCallback` here to memoize the function for subsequent renders.
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'accordion':
return <Accordion {...props} />
default:
return <DefaultElement {...props} />
}
}, [])
return (
<div className='p-5'>
<Slate editor={editor} value={initialValue}>
<Editable
// Pass in the `renderElement` function.
renderElement={renderElement}
/>
</Slate>
</div>
)
}
const DefaultElement = props => {
return <span {...props.attributes}>
{props.children}
</span>
}
export default App;
I'm trying to get the Accordion title to be properly editable and work as Slate intended. Just a a simple textfield. I can't figure out the correct syntax and how i'm supposed to do this.
When i do this, the title and the accordion content (props.children) are the same. I don't understand why.
If i remove the <DefaultElement {...props}/> and just write some text, it throws me this error when i edit it: Uncaught Error: Cannot resolve a Slate point from DOM point: [object Text],4

How to toggle Modal and pass/change data

I am new to reacting and having trouble understanding how to pass data meta into each modal when an image is clicked and update the modal with the clicked data info. Following in my bare minimum code for sake of example
app.js
<div className="movie">
<Modal >hello world/*making sure the static text is passed into as children prop*/</Modal>
{movies.length > 0 &&
movies.map((data) => {
return <Library key={data.id} {...data} searchTerm={searchTerm} />;
})}
</div>
modal.jsx
export default function Index({children}) {
const [isOpen, setIsOpen] = useState(true)
return (
isOpen && (
<div className='modalContainer'>
<div className="modal">
<div className="close">
<button onClick={()=>{
setIsOpen(false)
}}>close</button>
</div>
{children}
</div>
</div>
)
)
}
Library.jsx
import "./Library.scss";
import {Link} from "react-router-dom";
const IMG_API = "http://image.tmdb.org/t/p/w1280";
const Library = ({
title,
poster_path,
release_date,
}) => {
return (
<div>
<Link to="/modal">
<img src={IMG_API + poster_path} alt={title} />
</Link>
<div className="meta">
<h5>{title}</h5>
<p>{release_date.slice(0, 4)}</p>
</div>
</div>
);
};
export default Library;
You should declare the isOpen state in the upper-level component so you can actually open the modal on some kind of event.
Also, you should declare a props where to pass the actual text to the Modal component:
const [isOpen, setIsOpen] = useState(false)
const handleOpen = () => setIsOpen(true);
<div className='movie'>
<Modal isOpen={isOpen} setIsOpen={setIsOpen} text='Hello, world' />
{movies.length > 0 &&
movies.map((data) => {
return <Library key={data.id} {...data} searchTerm={searchTerm} />;
})}
<button type='button' onClick={() => handleOpen()}>Open modal</button>
</div>;
You should then change your Modal declaration as:
export default function Index({ isOpen, setIsOpen, text }) {
return (
isOpen && (
<div className='modalContainer'>
<div className="modal">
<div className="close">
<button onClick={()=>{
setIsOpen(false)
}}>close</button>
</div>
{text}
</div>
</div>
)
)
}

Unable to load pdf in react

I'm trying to load a pdf file for the user in another tab once they click a button but it's not working and I'm not sure how to make it work. Could I have some help in doing this?
I defined a function PdfViewer() and I call it when a button is clicked using onClick(), but once the button is clicked I get this error:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
Here's my code:
import "../styles/ProjectDetails.css";
import React, { useState } from 'react'
import { Document, Page } from 'react-pdf/dist/esm/entry.webpack'
function PdfViewer() {
const [numPage, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({numPages}) {
setNumPages(numPage);
setPageNumber(1);
}
return (
<div>
<header>
<Document file="../pdfs/Mini_Case_Study_15.pdf" onLoadSuccess={onDocumentLoadSuccess}>
<Page height="600" pageNumber={pageNumber}></Page>
</Document>
</header>
</div>
)
}
const ProjectDetails = ({ project }) => {
return (
<div className="card-grid">
<div className="card">
<div className="card-header card-image">
<img src="https://c4.wallpaperflare.com/wallpaper/672/357/220/road-background-color-hd-wallpaper-thumb.jpg"/>
</div>
<div className="card-title"><strong>{project.sdg}</strong></div>
<div className="card-body">
<strong>Goal:</strong> {project.goal}
</div>
<div className="card-themes">
<strong>Themes:</strong> {project.theme.map((theme)=>{return theme + ', '})}
</div>
<div className="card-assignment">
<strong>Assignment Type:</strong> {project.assignment_type}
</div>
<div className="card-footer">
<button className="btn">Details</button>
{project.assignment_type === 'Mini Case Studies' &&
<>
<button className="btn btn-outline">Download</button>
{/* <button onClick={PdfViewer} className="btn">Preview</button> */}
</>
}
</div>
</div>
</div>
)
}
export default ProjectDetails
How do I make it so that once the user clicks the button, it takes them to another page with the pdf file shown?
You could try this approach here, inserting the Preview as a Component.
const ProjectDetails = ({ project }) => {
const [preview, setPreview] = useState(false)
const onClickToPreviewPDF = () => {
setPreview(preview ? false : true);
}
return (
<>
<div className="card-grid">
<div className="card">
<div className="card-header card-image">
<img src="https://c4.wallpaperflare.com/wallpaper/672/357/220/road-background-color-hd-wallpaper-thumb.jpg"/>
</div>
<div className="card-title"><strong>{project.sdg}</strong></div>
<div className="card-body">
<strong>Goal:</strong> {project.goal}
</div>
<div className="card-themes">
<strong>Themes:</strong> {project.theme.map((theme)=>{return theme + ', '})}
</div>
<div className="card-assignment">
<strong>Assignment Type:</strong> {project.assignment_type}
</div>
<div className="card-footer">
<button className="btn">Details</button>
{project.assignment_type === 'Mini Case Studies' &&
<>
<button className="btn btn-outline">Download</button>
<button onClick={onClickToPreviewPDF} className="btn">Preview</button>
</>
}
</div>
</div>
</div>
{preview && <PdfViewer />}
</>
)
}

React array item selection

I am trying to click on one card of a dynamically created list using map(). I want to click on one card from the array and add a class to it, while at the same time deselecting the other card that was previously clicked. How can I accomplish this? This is what I have so far:
const CardList = () => {
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} {...otherData} />
))}
</div>
);
};
export default CardList;
const Card = ({
headline,
time,
views,
thumbImg,
trainerImg,
workouts,
id
}) => {
const [isSelected, setIsSelected] = useState(false);
const [clickId, setClickId] = useState('');
function handleClick(id) {
setIsSelected(!isSelected);
setClickId(id);
}
return (
<div
className={`card ${isSelected && clickId === id ? 'clicked' : ''}`}
onClick={() => handleClick(id)}
>
<div className='thumbnail-div'>
<img className='thumbnail-img' src={thumbImg} alt='video' />
{workouts ? (
<div className='workout-overlay'>
<p>{workouts}</p>
<p className='workouts'>workouts</p>
</div>
) : null}
</div>
<div className='card-info'>
<div className='card-headline'>
<p>{headline}</p>
<img src={trainerImg} alt='trainer' />
</div>
{time && views ? (
<div className='trainer-data'>
<span>
<i className='glyphicon glyphicon-time'></i>
{time}
</span>
<span>
<i className='glyphicon glyphicon-eye-open'></i>
{views}
</span>
</div>
) : null}
</div>
</div>
);
};
export default Card;
The parent component should control what card is clicked. Add className property to card component:
const Card = ({
//...
className,
onClick
}) => {
//...
return (
<div
className={`card ${className}`}
onClick={() => onClick(id)}
>...</div>
)
}
In parent component pass the className 'clicked' and add the onClick callback to set the selected card:
const CardList = () => {
const [isSelected, setIsSelected] = useState(null);
const handleClick = (id) => {
setIsSelected(id);
}
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} className={isSelected===id && 'clicked'} onClick ={handleClick} {...otherData} />
))}
</div>
);
};
You can do something like this.
First you don't have to set state to each card. Instead Lift state Up.
You define which card is selected in parent so you can pass that to children and add classes if current selected is matching that children.
const CardList = () => {
const [isSelected, setIsSelected] = useState();
const handleCardClick = (id) => {
setIsSelected(id);
}
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} {...otherData} handleClick={handleCardClick} isSelected={isSelected}/>
))}
</div>
);
};
export default CardList;
const Card = ({
headline,
time,
views,
thumbImg,
trainerImg,
workouts,
id,
isSelected,
handleClick
}) => {
return (
<div
className={`card ${isSelected === id ? 'clicked' : ''}`}
onClick={() => handleClick(id)}
>
<div className='thumbnail-div'>
<img className='thumbnail-img' src={thumbImg} alt='video' />
{workouts ? (
<div className='workout-overlay'>
<p>{workouts}</p>
<p className='workouts'>workouts</p>
</div>
) : null}
</div>
<div className='card-info'>
<div className='card-headline'>
<p>{headline}</p>
<img src={trainerImg} alt='trainer' />
</div>
{time && views ? (
<div className='trainer-data'>
<span>
<i className='glyphicon glyphicon-time'></i>
{time}
</span>
<span>
<i className='glyphicon glyphicon-eye-open'></i>
{views}
</span>
</div>
) : null}
</div>
</div>
);
};
export default Card;

Toggle specific div (id) within a react component

I have a site built with post-components to show articles in a feed. Inside the component, I have a button that opens a modal onClick. I use useState to toggle on the modal which works perfectly fine. The problem is that since the toggle is put on the modal-div inside the component.. every single post modal opens whenever I click one of the buttons. I want to open only the targeted post modal (with the sam post id as the button I’m clicking). I can’t figure out how to do this…in react.
const [toggle, setToggle] = useState (true);
const toggler = () => {
setToggle(prev => !prev)
}
...
return (
<section className="posts">
{data.allMarkdownRemark.edges.map((edge) => {
return (
<div className="post">
<div className="postDescrip">
<h2 className="postTitle">{edge.node.frontmatter.title}</h2>
<h2 className="name">{edge.node.frontmatter.name}</h2>
<button className="readMoreBtn" onClick={toggler}>{toggle ? <h2 className="readMore">Read more</h2> : <h2 className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={edge.node.frontmatter.featuredImage.childImageSharp.fluid} />
<div className={toggle ? 'hide' : 'postCopy'} >
<Close close={toggler} />
<h3>{edge.node.frontmatter.details}</h3>
<div className="copy" dangerouslySetInnerHTML= {{__html: edge.node.html}}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)}
)}
</section>
)
}
export default Posts;
After trying suggested solution using object instead on bolean. I now receive this error message
[Error message][1]for the following code:
const [toggles, setToggles] = useState ({});
let id;
const createToggler = (id) = () => {
setToggles(prev => { [id] : !prev[id] })
// setToggle(prev => { ...prev, [id]: !prev[id] }) // or support multi modal at same time. but I think you don't want it.
}
const data = useStaticQuery(graphql`
query {
allMarkdownRemark (
sort: { order: DESC, fields: [frontmatter___date] }
){
edges {
node {
frontmatter {
id
title
name
details
featuredImage {
childImageSharp {
fluid(maxWidth: 800) {
...GatsbyImageSharpFluid
}
}
}
}
html
fields {
slug
}
}
}
}
}
`)
return (
<section className="posts">
{data.allMarkdownRemark.edges.map((edge) => {
const id = edge.node.frontmatter.id;
const toggle = toggles[id];
const toggler = createToggler(id);
return (
<div className="post" id={edge.node.frontmatter.id}>
<div className="postDescrip">
<h2 className="postTitle">{edge.node.frontmatter.title}</h2>
<h2 className="name">{edge.node.frontmatter.name}</h2>
<button className="readMoreBtn" onClick={toggler}>{toggle ? <h2 className="readMore">Read more</h2> : <h2 className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={edge.node.frontmatter.featuredImage.childImageSharp.fluid} />
<div className={toggle ? 'hide' : 'postCopy'} id={edge.node.frontmatter.id}>
<Close close={toggler} />
<h3>{edge.node.frontmatter.details}</h3>
<div className="copy" dangerouslySetInnerHTML= {{__html: edge.node.html}}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)}
)}
</section>
)
}
export default Posts;
[1]: https://i.stack.imgur.com/VhIYF.png
like this.
use a object instead of a single boolean.
const [toggles, setToggles] = useState ({});
const createToggler = (id) = () => {
setToggle(prev => { [id]: !prev[id] }) // atmost one id is true. others is undefine or false.
// setToggle(prev => { ...prev, [id]: !prev[id] }) // or support multi modal at same time. but I think you don't want it.
}
...
return (
<section className="posts">
{data.allMarkdownRemark.edges.map((edge) => {
const id = ... // get your id form edge.
const toggle = toggles[id];
const toggler = createToggler(id);
return (
<div className="post">
<div className="postDescrip">
<h2 className="postTitle">{edge.node.frontmatter.title}</h2>
<h2 className="name">{edge.node.frontmatter.name}</h2>
<button className="readMoreBtn" onClick={toggler}>{toggle ? <h2 className="readMore">Read more</h2> : <h2 className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={edge.node.frontmatter.featuredImage.childImageSharp.fluid} />
<div className={toggle ? 'hide' : 'postCopy'} >
<Close close={toggler} />
<h3>{edge.node.frontmatter.details}</h3>
<div className="copy" dangerouslySetInnerHTML= {{__html: edge.node.html}}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)}
)}
</section>
)
}
export default Posts;
I solved my problem like this
import React, {useState} from "react"
import Img from "gatsby-image"
import './posts.css';
import cancel from '../images/cancel.png'
const Post = ({title, name, id, image, details, html}) => {
const [toggle, setToggle] = useState (true);
const toggler = () => {
setToggle(prev => !prev)
}
const selectPost= (event) =>{
let id = event.target.id,
postCopy = document.getElementById('hide' + id);
toggler.call(postCopy);
}
return (
<div className="post">
<div className="postDescrip">
<h2 className="postTitle">{title}</h2>
<h2 className="name">{name}</h2>
<button className="readMoreBtn" onClick={selectPost}>{toggle ? <h2 id={id} className="readMore">Read more</h2> : <h2 id={id} className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={image} />
<div id={'hide' + id} className={toggle ? 'hide' : 'postCopy'} >
<button aria-label="Close" onClick={selectPost} className="closeBtn">
<img alt="close-button" src={cancel}/>
</button>
<h3>{details}</h3>
<div className="copy" dangerouslySetInnerHTML= {html}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)
}
export default Post;

Resources