Skip to content

Commit 6bc1f57

Browse files
authored
Merge branch 'master' into documentation-improvements-21-10-24
2 parents b60ccdf + f6c7802 commit 6bc1f57

File tree

8 files changed

+111
-8
lines changed

8 files changed

+111
-8
lines changed

.changeset/silver-zebras-sing.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-select': minor
3+
---
4+
5+
Fix animated MultiValue transitions when being removed and change method used to generate unqiue keys for Option components. Closes #4844 , closes #4602

.changeset/sweet-ways-listen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-select': patch
3+
---
4+
5+
Make input container css re-compute whenever input value changes due to a bug from `@emotion/react` in development env.

packages/react-select/src/Select.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1648,6 +1648,7 @@ export default class Select<
16481648
if (isMulti) {
16491649
return selectValue.map((opt, index) => {
16501650
const isOptionFocused = opt === focusedValue;
1651+
const key = `${this.getOptionLabel(opt)}-${this.getOptionValue(opt)}`;
16511652

16521653
return (
16531654
<MultiValue
@@ -1659,7 +1660,7 @@ export default class Select<
16591660
}}
16601661
isFocused={isOptionFocused}
16611662
isDisabled={isDisabled}
1662-
key={`${this.getOptionValue(opt)}-${index}`}
1663+
key={key}
16631664
index={index}
16641665
removeProps={{
16651666
onClick: () => this.removeValue(opt),

packages/react-select/src/animated/ValueContainer.tsx

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { ReactElement } from 'react';
1+
import React, { useEffect, useState, ReactElement, ReactNode } from 'react';
22
import { TransitionGroup } from 'react-transition-group';
33
import { ValueContainerProps } from '../components/containers';
44
import { GroupBase } from '../types';
@@ -11,12 +11,92 @@ export type ValueContainerComponent = <
1111
props: ValueContainerProps<Option, IsMulti, Group>
1212
) => ReactElement;
1313

14+
interface IsMultiValueContainerProps extends ValueContainerProps {
15+
component: ValueContainerComponent;
16+
}
17+
1418
// make ValueContainer a transition group
1519
const AnimatedValueContainer =
1620
(WrappedComponent: ValueContainerComponent) =>
1721
<Option, IsMulti extends boolean, Group extends GroupBase<Option>>(
1822
props: ValueContainerProps<Option, IsMulti, Group>
1923
) =>
20-
<TransitionGroup component={WrappedComponent} {...(props as any)} />;
24+
props.isMulti ? (
25+
<IsMultiValueContainer component={WrappedComponent} {...(props as any)} />
26+
) : (
27+
<TransitionGroup component={WrappedComponent} {...(props as any)} />
28+
);
29+
30+
const IsMultiValueContainer = ({
31+
component,
32+
...restProps
33+
}: IsMultiValueContainerProps) => {
34+
const multiProps = useIsMultiValueContainer(restProps);
35+
36+
return <TransitionGroup component={component} {...(multiProps as any)} />;
37+
};
38+
39+
const useIsMultiValueContainer = ({
40+
children,
41+
...props
42+
}: ValueContainerProps) => {
43+
const {
44+
isMulti,
45+
hasValue,
46+
innerProps,
47+
selectProps: { components, controlShouldRenderValue },
48+
} = props;
49+
50+
const [cssDisplayFlex, setCssDisplayFlex] = useState(
51+
isMulti && controlShouldRenderValue && hasValue
52+
);
53+
const [removingValue, setRemovingValue] = useState(false);
54+
55+
useEffect(() => {
56+
if (hasValue && !cssDisplayFlex) {
57+
setCssDisplayFlex(true);
58+
}
59+
}, [hasValue, cssDisplayFlex]);
60+
61+
useEffect(() => {
62+
if (removingValue && !hasValue && cssDisplayFlex) {
63+
setCssDisplayFlex(false);
64+
}
65+
setRemovingValue(false);
66+
}, [removingValue, hasValue, cssDisplayFlex]);
67+
68+
const onExited = () => setRemovingValue(true);
69+
70+
const childMapper = (child: ReactNode) => {
71+
if (isMulti && React.isValidElement(child)) {
72+
// Add onExited callback to MultiValues
73+
if (child.type === components.MultiValue) {
74+
return React.cloneElement(child, { onExited });
75+
}
76+
// While container flexed, Input cursor is shown after Placeholder text,
77+
// so remove Placeholder until display is set back to grid
78+
if (child.type === components.Placeholder && cssDisplayFlex) {
79+
return null;
80+
}
81+
}
82+
return child;
83+
};
84+
85+
const newInnerProps = {
86+
...innerProps,
87+
style: {
88+
...innerProps?.style,
89+
display: cssDisplayFlex ? 'flex' : 'grid',
90+
},
91+
};
92+
93+
const newProps = {
94+
...props,
95+
innerProps: newInnerProps,
96+
children: React.Children.toArray(children).map(childMapper),
97+
};
98+
99+
return newProps;
100+
};
21101

22102
export default AnimatedValueContainer;

packages/react-select/src/animated/transitions.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,13 @@ export class Collapse extends Component<CollapseProps, CollapseState> {
127127
getTransition = (state: TransitionStatus) => this.transition[state];
128128

129129
render() {
130-
const { children, in: inProp } = this.props;
130+
const { children, in: inProp, onExited } = this.props;
131+
const exitedProp = () => {
132+
if (this.nodeRef.current && onExited) {
133+
onExited(this.nodeRef.current);
134+
}
135+
};
136+
131137
const { width } = this.state;
132138

133139
return (
@@ -136,6 +142,7 @@ export class Collapse extends Component<CollapseProps, CollapseState> {
136142
mountOnEnter
137143
unmountOnExit
138144
in={inProp}
145+
onExited={exitedProp}
139146
timeout={this.duration}
140147
nodeRef={this.nodeRef}
141148
>

packages/react-select/src/components/Input.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,17 @@ export const inputCSS = <
3939
Group extends GroupBase<Option>
4040
>({
4141
isDisabled,
42+
value,
4243
theme: { spacing, colors },
4344
}: InputProps<Option, IsMulti, Group>): CSSObjectWithLabel => ({
4445
margin: spacing.baseUnit / 2,
4546
paddingBottom: spacing.baseUnit / 2,
4647
paddingTop: spacing.baseUnit / 2,
4748
visibility: isDisabled ? 'hidden' : 'visible',
4849
color: colors.neutral80,
50+
// force css to recompute when value change due to @emotion bug.
51+
// We can remove it whenever the bug is fixed.
52+
transform: value ? 'translateZ(0)' : '',
4953
...containerStyle,
5054
});
5155

packages/react-select/src/components/containers.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,10 @@ export const valueContainerCSS = <
8686
theme: { spacing },
8787
isMulti,
8888
hasValue,
89+
selectProps: { controlShouldRenderValue },
8990
}: ValueContainerProps<Option, IsMulti, Group>): CSSObjectWithLabel => ({
9091
alignItems: 'center',
91-
display: isMulti && hasValue ? 'flex' : 'grid',
92+
display: isMulti && hasValue && controlShouldRenderValue ? 'flex' : 'grid',
9293
flex: 1,
9394
flexWrap: 'wrap',
9495
padding: `${spacing.baseUnit / 2}px ${spacing.baseUnit * 2}px`,

yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10250,9 +10250,9 @@ mixin-deep@^1.2.0:
1025010250
is-extendable "^1.0.1"
1025110251

1025210252
mixme@^0.5.0:
10253-
version "0.5.1"
10254-
resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.1.tgz#b3da79a563b2da46efba9519830059e4c2a9e40f"
10255-
integrity sha512-NaeZIckeBFT7i0XBEpGyFcAE0/bLcQ9MHErTpnU3bLWVE5WZbxG5Y3fDsMxYGifTo5khDA42OquXCC2ngKJB+g==
10253+
version "0.5.4"
10254+
resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.4.tgz#8cb3bd0cd32a513c161bf1ca99d143f0bcf2eff3"
10255+
integrity sha512-3KYa4m4Vlqx98GPdOHghxSdNtTvcP8E0kkaJ5Dlh+h2DRzF7zpuVVcA8B0QpKd11YJeP9QQ7ASkKzOeu195Wzw==
1025610256

1025710257
mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.1:
1025810258
version "0.5.5"

0 commit comments

Comments
 (0)