Goal: I want Fade's transition to happen after a certain time has passed (ex: 4000 milliseconds).
Here is a fragment of my code:
<Fade in timeout={{ enter: 8000 }}>
<Box display="flex" justifyContent="center">
<IconButton href="https://soundcloud.com/jyillamusic">
<Soundcloud />
</IconButton>
<IconButton href="https://www.instagram.com/justinyum98/">
<Instagram />
</IconButton>
</Box>
</Fade>
Expected result: With enter: 8000, I expect the transition to happen after 8000 milliseconds.
Actual result: The transition starts at 0 milliseconds, finishes after 8000 milliseconds.
Does Fade support delaying the Fade transition by some specified time?
(Looking at Fade's API, I assumed that duration.enteringScreen meant that it was the amount of milliseconds before the transition occurs, but I'm probably mistaken.)
In "Components > Transitions > Zoom demo" of Material UI doc there are some examples in order to achieve it; a bit more elegant than touch the state!
In esence, you can pass a style attribute to Fade component and set the delay (transitionDelay). In the code below we iterate a result array and causes each individual element be showed with a 100ms delay from previous.
{profileItems.map((pi, index) => (
<Fade in={profileItems.length > 0}
timeout={{ enter: 500, exit: 250 }}
style={{ transitionDelay: `${index * 100}ms` }}
key={`asi-${pi.key}-${index}`}>
<span>
<DisplayItem profileItem={pi} />
</span>
</Fade>))}
Fade does not implement a delay feature, however you can manually handle your transition status using in.
in
Show the component; triggers the enter or exit states
type: boolean
default: false
In code you could do :
<Fade in={this.state.in} timeout={{ enter: 8000 }}>
<Box display="flex" justifyContent="center">
<IconButton href="https://soundcloud.com/jyillamusic">
<Soundcloud />
</IconButton>
<IconButton href="https://www.instagram.com/justinyum98/">
<Instagram />
</IconButton>
</Box>
</Fade>
And on display run a timeout to wait and create the delay
this.state = {
in: false
};
setTimeout(() => {
setState({ in: true });
}, 8000);
To piggyback off of sebastienbarbier, you could create a custom transition component that will handle the state for you by simply passing a delay prop.
Here is what the component would look like (I added additional useful props):
const MyFade = ({
children,
in: In = true,
timeout = 1000,
delay = 0
}) => {
const [isIn, setIsIn] = useState(In && delay === 0)
useEffect(() => {
if (delay > 0) {
setTimeout(() => setIsIn(true), delay)
}
})
return (
<Fade in={isIn} timeout={timeout}>
{children}
</Fade>
)
}
And then for your specific issue to have it wait 4000ms:
<MyFade in timeout={{ enter: 8000 }} delay={4000}>
<Box display="flex" justifyContent="center">
<IconButton href="https://soundcloud.com/jyillamusic">
<Soundcloud />
</IconButton>
<IconButton href="https://www.instagram.com/justinyum98/">
<Instagram />
</IconButton>
</Box>
</MyFade>
This seem to work for me without Fade:
const [open, setOpen] = React.useState(false);
<List>
{[
'Inbox',
'Starred',
'Send email',
'Drafts',
'foo',
'bar',
'baz',
].map((text, index) => (
<ListItem
key={text}
sx={{
transitionProperty: 'all',
transitionDuration: '750ms',
...(open && {
opacity: 1,
transform: 'translateY(0)',
transitionDelay: `${index * 150}ms`,
}),
...(!open && {
opacity: 0,
transform: 'translateY(100%)',
}),
}}
>
or
<ListItem
key={text}
style={{ transitionDelay: open ? `${index * 150}ms` : '0ms' }}
>
Related
I am trying to add a play/pause control in my react project. Currently, I am using react-slick to build a carousel on my web. I have a problem with my customized button as shown below. I don't know why as I am pressing the button, the console logs the tag, but the video on my web didn't change its current status.
Here is part of my code:
const RootStyle = styled('div')(({ theme }) => ({
'& .slick-slide': {
float: theme.direction === 'rtl' ? 'right' : 'left',
'&:focus': { outline: 'none' },
},
}));
const videoElement = useRef(null);
const [playerState, setPlayerState] = useState({isPlaying: true});
const togglePlay = () => {
setPlayerState({
isPlaying: !playerState.isPlaying,
});
};
useEffect(() => {
playerState.isPlaying? videoElement.current.play() : videoElement.current.pause();
console.log(videoElement.current);
}, [playerState.isPlaying, videoElement]);
return (
<RootStyle >
<Box sx={{ p: 0 }} >
<Box sx={{ overflow: 'hidden', position: 'relative', border: '1px solid' }} >
<Slider {...settings1} asNavFor={nav2} ref={slider1} >
{data.map((url) => (
url.videoDurationSeconds != null &&
<Box position={'relative'}>
<video
key={url.id}
src={url.url}
ref={videoElement}
height={'100%'}
width={'100%'}
max-height={480}
max-width={870}
autoPlay
loop
muted
/>
<Typography {...styles}>
{url.videoDurationSeconds}
</Typography>
<button onClick={togglePlay} className='play-button' >
<BarChartRoundedIcon style={{color:'white'}} height={'100%'} width={'100%'}/>
</button>
</Box> ||
<LazyImage src={url.url} alt={url.id} height={480} width={870} loading="eager" objectFit="contain" />
))}
</Slider>
</Box>
</Box>
</RootStyle>
By the way, sometimes after I clicked the button several times, the console showed these, I don't know if this is related to my problem:
Error
I am using modal with framer motion, the initial animation for the modal works fine, but on the exit it does not work at all. The modal disappears immediately for some reason.
This is where I am opening the modal:
const Agenda = () => {
const [modalOpen, setModalOpen] = useState(false);
const close = () => setModalOpen(false);
const open = () => setModalOpen(true);
return (
<>
<AnimatePresence
initial={false}
exitBeforeEnter={true}
>
{modalOpen && (
<Modal modalOpen={modalOpen} handleClose={close}>
<AgendaContent />
</Modal>
)}
</AnimatePresence>
</>
);
};
export default Agenda;
And here is the modal itself:
const newspaper = {
hidden: {
transform: "scale(0) rotate(720deg)",
opacity: 0,
transition: {
delay: 0.3,
},
},
visible: {
transform: " scale(1) rotate(0deg)",
opacity: 1,
transition: {
duration: 0.5,
},
},
exit: {
transform: "scale(0) rotate(-720deg)",
opacity: 0,
transition: {
duration: 0.3,
},
},
};
const Modal = ({ handleClose, children }) => {
return (
<Backdrop onClick={handleClose}>
<motion.div
drag
onClick={(e) => e.stopPropagation()}
className="modal"
variants={newspaper}
initial="hidden"
animate="visible"
exit="exit"
>
<img
className="close-icon"
src={CloseIcon}
alt="close"
onClick={handleClose}
/>
{children}
</motion.div>
</Backdrop>
);
};
export default Modal;
I followed this tutorial. I am not sure what I am doing wrong here, I am using the AnimatePresence there, any idea what could be wrong?
From the AnimatePresence docs:
Note: Direct children must each have a unique key prop so
AnimatePresence can track their presence in the tree.
It's frustratingly easy to forget even if you know about the requirement.
Something like this should work:
<AnimatePresence
initial={false}
exitBeforeEnter={true}
>
{modalOpen && (
<Modal key="modal" modalOpen={modalOpen} handleClose={close}>
<AgendaContent />
</Modal>
)}
</AnimatePresence>
I'm trying to test if a button toggle works as expected.
This is my code.
<>
<IconButton color="secondary" data-testid="HelpIconButton" ref={anchorRef} onClick={handleToggle}>
<Help />
</IconButton>
<Popper {...{ open }} anchorEl={anchorRef.current} transition disablePortal data-testid="HelpIconPopper">
{({ TransitionProps, placement }) => (
<Grow {...TransitionProps} style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} onKeyDown={handleListKeyDown}>
{menuItems.map(({ onClick, label }, index) => (
<MenuItem key={index} {...{ onClick }}>
{label}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
In the browser, clicking the browser works as expected. Clicking on the IconButton toggles the Popper, removing and adding it into the DOM.
On test however, firing a click event placed the Popper in the DOM but its not removed when fired again. Hence, the popper isn't toggled.
Here is the test Code.
it('Ensures the help menu toggles when the help button is clicked.', () => {
const { getByTestId, queryByTestId } = render(<HelpButton />);
expect(queryByTestId('HelpIconPopper')).not.toBeInTheDocument();
fireEvent.click(getByTestId('HelpIconButton'));
expect(queryByTestId('HelpIconPopper')).toBeInTheDocument();
fireEvent.click(getByTestId('HelpIconButton'));
expect(queryByTestId('HelpIconPopper')).not.toBeInTheDocument();
});
A work around currently in use is using fireEvent.doubleClick
it('Ensures the help menu toggles when the help button is clicked.', () => {
const { getByTestId, queryByTestId } = render(<HelpButton />);
expect(queryByTestId('HelpIconPopper')).not.toBeInTheDocument();
fireEvent.doubleClick(getByTestId('HelpIconButton'));
expect(queryByTestId('HelpIconPopper')).not.toBeInTheDocument();
});
The second test passes.
I am creating a simple carousel with react and I noticed that my index gets call multiple times and I don't understand why, here is my snippet of code here also here is a full version https://codesandbox.io/s/small-bash-4l7ix?file=/src/index.js
...
const pages = [
React.forwardRef((props, ref) => (
<animated.div ref={ref} style={{ ...props.style, background: 'lightpink' }}>
A
</animated.div>
)),
React.forwardRef((props, ref) => (
<animated.div ref={ref} style={{ ...props.style, background: 'lightblue' }}>
B
</animated.div>
)),
React.forwardRef((props, ref) => (
<animated.div ref={ref} style={{ ...props.style, background: 'lightgreen' }}>
C
</animated.div>
)),
]
export default function App() {
const [index, set] = useState(0)
const [containerRef, containerSize] = useDimensions()
const transitions = useTransition(index, p => p, {
from: { opacity: 0, transform: 'translate3d(100%,0,0)' },
enter: { opacity: 1, transform: 'translate3d(0%,0,0)' },
leave: { opacity: 0, transform: 'translate3d(-50%,0,0)' },
})
const divStyle = {
height: `${containerSize.height}px`,
}
console.log(index)
return (
<>
<button className={`btn ${index === 0 ? 'btn--active' : ''}`} onClick={() => set(0)}>
Slide 1
</button>
<button className={`btn ${index === 1 ? 'btn--active' : ''}`} onClick={() => set(1)}>
Slide 2
</button>
<button className={`btn ${index === 2 ? 'btn--active' : ''}`} onClick={() => set(2)}>
Slide 3
</button>
<div style={divStyle} className="simple-trans-main">
{transitions.map(({ item, props, key }) => {
const Page = pages[item]
return <Page ref={containerRef} key={key} style={props} />
})}
</div>
<p> Lorem ipusum</p>
</>
)
}
...
In order to transition components in and out the useTransition hook from react-spring keeps track of component instances for you. These extra renders are caused by overlapping mounting and unmounting of nodes.
For example:
Start at 'Slide 1'
Click 'Slide 2'
Library mounts 'Slide 2' // Triggers rerender
Library starts to transition 'Slide 1' out
Library unmounts 'Slide 1' // Triggers rerender
Each transition is pushed into the array and the library animates them in order. So you can trigger multiple overlapping animations simultaneously.
Checkout the React DevTools in the codesandbox and you will see the nodes mounting and unmounting.
This is because of useTransition.
That creates an extra render of sorts, that is why you see index printed multiple times.
I have removed the useTransition and you can check it prints only on index change.
Check below.
I am trying to develop a generic filter component which can have many fields to filter on like color,
size, price range etc and each field might have different types of elements like color may have
checkboxes, radio button and price range might have input element, dropdown etc. To support such varied
cases, I tried to go with this pattern but here I have to iterate the same things multiple times.
I am not sure of this data structure. If anyone has suggestion please help me to improve this code but
the main problem here is "multiple iteration". How can i improve this code?
const filterParams = {
field: {
id : 1, label : 'Field', content: <FieldFilter />
},
employee: {
id : 1, label : 'Employee', content: <Employee />
}
}
<Filter filterParams={filterParams} activeFilterParam="field" />
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
return (
<>
<Button secondary icon={filter} onClick={() => setShow(!show)}>Filter</Button>
{show && (
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List
render={() => {
return (
Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
)
}))
}} />
<Tabs.Panels>
{Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
)
})}
</Tabs.Panels>
</Tabs>
</Card.Content>
<Card.Footer>
<Button>
<Button.Content style={{ marginRight: 10 }}>Save</Button.Content>
<Button.Content secondary onClick={()=>setShow(!show)}>Cancel</Button.Content>
</Button>
</Card.Footer>
</Card>
)}
</>
)
}
If you're not liking the multiple calls to Object.keys(filterParams).map, you could move the loop to the top of the component function. Something like the below might work:
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
const {tabs, panels} = Object.keys(filterParams)
.reduce((acc, filterParam) => {
acc.tabs.push(
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
);
acc.panels.push(
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
);
return acc;
}, { tabs: [], panels: [] });
return (
...
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List render={() => tabs} />
<Tabs.Panels>
{panels}
</Tabs.Panels>
</Tabs>
...
</Card>
...
)
}
Note that I haven't run this - it likely won't be quite right, but should give the general idea...