react createRef keeps returning null - reactjs

I have a ref:
const ref = React.createRef();
I pass it like so:
<WinnerPopup winner={turn ? 'cross' : 'circle'} ref={ref}></WinnerPopup>
This is the component:
const WinnerPopup = React.forwardRef((props, ref) => (
<Popup
trigger={<button ref={ref} className="button"></button>}
modal
nested
>
{close => (
<div className="modal">
<button className="close" onClick={close}>
×
</button>
<div className="header"> We have a winner! </div>
<div className="content">
{`Winner is ${props.winner}`}
</div>
</div>
)}
</Popup>
));
And I get the error:
TypeError: Cannot read property 'click' of null
This is such a simple use case and almost exactly the same thing as the document so I cannot figure out why I can't access the button.

I think you normally use forwardRef when you want to pass along the ref to the outer most component of what you are wrapping with forwardRef... ie. you create a that is a wrapper around so you forward the ref onto the ... in this case you are passing it to the button you are passing into a prop (trigger) of your outer most component... maybe you want to just pass the reference in a custom prop, like "triggerButtonRef"... also, what purpose do you need the ref on that button?
You might try and pass the ref as a custom prop, instead of trying to use forwardRef, like:
<WinnerPopup winner={turn ? 'cross' : 'circle'} triggerRef={ref} />
const WinnerPopup = ({ triggerRef, winner }) => (
<Popup
trigger={<button ref={triggerRef} className="button"></button>}
modal
nested
>
{close => (
<div className="modal">
<button className="close" onClick={close}>
×
</button>
<div className="header"> We have a winner! </div>
<div className="content">
{`Winner is ${winner}`}
</div>
</div>
)}
</Popup>
);
Depending on how <WinnerPopup /> works and/or if that is also your own custom component, AND you want to be able to control if it's open from the parent or another component, I think a better solution might be to store a state variable to control whether or not the popup is open, for instance:
const ParentComponent = () => {
const [open, setOpen] = useState(false);
const onClick = () => {
setOpen(!open)
}
return (
<>
<SomeOtherButton onClick={onClick} />
<WinnerPopup winner={turn ? 'cross' : 'circle'} isOpen={open} />
</>
)
}
const WinnerPopup = ({ isOpen, winner }) => (
<Popup
trigger={<button className="button"></button>}
modal
nested
open={isOpen}
>
{close => (
<div className="modal">
<button className="close" onClick={close}>
×
</button>
<div className="header"> We have a winner! </div>
<div className="content">
{`Winner is ${winner}`}
</div>
</div>
)}
</Popup>
);

Related

How can I only open one popup at a time based on a map function in react?

I am making a movie dashboard where users can search for movies and save them to their watch list. I am trying to have a popup containing the movie information that will appear if the user clicks on the image card for each movie. This is kind of working, but the problem is that the popup shows for every movie when I only click on one. My code is shown before. I haven't styled the popup yet, but the information looks right for each movie. Thanks for your help!
function Watchlist() {
const { watchlist } = useContext(GlobalContext);
const [modal, setModal] = useState(false);
const toggleModal = () => {
setModal(!modal);
}
return (
<div className='watchlist'>
<h1 className='title'>your watchlist</h1>
{watchlist.length > 0 ? (
<div className="movies">
{watchlist.map((movie, index) => {
if(index % 3 === 1) {
return (
<div className="movie-wrapper">
<button name={movie.id} className="open-modal" onClick={toggleModal} key={movie.id}>
<Card img={`https://image.tmdb.org/t/p/w200${movie.poster_path}`} margin={30} key={movie.id}/>
</button>
{modal && (
<div className="movie-details">
<div className="modal-content">
<ResultCard movie={movie} key={movie.id}/>
<button className="close-modal" onClick={toggleModal}>X</button>
</div>
</div>
)}
</div>
)
} else {
<div className="movie-wrapper">
<button name={movie.id} className="open-modal" onClick={toggleModal} key={movie.id}>
<Card img={`https://image.tmdb.org/t/p/w200${movie.poster_path}`} margin={0} key={movie.id}/>
</button>
{modal && (
<div className="movie-details">
<div className="modal-content">
<ResultCard movie={movie} key={movie.id}/>
<button className="close-modal" onClick={toggleModal}>X</button>
</div>
</div>
)}
</div>
}
})}
</div>
) : (
<h2 className="no-movies">Add some movies!</h2>
)}
</div>
)
}
You need to keep the movie id you want to show the modal for:
const [modal, setModal] = useState(false);
const [selectedMovie, setSelectedMovie] = useState();
const toggleModal = (movieId) => {
setModal(!modal);
setSelectedMovie(movieId)
}
onClick={() => toggleModal(movie.id)}
and check the selected movie when rendering:
{modal && movie.id === selectedMovie && (...
It's because you are rendering a modal for each movie in your list.
Take the modal out of the map function, and keep it as a child of the component where it does not rely on any condition, any mapped list, or anything else for it to be rendered. It's kind of a convention (at least among the codebases I've come across) to put modals at the end of the component JSX, as a direct child of the root element of the component.
Hide it by default, and only show it when a user clicks on a movie, passing the relevant info into the modal.
It might look something like this:
function Watchlist() {
const { watchlist } = useContext(GlobalContext);
const [modal, setModal] = useState(false);
const [selectedMovie, setSelectedMovie] = useState({});
const toggleModal = () => {
setModal(!modal);
}
return (
<div className='watchlist'>
<h1 className='title'>your watchlist</h1>
{watchlist.length > 0 ? (
<div className="movies">
{watchlist.map((movie, index) => {
// Render the mapped list of JSX elements (movie cards?)
// Potentially the `onClick` event would trigger the `setSelectedMovie` function in addition to setting `toggleModal`
})}
</div>
) : (
<h2 className="no-movies">Add some movies!</h2>
)}
{modal && (
<div className="movie-details">
<div className="modal-content">
<ResultCard movie={selectedMovie} key={selectedMovie.id}/>
<button className="close-modal" onClick={toggleModal}>X</button>
</div>
</div>
)}
</div>
)
}

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

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.

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: Child don't update parent's state

I'm new to React and I was trying to create a Modal using mui that opens and closes based on parent's state.
The problem is that the modal opens well based on state but upon closing, the modal onClose function works well, but when I click on Button that has the same with the same update state function it doesn't work.
Here's my parent component
const TableIcon = props => {
const {title,icon}=props;
// handling modal functionality
const [modalOpen, setModalOpen] = useState(false);
const handleModalOpen = () => {
setModalOpen(true);
};
const handleModalClose = () => {
setModalOpen(false);
};
console.log('modal', modalOpen)
return (
<button className={styles.button} title={title} onClick={()=>handleModalOpen()}>
{icon}
<ActionModal
open={modalOpen}
handleClose={handleModalClose}
handleOpen={handleModalOpen}
/>
</button>
)
}
and here is the modal component
const ActionModal=(props)=> {
const {open,handleOpen,handleClose}=props;
console.log(props,'modal')
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box className={styles.box}>
<div className={styles.header}>
<div className={styles.icon}>
<BsFillTrashFill/>
</div>
</div>
<div className={styles.content}>
<h2>You are about to delete a school</h2>
<h3>School Name/Tuituion</h3>
<p>This will delete the school from the database
Are you sure?</p>
</div>
<div className={styles.footer}>
<Button color='var(--unnamed-color-ffffff)' name='Cancel' onClick={handleClose}/>
<Button color='var(--unnamed-color-f53748)' name='Confirm'/>
</div>
</Box>
</Modal>
</div>
);
}
and here is the button component
const Button=(props)=>{
const {color,onClick,icon,name}=props;
return <div className={styles.container} style={{background:color}} onClick={()=>onClick()}>
{icon}
<span>{name}</span>
</div>
}
and the Button onClick works just fine
The problem is that your <ActionModal> component is inside the <button> tag. So when clicking the Cancel button in the modal you first get the call to handleModalClose, immediately followed by the call to handleModalOpen, because the "click" is getting passed to the button that opens the modal.
You need to change the code in <TableIcon> to something like:
return (
<>
<button title={title} onClick={() => handleModalOpen()}>
{icon}
</button>
<ActionModal open={modalOpen} handleClose={handleModalClose} />
</>
);
You can use this Stackblitz example.

Resources