Skip to content

Commit

Permalink
Merge pull request #4860 from ebonow/Fixes-for-AnimatedValueContainer…
Browse files Browse the repository at this point in the history
…-and-unique-keys-for-options

Fixes for animated value container and unique keys for options
  • Loading branch information
ebonow committed Oct 17, 2021
2 parents ca5b453 + 5c465f7 commit f6c7802
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-zebras-sing.md
@@ -0,0 +1,5 @@
---
'react-select': minor
---

Fix animated MultiValue transitions when being removed and change method used to generate unqiue keys for Option components. Closes #4844 , closes #4602
3 changes: 2 additions & 1 deletion packages/react-select/src/Select.tsx
Expand Up @@ -1648,6 +1648,7 @@ export default class Select<
if (isMulti) {
return selectValue.map((opt, index) => {
const isOptionFocused = opt === focusedValue;
const key = `${this.getOptionLabel(opt)}-${this.getOptionValue(opt)}`;

return (
<MultiValue
Expand All @@ -1659,7 +1660,7 @@ export default class Select<
}}
isFocused={isOptionFocused}
isDisabled={isDisabled}
key={`${this.getOptionValue(opt)}-${index}`}
key={key}
index={index}
removeProps={{
onClick: () => this.removeValue(opt),
Expand Down
84 changes: 82 additions & 2 deletions packages/react-select/src/animated/ValueContainer.tsx
@@ -1,4 +1,4 @@
import React, { ReactElement } from 'react';
import React, { useEffect, useState, ReactElement, ReactNode } from 'react';
import { TransitionGroup } from 'react-transition-group';
import { ValueContainerProps } from '../components/containers';
import { GroupBase } from '../types';
Expand All @@ -11,12 +11,92 @@ export type ValueContainerComponent = <
props: ValueContainerProps<Option, IsMulti, Group>
) => ReactElement;

interface IsMultiValueContainerProps extends ValueContainerProps {
component: ValueContainerComponent;
}

// make ValueContainer a transition group
const AnimatedValueContainer =
(WrappedComponent: ValueContainerComponent) =>
<Option, IsMulti extends boolean, Group extends GroupBase<Option>>(
props: ValueContainerProps<Option, IsMulti, Group>
) =>
<TransitionGroup component={WrappedComponent} {...(props as any)} />;
props.isMulti ? (
<IsMultiValueContainer component={WrappedComponent} {...(props as any)} />
) : (
<TransitionGroup component={WrappedComponent} {...(props as any)} />
);

const IsMultiValueContainer = ({
component,
...restProps
}: IsMultiValueContainerProps) => {
const multiProps = useIsMultiValueContainer(restProps);

return <TransitionGroup component={component} {...(multiProps as any)} />;
};

const useIsMultiValueContainer = ({
children,
...props
}: ValueContainerProps) => {
const {
isMulti,
hasValue,
innerProps,
selectProps: { components, controlShouldRenderValue },
} = props;

const [cssDisplayFlex, setCssDisplayFlex] = useState(
isMulti && controlShouldRenderValue && hasValue
);
const [removingValue, setRemovingValue] = useState(false);

useEffect(() => {
if (hasValue && !cssDisplayFlex) {
setCssDisplayFlex(true);
}
}, [hasValue, cssDisplayFlex]);

useEffect(() => {
if (removingValue && !hasValue && cssDisplayFlex) {
setCssDisplayFlex(false);
}
setRemovingValue(false);
}, [removingValue, hasValue, cssDisplayFlex]);

const onExited = () => setRemovingValue(true);

const childMapper = (child: ReactNode) => {
if (isMulti && React.isValidElement(child)) {
// Add onExited callback to MultiValues
if (child.type === components.MultiValue) {
return React.cloneElement(child, { onExited });
}
// While container flexed, Input cursor is shown after Placeholder text,
// so remove Placeholder until display is set back to grid
if (child.type === components.Placeholder && cssDisplayFlex) {
return null;
}
}
return child;
};

const newInnerProps = {
...innerProps,
style: {
...innerProps?.style,
display: cssDisplayFlex ? 'flex' : 'grid',
},
};

const newProps = {
...props,
innerProps: newInnerProps,
children: React.Children.toArray(children).map(childMapper),
};

return newProps;
};

export default AnimatedValueContainer;
9 changes: 8 additions & 1 deletion packages/react-select/src/animated/transitions.tsx
Expand Up @@ -127,7 +127,13 @@ export class Collapse extends Component<CollapseProps, CollapseState> {
getTransition = (state: TransitionStatus) => this.transition[state];

render() {
const { children, in: inProp } = this.props;
const { children, in: inProp, onExited } = this.props;
const exitedProp = () => {
if (this.nodeRef.current && onExited) {
onExited(this.nodeRef.current);
}
};

const { width } = this.state;

return (
Expand All @@ -136,6 +142,7 @@ export class Collapse extends Component<CollapseProps, CollapseState> {
mountOnEnter
unmountOnExit
in={inProp}
onExited={exitedProp}
timeout={this.duration}
nodeRef={this.nodeRef}
>
Expand Down
3 changes: 2 additions & 1 deletion packages/react-select/src/components/containers.tsx
Expand Up @@ -86,9 +86,10 @@ export const valueContainerCSS = <
theme: { spacing },
isMulti,
hasValue,
selectProps: { controlShouldRenderValue },
}: ValueContainerProps<Option, IsMulti, Group>): CSSObjectWithLabel => ({
alignItems: 'center',
display: isMulti && hasValue ? 'flex' : 'grid',
display: isMulti && hasValue && controlShouldRenderValue ? 'flex' : 'grid',
flex: 1,
flexWrap: 'wrap',
padding: `${spacing.baseUnit / 2}px ${spacing.baseUnit * 2}px`,
Expand Down

0 comments on commit f6c7802

Please sign in to comment.