I created this react Collapse component with react-spring which have the open and close functionality.
I want to have a defaultOpen prop, so the content stays open on initial render then it use isOpen state for closing and opening.
How can i do that?
here is a codesandbox link: https://codesandbox.io/s/awesome-fire-wsvml
For that, you will need to have Collapsible component own state
And pass defaultOpen from the parent component
Something like this:
const Collapse = ({ defaultOpen = false, children }) => {
const [isOpen, setIsOpen] = useState(defaultOpen)
const [ref, { height: viewHeight }] = useMeasure()
const { height, opacity } = useSpring({
from: { height: 0, opacity: 1 },
to: {
height: isOpen ? viewHeight : 0,
opacity: isOpen ? 1 : 0,
},
})
return (
<>
<button onClick={() => setIsOpen(isOpen => !isOpen)}>{isOpen ? 'close' : 'open'}</button>
<Content style={{ opacity, height }}>
<a.div ref={ref}>{children}</a.div>
</Content>
</>
)
}
Working Example:
If you wan't to make use of the property defaultOpen provide it to the App component like this:
export default function App({ defaultOpen = true }) {
const [isOpen, setIsOpen] = useState(defaultOpen)
return (
<Collapse isOpen={isOpen}>
...
</Collapse>
)
}
Related
I am trying to make a Modal component. I would like that when the modal appears, it appears with a transition just like when it disappears. This is currently very jerky, why and how can I fix it?
I would like that when the modal is shown it is shown with an animation and the same behavior when the modal disappears (click on the button).
thank you very much for the help, I know I will learn a lot.
//content of styles.css
.modal {
position: absolute;
width: 100%;
height: 100%;
background: red;
transition: all 300ms ease-out;
transform: translateY(100%);
}
.show {
transform: translateY(0%);
}
.hide {
transform: translateY(100%);
}
app.js
import Modal from "./modal";
/*
const Modal = ({ show, children }) => {
return <div className={`modal ${show ? "show" : "hide"}`}>.
{children} </div>;
};
export default Modal;
*/
import ModalContent from "./modalContent";
/*
const ModalContent = ({ show }) => {
const showModal = () => {
show();
};
return <button onClick={showModal}> close modal</button>;
};
export default ModalContent;
*/
export default function App() {
const [show, setShow] = useState(false);
const closeModal = () => {
setShow(false);
};
useEffect(() => setShow(true), []);
return (
<div className="App">
{show && (
<Modal show={show}>
<ModalContent show={closeModal} />
</Modal>
)}
</div>
);
}
I updated my code:
this is my live code
First of all, in your demo modal disappears immediately, without any transition. It seems, that it's cause by re-rendering of whole App component, on show state change. Extracting Modal component out of App do the trick for me:
const Modal = ({ show, children }) => {
useEffect(() => {}, [show]);
return <div className={`modal ${show ? "show" : "hide"}`}>{children} </div>;
};
export default function App() {
Second point - you can't control initial setup just with with css transition. Transition appears when something (class, attribute, pseudoclass) changes on the given element. To get around this and have smooth modal appearance, you can setup one-time useEffect in the App component, which will change show state from false to true. My overall snippet:
const Modal = ({ show, children }) => {
return <div className={`modal ${show ? "show" : "hide"}`}>{children} </div>;
};
export default function App() {
const ModalContent = ({ show }) => {
const showModal = () => {
show();
};
return <button onClick={showModal}> close modal</button>;
};
const [show, setShow] = useState(false);
const closeModal = () => {
setShow(false);
};
useEffect(() => setShow(true), [])
return (
<div className="App">
<Modal show={show}>
<ModalContent show={closeModal} />
</Modal>
</div>
);
}
I am working with the MUI library, specifically with the Draggable Dialog component seen here.
My question is how do I stop the dialog from being dragged off the screen?
I have tried this inside the Paper Component, but its not working:
import * as React from 'react';
import Paper, { PaperProps } from '#mui/material/Paper';
import Draggable, { DraggableEvent, DraggableData } from 'react-draggable';
export const PaperComponent = (props: PaperProps) => {
const widgetContainerRef = React.useRef<HTMLInputElement>(null);
const [widgetState, setWidgetState] = React.useState({
visible: false,
disabled: true,
bounds: { left: 0, top: 0, bottom: 0, right: 0 },
});
// const draggleRef = useRef<HTMLInputElement>(null);
const onStart = (event: DraggableEvent, uiData: DraggableData) => {
const { clientWidth, clientHeight } = window?.document?.documentElement;
const targetRect = widgetContainerRef?.current?.getBoundingClientRect();
if (targetRect) {
setWidgetState((prevState) => ({
...prevState,
bounds: {
left: -targetRect?.left + uiData?.x,
right: clientWidth - (targetRect?.right - uiData?.x),
top: -targetRect?.top + uiData?.y,
bottom: clientHeight - (targetRect?.bottom - uiData?.y),
},
}));
}
};
return (
<Draggable
handle="#draggable-dialog-title"
cancel={'[class*="MuiDialogContent-root"]'}
nodeRef={widgetContainerRef}
onStart={(event, uiData) => onStart(event, uiData)}
>
{/* <Resizable height={scaler.height} width={scaler.width} onResize={onResize}> */}
<div ref={widgetContainerRef}>
<Paper {...props} />
</div>
</Draggable>
);
};
Would anyone know how to prevent my user from dragging the dialog off the screen?
Looks like I overlooked a little thing here. It works if you add :
<Draggable
...
bounds={widgetState.bounds}
>
<div ref={widgetContainerRef}>
<Paper {...props} />
</div>
</Draggable>
Use onDrag Prop to prevent from being dragged, just return false on a specific scenario
onDrag={(e: any) => {
const from = e.relatedTarget || e.toElement;
if (!from || from.nodeName === EVENT_NODE_NAME.HTML) {
// stop your drag event here
return false;
}
}}
handle='#event-max-dialog'
cancel={'[class*="MuiDialogContent-root"]'}>```
I am attempting to create a button which pops up a react-color SketchPicker component(akin to the first example listed in react-color's "More Examples"). However, when I open my popup and drag the selector in SketchPicker, the color in the UI does not update, and the selector does not move.
I have perused my component, and I cannot find a single aspect of it in error; I have merely updated the first component in More Examples to utilize useState.
Why is this?
CodeSandbox
You need to pass a color prop to SketchPicker component and also add an onChange handler so whenever you move the cursor this handler triggers and it has access to selected color which you can update the state variable and pass that state variable to color prop.
import React, { useState } from "react";
import "./ColorSelector.css";
import { SketchPicker } from "react-color";
const ColorSelector = () => {
const [display, setDisplay] = useState(false);
const [color, setColor] = useState({
r: "241",
g: "112",
b: "19",
a: "1"
});
const onClickMethod = () => {
setDisplay(!display);
};
const onCloseMethod = () => {
setDisplay(false);
};
const onChangeMethod = (color) => {
setColor({ ...color.rgb });
};
const popover = {
position: "absolute",
zIndex: "3"
};
const cover = {
position: "fixed",
top: "0px",
right: "0px",
bottom: "0px",
left: "0px"
};
return (
<div>
<a className="colorSelector" onClick={onClickMethod}>
select color
</a>
{display ? (
<div style={popover}>
<div style={cover} onClick={onCloseMethod} />
<SketchPicker color={color} onChange={onChangeMethod} />
</div>
) : null}
</div>
);
};
export default ColorSelector;
I am using react-slick and I have my own customised arrows. This Slider is NOT an infinite loop and I would like to hide the arrows at the start and end of the loop. So basically at start PrevArrow should be hidden and at the end the NextArrow should be hidden. Which I am trying to set hidden class name depending on state changes. However the class name is not changing although the state is changing correctly. Do you know what's the problem with this code? and how to get it work?
Below is my setting for Slider and the component which renders Slider.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Slider from 'react-slick';
import Arrow from '../slider/Arrow';
export default class CityCarousel extends Component {
constructor(props) {
super(props);
this.state = {
displayLeftArrow: true,
displayRightArrow: true
};
this.slidesToShow = 5;
this.sliderSetting = {
dots: false,
arrows: true,
infinite: false,
initialSlide: 0,
slidesToShow: this.slidesToShow,
slidesToScroll: 1,
speed: 500,
rows: 0,
nextArrow: <Arrow
styleClassName={`city-carousel__right ${
this.state.displayRightArrow ? '' : 'city-carousel__right--hide'
}`}
direction="right"
clickHandler={this.clickHandler}
/>,
prevArrow: <Arrow
styleClassName={`city-carousel__left ${
this.state.displayLeftArrow ? '' : 'city-carousel__left--hide'
}`}
direction="left"
clickHandler={this.clickHandler}
/>,
afterChange: currentSlide => this.setArrowDisplay(currentSlide)
};
}
clickHandler = direction => {
if (direction === 'left') {
this.slider.slickPrev();
} else if (direction === 'right') {
this.slider.slickNext();
}
};
setArrowDisplay = currentSlide => {
const { cityList } = this.props;
const displayLeftArrow = currentSlide !== 0;
const displayRightArrow = currentSlide !== cityList.length - this.slidesToShow;
this.setState({ displayRightArrow, displayLeftArrow });
};
render() {
const { cityList, tours } = this.props;
return (
<div>
<div className="city-selection">
<Slider
ref={c => this.slider = c }
{...this.sliderSetting}
>
{cityList.length > 0 ? this.renderCityList() : null}
</Slider>
</div>
</div>
);
}
}
Here is also code for Arrow component
import React from 'react';
import PropTypes from 'prop-types';
const Arrow = ({ styleClassName, direction, clickHandler }) => {
return(
<div
className={`slick-arrow--${direction} slider-arrow--${direction} ${styleClassName}`}
onClick={e => clickHandler(direction)}
/>
)};
Arrow.propTypes = {
styleClassName: PropTypes.string,
direction: PropTypes.string,
clickHandler: PropTypes.func
};
export default Arrow;
It seems react-slick is not re-rendering <Arrow /> with new props, and it's always with first initialized props setting.
I think the solution is to get <Arrow /> out of slider like this:
<div className="city-selection">
<Arrow
styleClassName={`city-carousel__right ${
this.state.displayRightArrow ? '' : 'city-carousel__right--hide'
}`}
direction="right"
clickHandler={this.clickHandler}
/>
<Arrow
styleClassName={`city-carousel__left ${
this.state.displayLeftArrow ? '' : 'city-carousel__left--hide'
}`}
direction="left"
clickHandler={this.clickHandler}
/>
<Slider
{/* list of items*/}
</Slider>
</div>
you can see how it's working in here
Pass the current slide prop to the component and do a check for the slide you want to trigger hiding the arrow:
function SamplePrevArrow(props) {
const { currentSlide, className, onClick } = props;
if (currentSlide === 0) {
return false;
} else {
return <div className={className} onClick={onClick} />;
}
}
In your this.sliderSettings set nextArrow & prevArrow for your Arrow component do something like this
<Arrow
styleClassName={'YOUR_CLASSES_HERE'}
direction="right"
clickHandler={this.clickHandler}
isHidden={this.state.isHidden}
/>
Where this.state.isHidden is the state variable where you are trying toggle the arrows.
Then in your Arrow component do something like this.
const Arrow = ({ styleClassName, direction, clickHandler, isHidden }) => {
return (
<div
className={`slick-arrow--${direction} slider-arrow--${direction}
${styleClassName}`}
style={{ display: isHidden: 'none': 'block' }}
onClick={e => clickHandler(direction)}
/>
)
};
If you want the best manual controls over the arrow then use a custom arrow.
Create it by using a simple Arrow component.
Set the onClick handler of that component by using a ref in the main Slider component.
const sliderRef = useRef<Slider>(null);
<Slider {...settings} ref={sliderRef}>
Use states to conditionally show or hide arrows.
I wrote a blog post which covers the workflow to achieve similar kind of functionalities you want. You can have a look there.
https://medium.com/#imasharaful/image-slider-with-react-slick-d54a049f043
In 2022, we have boolean arrows, just declare it false in settings.
const settings = {
...,
arrows: false,
};
The callback function (lies in Images component) is responsible for making a state update. I'm passing that function as props to the Modal component, and within it it's being passed into the ModalPanel component.
That function is used to set the state property, display, to false which will close the modal. Currently, that function is not working as intended.
Image Component:
class Images extends Component {
state = {
display: false,
activeIndex: 0
};
handleModalDisplay = activeIndex => {
this.setState(() => {
return {
activeIndex,
display: true
};
});
};
closeModal = () => {
this.setState(() => {
return { display: false };
});
}
render() {
const { imageData, width } = this.props;
return (
<div>
{imageData.resources.map((image, index) => (
<a
key={index}
onClick={() => this.handleModalDisplay(index)}
>
<Modal
closeModal={this.closeModal}
display={this.state.display}
activeIndex={this.state.activeIndex}
selectedIndex={index}
>
<Image
cloudName={CLOUDINARY.CLOUDNAME}
publicId={image.public_id}
width={width}
crop={CLOUDINARY.CROP_TYPE}
/>
</Modal>
</a>
))}
</div>
);
}
}
export default Images;
Modal Component:
const overlayStyle = {
position: 'fixed',
zIndex: '1',
paddingTop: '100px',
left: '0',
top: '0',
width: '100%',
height: '100%',
overflow: 'auto',
backgroundColor: 'rgba(0,0,0,0.9)'
};
const button = {
borderRadius: '5px',
backgroundColor: '#FFF',
zIndex: '10'
};
class ModalPanel extends Component {
render() {
const { display } = this.props;
console.log(display)
const overlay = (
<div style={overlayStyle}>
<button style={button} onClick={this.props.closeModal}>
X
</button>
</div>
);
return <div>{display ? overlay : null}</div>;
}
}
class Modal extends Component {
render() {
const {
activeIndex,
children,
selectedIndex,
display,
closeModal
} = this.props;
let modalPanel = null;
if (activeIndex === selectedIndex) {
modalPanel = (
<ModalPanel display={this.props.display} closeModal={this.props.closeModal} />
);
}
return (
<div>
{modalPanel}
{children}
</div>
);
}
}
export default Modal;
links to code
https://github.com/philmein23/chez_portfolio/blob/chez_portfolio/components/Images.js
https://github.com/philmein23/chez_portfolio/blob/chez_portfolio/components/Modal.js
You're dealing with this modal through a very non-react and hacky way.
Essentially, in your approach, all the modals are always there, and when you click on image, ALL modals display state becomes true, and you match the index number to decide which content to show.
I suspect it's not working due to the multiple children of same key in Modal or Modal Panel.
I strongly suggest you to ditch current approach. Here's my suggestions:
Only a single <Modal/> in Images component.
Add selectedImage state to your Images component. Every time you click on an image, you set selectedImage to that clicked image object.
Pass selectedImage down to Modal to display the content you want.
This way, there is only ONE modal rendered at all time. The content changes dynamically depending on what image you click.
This is the working code I tweaked from your repo:
(I'm not sure what to display as Modal content so I display public_id of image)
Images Component
class Images extends Component {
state = {
display: false,
selectedImage: null
};
handleModalDisplay = selectedImage => {
this.setState({
selectedImage,
display: true
})
};
closeModal = () => {
//shorter way of writing setState
this.setState({display: false})
}
render() {
const { imageData, width } = this.props;
return (
<div>
<Modal
closeModal={this.closeModal}
display={this.state.display}
selectedImage={this.state.selectedImage}
/>
{imageData.resources.map((image, index) => (
<a
//Only use index as key as last resort
key={ image.public_id }
onClick={() => this.handleModalDisplay(image)}
>
<Image
cloudName={CLOUDINARY.CLOUDNAME}
publicId={image.public_id}
width={width}
crop={CLOUDINARY.CROP_TYPE}
/>
</a>
))}
</div>
);
}
}
Modal Component
class Modal extends Component {
render() {
const { display, closeModal, selectedImage } = this.props;
const overlayContent = () => {
if (!selectedImage) return null; //for when no image is selected
return (
//Here you dynamically display the content of modal using selectedImage
<h1 style={{color: 'white'}}>{selectedImage.public_id}</h1>
)
}
const overlay = (
<div style={overlayStyle}>
<button style={button} onClick={this.props.closeModal}>
X
</button>
{
//Show Modal Content
overlayContent()
}
</div>
);
return <div>{display ? overlay : null}</div>;
}
}