I have to show a PDF file on a page and it works well but whenever I open or close the modal the PDF is re-rendered.
import { documentActions } from '../_actions'
export default function DocPreview() {
const document = useSelector(({ document }) => document)
const [show, setShow] = useState(false)
const dispatch = useDispatch()
useEffect(() => dispatch(documentActions.getDocumentContent('someDocId')), [dispatch])
return <main>
{document.loaded && <embed src={URL.createObjectURL(document.data)} type='application/pdf' />}
<button onClick={() => setShow(true)}>Sign document</button>
<Modal show={show} onHide={() => setShow(false)}>
<button className='btn btn-secondary' onClick={() => setShow(false)}>Close</button>
</Modal>
</main>
}
You can make the pdf document it's own component and only re render when document changes:
const Pdf = React.memo(function Pdf({ document }) {
document.loaded && (
<embed
src={URL.createObjectURL(document.data)}
type="application/pdf"
/>
);
});
In DocPreview
<main>
<Pdf document={document} />
Related
Problem: when i click on the button
<button
onClick={() => {
navigate('/posts');
setResponse(e.id);
}}
>
I get this error: Warning: Cannot update a component (Home) while rendering...
I think problem only in this line
navigate('/posts');
because if I delete it, error disappears
full code under without import
App.js
function App() {
const [response, setResponse] = useState({});
return (
<Router>
<Routes>
<Route exact path="/" element={<Home setResponse={setResponse} />} />
<Route exact path="/posts" element={<Posts response={response} />} />
<Route exact path="*" />
</Routes>
</Router>
);
}
export default App;
Home.js
function Home({ setResponse }) {
const [modal, setModal] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
const state = useSelector((state) => state);
console.log(state);
if (state.user.isLoading) {
return <h1>Loading...</h1>;
}
const toggleModal = () => {
setModal(!modal);
};
return (
<div className="App">
<button onClick={(e) => dispatch(fetchUsers())}>Fetch users</button>
{state.user.data &&
state.user.data.map((e) => (
<div key={e.id}>
<li key={e.name}>{e.name}</li>
<button
key={e.id + 10}
onClick={() => {
navigate('/posts');
setResponse(e.id);
}}
className="btn"
>
Posts
</button>
<button onClick={toggleModal} key={e.id + 100} className="bnt">
Albums
</button>
</div>
))}
<Albums modal={modal} setModal={setModal} />
</div>
);
}
export default Home;
Posts.js
function Posts({ response }) {
const dispatch = useDispatch();
const navigate = useNavigate();
const state = useSelector((state) => state);
console.log(state);
if (state.post.isLoading) {
return <h1>Loading...</h1>;
}
if (!state.post.data) {
dispatch(fetchPosts());
}
return (
<div className="App">
Posts
{state.post.data &&
state.post.data
.filter((e) => e.userId === response)
.map((e) => (
<div key={e.userId.toString() + e.id.toString()}>
<li key={e.id}>{e.title}</li>
</div>
))}
<button
onClick={() => {
navigate('/');
}}
>
List of users
</button>
</div>
);
}
export default Posts;
I tried to use useEffect(), but it doesn't work in my case
<button
onClick={() => {useEffect(()=>{
navigate('/posts');
setResponse(e.id);},[])
}}
>
If you're navigating away to another page, you shouldn't be updating the state as you're navigating (which you're doing by calling setResponse after navigate).
To fix this error, you'd have to call navigate() after React finishes updating the response variable, which you can do by using a useEffect call at the top-level of your Home component:
// you should also pass the value of response to the Home component so it knows when it's been changed
function Home({ setResponse, response }) {
const [modal, setModal] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
const state = useSelector((state) => state);
console.log(state);
useEffect(() => {
// This if statement checks if response === {}, which is a bit awkward; you should instead initialize your response state to a value like `null`
if (typeof response === 'object' && Object.keys(response).length === 0) {
return
}
navigate('/posts')
}, [response])
if (state.user.isLoading) {
return <h1>Loading...</h1>;
}
const toggleModal = () => {
setModal(!modal);
};
return (
<div className="App">
<button onClick={(e) => dispatch(fetchUsers())}>Fetch users</button>
{state.user.data &&
state.user.data.map((e) => (
<div key={e.id}>
<li key={e.name}>{e.name}</li>
<button
key={e.id + 10}
onClick={() => {
// navigate('/posts');
setResponse(e.id);
}}
className="btn"
>
Posts
</button>
<button onClick={toggleModal} key={e.id + 100} className="bnt">
Albums
</button>
</div>
))}
<Albums modal={modal} setModal={setModal} />
</div>
);
}
export default Home;
I would like to create a custom button component that shows a loading spinner within itself when it's pressed and with a condition that can be externally defined which will tell the button to remove the spinner and return to its original appearance. Something like this:
<CustomButton
type="button"
className="btn btn-primary"
stopSpinningWhen={condition}
onClick={() => ...}
>
Click me
</CustomButton>
Currently, my buttons with a spinner look like this, which is super fine, but it's a pain to write repetitive code/states for each single button:
const [buttonSpinner, setButtonSpinner] = useState(false);
const onClickEvent = (ev) => {
setButtonSpinner(true);
if (condition) {
setButtonSpinner(false);
}
};
return (
<button
type="button"
className="btn btn-primary"
onClick={onClickEvent}
disabled={buttonSpinner}
>
{buttonSpinner ? (
<span
className="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
></span>
) : (
"Click me"
)}
</button>
);
I'm using React 17.0.2.
Is it even possible?
You can create your own custom button that receives isLoading additionally.
const Spinner = (
<span
className="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
)
const CustomButton = (props) => (
<button
type="button"
className="btn btn-primary"
onClick={props.onClick}
disabled={props.isLoading}
>
{props.isLoading ? <Spinner /> : "Click me"}
</button>
)
const YourComponent = () => {
const [isLoading, setIsLoading] = useState(false)
const onClick = async (event) => {
setIsLoading(true)
doHeavyTask()
setIsLoading(false)
}
return (
<div>
<CustomButton isLoading={isLoading} onClick={onClick} />
</div>
)
}
You use a loading property as your condition and just pass that as a prop to your custom button component. Something like this:
const myComponent = () =>{
const [loading, setLoading] = useState(false)
const myFunc = async () = {
setLoading(true)
//call api or do seomthing, after the process finishes set loading to false again
const resp = await fetch("myAPIURL")
console.log(resp.data)
setLoading(false)
}
return(
<CustomButton
type="button"
className="btn btn-primary"
spinning={loading}
onClick={() => myFunc()}
>
Click me
</CustomButton>
)
}
I am intern ReactJs, how to use multiple-drawer reactJs in same page.
code my App.js file
App = () =>{
const [visible, setVisible] = useState(false);
const showDrawer = () => {
setVisible(true);
};
const onClose = () => {
setVisible(false);
};
return(
// drawer 1
<Button type="primary" className="btn-submit" onClick={this.showDrawer}>submit drawer 1</Button>
<Drawer className="modal-form"onClose={onClose} visible={visible}>
content
</Drawer>
// drawer 2
<Button type="primary" className="btn-submit" onClick={this.showDrawer}>submit drawer 2</Button>
<Drawer className="modal-form"onClose={onClose} visible={visible}>
content
</Drawer>
)
}
how can i click on the button tag to show the corresponding drawer!
pls! help me.
If these drawers contain different content then you will have to manage two state variables for each drawer to control the visibility of drawer.
App = () =>{
const [visibleDrawer1, setVisibleDrawer1] = useState(false);
const [visibleDrawer2, setVisibleDrawer2] = useState(false);
const showDrawer1 = () => {
setVisibleDrawer1(true);
};
const onCloseDrawer1 = () => {
setVisibleDrawer1(false);
};
const showDrawer2 = () => {
setVisibleDrawer2(true);
};
const onCloseDrawer2 = () => {
setVisibleDrawer2(false);
};
return(
// drawer 1
<Button type="primary" className="btn-submit"
onClick={this.showDrawer1}>submit drawer 1</Button>
<Drawer className="modal-form"onClose={onCloseDrawer1}
visible={visibleDrawer1}>
content
</Drawer>
// drawer 2
<Button type="primary" className="btn-submit"
onClick={this.showDrawer2}>submit drawer 2</Button>
<Drawer className="modal-form"onClose={onCloseDrawer2}
visible={visibleDrawer2}>
content
</Drawer>
)
}
I am trying to create a modal that I can reuse/call from multiple components. I want the modal to display in app.js but the button call is on another component.
Once I am able to implement one, I can but button on other components and call same modal instead of having to create the same modal for each component
<div className="App">
<HeroSlider />
<HowItWorks />
<Modal />
<Footer />
</div>
The modal button is on this component(HeroSlider). Once its click it will call the modal component and display it in app.js
import React, { useState } from "react";
import Header from './Header'
function HeroSlider(props) {
const [show, setShow] = useState(false);
const modalShow = () => setShow(true);
const openIsAccount = () => {
}
return (
<div className="jumbotron" id="jumbotron2" >
<button type="button" className="btn btn-primary" id="shoutBtn" onClick={modalShow}><span>Get Started</span>
</button>
</div>
);
}
export default HeroSlider;
Here is the Modal.js
const IsAccountOpen = (props) => {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Modal show={props.show} onHide={handleClose} backdrop="static" keyboard={false}>
<Modal.Header closeButton>
<Modal.Title>Modal title</Modal.Title>
</Modal.Header>
<Modal.Body>
I will not close
</Modal.Body>
</Modal>
</>
);
};
export default IsAccountOpen
You need to add a function in the app.js to handle the click on the button, so the function will change the state show to true, and you will pass the state to your modal, like this:
App.js
[showState, setShowState] = useState(false)
buttonClickedHandler = () => {
setShowState((showState) => showState = !showState )
}
<div className="App">
<HeroSlider buttonClicked={buttonClickedHandler} />
<HowItWorks />
<Modal show={showState} buttonClicked={buttonClickedHandler} />
<Footer />
</div>
HeroSlider.js
import React, { useState } from "react";
import Header from './Header'
function HeroSlider(props) {
const [show, setShow] = useState(false);
const modalShow = () => setShow(true);
const openIsAccount = () => {
}
return (
<div className="jumbotron" id="jumbotron2" >
<button type="button" className="btn btn-primary" id="shoutBtn" onClick={props.buttonClicked}><span>Get Started</span>
</button>
</div>
);
}
export default HeroSlider;
IsAccountOpen.js
const IsAccountOpen = (props) => {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Modal show={props.show} onHide={props.buttonClicked} backdrop="static" keyboard={false}>
<Modal.Header closeButton>
<Modal.Title>Modal title</Modal.Title>
</Modal.Header>
<Modal.Body>
I will not close
</Modal.Body>
</Modal>
</>
);
};
export default IsAccountOpen
I think the best solution is to use redux, because you will need to access and update the state from different components, you shouldn't use context API because the state changes frequently and you will hit the performance.
You'll need to extract the state to App level, so that this can be shared to components. You can do this with useState, or using context & custom hooks if you want to make it very clean. But do it with useState on first.
HeroSlider should receive modalShow function in it's props.
Modal.js should receive the show state as props.
Here I have my modal component. I am making an app that I want a button to open this modal that I use in multiple places like opening a preview or deleting options.
import React from 'react';
import ReactDOM from 'react-dom';
import { CSSTransition } from 'react-transition-group';
import Backdrop from '../Backdrop/Backdrop';
import '../Modal/Modal.css';
const ModalOverlay = (props) => {
const content = (
<div className={`modal ${props.className}`} style={props.style}>
<header className={`modal__header ${props.headerClass}`}>
<h2>{props.header}</h2>
</header>
<form
onSubmit={
props.onSubmit ? props.onSubmit : (event) => event.preventDefault()
}
>
<div className={`modal__content ${props.contentClass}`}>
{props.children}
</div>
<footer className={`modal__footer ${props.footerClass}`}>
{props.footer}
</footer>
</form>
</div>
);
return ReactDOM.createPortal(content, document.getElementById('modal-hook'));
};
const Modal = (props) => {
return (
<React.Fragment>
{props.show && <Backdrop onClick={props.onCancel} />}
<CSSTransition
in={props.show}
mountOnEnter
unmountOnExit
timeout={200}
classNames="modal"
>
<ModalOverlay {...props} />
</CSSTransition>
</React.Fragment>
);
};
export default Modal;
And here I use this modal for showing up deleting options.
const DocumentItem = (props) => {
const [showConfirmModal, setShowConfirmModal] = useState(false);
const showDeleteWarningHandler = () => {
setShowConfirmModal(true);
};
const calcelDeleteHandler = () => {
setShowConfirmModal(false);
};
const confirmDeleteHandler = () => {
setShowConfirmModal(false);
console.log('Delete!');
};
return (
<React.Fragment>
<Modal
show={showConfirmModal}
onCancel={calcelDeleteHandler}
header="Are you sure?"
footerClass="document-item__modal-actions"
footer={
<React.Fragment>
<Button inverse onClick={calcelDeleteHandler}>
CANCEL
</Button>
<Button danger onClick={confirmDeleteHandler}>
DELETE
</Button>
</React.Fragment>
}
>
<p>
Do you want to proceed and delete this document? Please note that it
can't be undone thereafter.
</p>
</Modal>
</React.Fragment>
);
};
I don't understand why my screen goes all black, transparent but my modal doesn't show.
How can I fix this problem?