Animation for react-select menu open/close - reactjs

Is it possible to animate opening and closing the menu? The menu node is removed or attached to DOM tree on opening/closing so this interferes with css animation. Is there a work around for this?

For opening animation, it's relatively easy, you only have to add the animation to the <Menu/> component using css.
The following example use fade-in/out animation
#keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(2rem);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.menu {
animation: fadeIn 0.2s ease-in-out;
}
In your render method
<Select
{...}
components={{
Menu: (props) => <components.Menu {...props} className="menu" />
}}
/>
The closing animation is a bit trickier because when the close event fires, the menu element is removed immediately, leaving no time for the closing animation to run.
We need to change the close event behavior a little bit. The strategy is to just let the menu close abruptly as normal. But before that, we make another clone of the menu, and that clone will run the closing animation and remove itself when the animation finished.
// generate unique ID for every Select components
const [uniqueId] = React.useState(
() => 'select_' + Math.random().toFixed(5).slice(2),
);
return (
<Select
id={uniqueId}
onMenuClose={() => {
const menuEl = document.querySelector(`#${uniqueId} .menu`);
const containerEl = menuEl?.parentElement;
const clonedMenuEl = menuEl?.cloneNode(true);
if (!clonedMenuEl) return; // safeguard
clonedMenuEl.classList.add("menu--close");
clonedMenuEl.addEventListener("animationend", () => {
containerEl?.removeChild(clonedMenuEl);
});
containerEl?.appendChild(clonedMenuEl!);
}}
{...}
/>
);
Don't forget to attach the closing animation to the right css class. In this case menu--close
#keyframes fadeOut {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(2rem);
}
}
.menu--close {
animation: fadeOut 0.2s ease-in-out;
}
In the end, you will have something like this
Live Demo

Another way is to use menuIsOpen props instead of cloning.
const [isMenuOpen, setIsMenuOpen] = useState(false)
const openMenuHandler = async () => {
await setIsMenuOpen(true)
const menu = document.querySelector(`#select .menu`)
menu.style.opacity = '1'
}
const closeMenuHandler = () => {
const menu = document.querySelector(`#select .menu`)
menu.style.opacity = '0'
setTimeout(() => {
setIsMenuOpen(false)
}, 400)
}
<Select
menuIsOpen={isMenuOpen}
onMenuOpen={() => {openMenuHandler()}}
onMenuClose={() => {closeMenuHandler()}}
/>

Related

How to use setInterval to change opacity back and forth in React

I want to create a flicker effect in React, so my down arrow can flickering , I use useEffect with setInterval to change a state from 0 to 1 , and render it with react icons, but after few tries it still not working. can anybody tell me how to achieve this effect?
useEffect(() => {
const opChange = setInterval(() => {
if (scroll) {
scrollOpacity = 0;
setScroll(!scroll);
} else {
scrollOpacity = 1;
setScroll(!scroll);
}
}, 2000);
return () => clearInterval(opChange);
}, [scroll]);
<HiOutlineChevronDown
className={`text-white mx-auto opacity-${scrollOpacity}`}
/>
tried several different method already, nothing works.
You can use css animation to create same effect without change state.
.cursor {
animation-name: blink;
animation-duration: 1s;
animation-iteration-count: infinite;
}
#keyframes blink {
from {
opacity: 0;
}
50% {
opacity: 1;
}
to {
opacity: 0;
}
}
Example: custom cursor blink effect

It seems you are interpolating a keyframe declaration (bKXFnj) into an untagged string

Want to add animation in useEffect hook when DOM is loaded with styled-components but this :
Error: It seems you are interpolating a keyframe declaration (bKXFnj) into an untagged string.
This was supported in styled-components v3, but is not longer supported in v4 as keyframes are
now injected on-demand. Please wrap your string in the css\`\` helper which ensures the styles
are injected correctly. See https://www.styled-components.com/docs/api#css
The question is How can I add animation using syntax element.style.animation using styled-components?
code:
const showHeading = keyframes`
from {
opacity: 0;
transform: translateY(60px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
const animation = css`
animation: ${showHeading} 0.7s ease-out 0.4s;
`;
const handleLoadAnimation = () => {
let ypos = ref.current.getBoundingClientRect().top;
if (ypos < window.innerHeight * 0.9) {
ref.current.style.animation = `${animation}`;
}
};
useEffect(() => {
window.addEventListener('load', handleLoadAnimation);
window.addEventListener('scroll', handleScrollAnimation);
return () => window.removeEventListener('scroll', handleScrollAnimation);
});

Modal component renders Twice on open

I'm using react-spring to animate a Modal based on #reach/dialog. The Modal can have any children. In the children I'm fetching some data based on some prop.
The problem is that the fetch call is made two times on opening the modal. I think it has probably to do with how I'm managing the state and that is causing re-renders.
I'v tried memoizing the children inside the modal and it didn't work, so I think that the problem is outside of the Modal component.
Here is something close to my code and how it is working https://codesandbox.io/s/loving-liskov-1xouh
EDIT: I already know that if I remove the react-spring animation the double rendering doesn't happen, but I want to try keeping the animation intact.
Do you think you can help me to identify where is the bug? (Also some tips on good practice with hooks are highly appreciated).
it renders three times because your return component has transitions.map since you have three item inside the
from: { opacity: 0 }
enter: { opacity: 1 }
leave: { opacity: 0 }
the {children} was called two times when the isOpen is true
you can fix the issue with just removing the from: { opacity: 0 } and leave: { opacity: 0 }
so change your modal.js => transitions
const transitions = useTransition(isOpen, null, {
enter: { opacity: 1 }
});
I checked and it is rendered twice because of animation in a Modal component when an animation is finished, modal is rendered second time when I commented out fragment responsible for animation, Modal renders only once.
const Modal = ({ children, toggle, isOpen }) => {
// const transitions = useTransition(isOpen, null, {
// from: { opacity: 0 },
// enter: { opacity: 1 },
// leave: { opacity: 0 }
// });
console.log("render");
const AnimatedDialogOverlay = animated(DialogOverlay);
// return transitions.map(
// ({ item, key, props }) =>
// item && (
return (
<AnimatedDialogOverlay isOpen={isOpen}>
<DialogContent>
<div
style={{
display: `flex`,
width: `100%`,
alignItems: `center`,
justifyContent: `space-between`
}}
>
<h2 style={{ margin: `4px 0` }}>Modal Title</h2>
<button onClick={toggle}>Close</button>
</div>
{children}
</DialogContent>
</AnimatedDialogOverlay>
);
// )
// );
};
The problem is, that at the end of the animation AnotherComponent remounts. I read similar problems about react-spring. One way could be, that you lift out the state from AnotherComponent to the index.js. This way the state will not lost at remount and you can prevent refetching the data.
const AnotherComponent = ({ url, todo, setTodo }) => {
useEffect(() => {
if (todo.length === 0) {
axios.get(url).then(res => setTodo(res.data));
}
});
....
}
Here is my version: https://codesandbox.io/s/quiet-pond-idyee

react-spring Transition does not animate enter state

I'm making a Collapse component using react-spring which receives children and a boolean collapsed prop.
It's rather basic, but for some reason the animation when children are mounted never runs and at the same time leave animation works good.
Here's what the component looks like
const baseStyles = {
overflow: "hidden"
};
const openStyles = {
height: "auto"
};
const collapsedStyles = {
height: 0
};
const animationConfig = {
duration: 1000
};
const Collapse = ({ collapsed, children, ...props }) => {
return (
<Transition
items={collapsed}
native
config={animationConfig}
from={baseStyles}
enter={openStyles}
leave={collapsedStyles}
// onFrame={console.log}
{...props}
>
{collapsed => !collapsed
? style => <animated.div style={style} children={children} />
: null
}
</Transition>
);
};
And here's working code https://codesandbox.io/s/459p84ky4
Am I doing something wrong or is it a bug in react spring?
You need to understand from and enter you are not applying anything in both props, means opacity is always 1 and thus animation is not working
from means what it should be at the initial stage and enter means what it should be at rendering.
So, you need to set opacity 0 in from and set it to 1 inside enter
const baseStyles = {
background: "rgba(255,0,0,.2)",
overflow: "hidden",
opacity:0
};
const openStyles = {
height: "auto",
opacity: 1
};
Edit:
If you want height form zero to auto then you need to first set height to 0 in from
const baseStyles = {
background: "rgba(255,0,0,.2)",
overflow: "hidden",
height: 0
};
const openStyles = {
height: "auto",
opacity: 1
};
Demo

Rotate arrow indicator in React-select v2

I'm using React Select v2 in my project with Styled Components and I need to be able to turn the arrow indicator upside down when the menu is open, which was supported in v1.
I kinda managed to do it by doing this:
css`
&.react-select__control--is-focused {
& .react-select__indicators {
& .react-select__dropdown-indicator {
transform: rotate(180deg);
}
}
}
`;
Problem is that, if I press the arrow to open the menu and click on it again to close it, the arrow stays upside down because the select is still focused, which feels a bit weird in terms of UIX.
Is there a proper way to rotate it based on the state of the menu? I looked for something in the documentation but I couldn't find it.
Maybe I missed it, if someone could point me in the right direction, that'd be awesome!
Thanks!
Technically you can use the style-in-JS props of the v2. Like the following example:
dropdownIndicator: (base, state) => ({
...base,
transition: 'all .2s ease',
transform: state.isFocused ? 'rotate(180deg)' : null
})
It seems that the isFocused state isn't bind with the isMenuOpen state but with the real focus state of the container.
A solution is to set closeMenuOnSelect={false} so the user would have to click outside the select and your arrow will flip back.
Or you could change the className props using onMenuOpen and onMenuClose by adding a specific suffix to target your animation.
UPDATE
You can directly access the menuOpen props via the state so no need to manually add class like the following:
dropdownIndicator: (base, state) => ({
...base,
transition: 'all .2s ease',
transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : null
})
PLEASE NOTE THAT
In react-select v2.3 a control--menu-is-open has been added directly in the code.
This worked for me
<select styles={{
dropdownIndicator: (provided, state) => ({
...provided,
transform: state.selectProps.menuIsOpen && "rotate(180deg)"
})
}}
/>
So, based on Laura's response, my solution was to use the onMenuClose and onMenuOpen to set the state of a property in my styled component.
const indicatorStyle = (props: StyledSelectProps & DropdownProps<{}>) => css`
& .react-select__indicators {
& .react-select__dropdown-indicator {
transition: all .2s ease;
transform: ${props.isOpen && "rotate(180deg)"};
}
}
`;
This function is called inside of my styled component's css.
And then in the component I call my styled component, I control the state:
export class Dropdown<TValue> extends React.Component<DropdownProps<TValue>> {
public state = { isOpen: false };
private onMenuOpen = () => this.setState({ isOpen: true });
private onMenuClose = () => this.setState({ isOpen: false });
public render() {
const { ...props } = this.props;
const { isOpen } = this.state;
return (
<StyledSelect {...props} isOpen={isOpen} onMenuOpen={this.onMenuOpen} onMenuClose={this.onMenuClose} />
);
}
}
A bit convoluted but it works for now.
This worked for me.
dropdownIndicator: (base, state) => ({
...base,
transform: state.selectProps.menuIsOpen ? 'rotate(-90deg)' : 'rotate(0)',
transition: '250ms',
}),
i solved this issue like this.
mostly you have to play arround with these in your css to do stuff on certain conditions
--is-focused
--menu-is-open
--is-disabled
--is-selected
styled component css
.paginatorPageSizeCustomSelectPreffix__indicator {
svg {
color: var(--black);
height: 18px;
width: 18px;
transform: rotate(180deg);
transition: 0.2s;
}
}
.paginatorPageSizeCustomSelectPreffix__control--menu-is-open {
.paginatorPageSizeCustomSelectPreffix__indicator {
svg {
color: var(--black);
height: 18px;
width: 18px;
transform: rotate(360deg);
transition: 0.2s;
}
}
}
}

Resources