Skip to content

Instantly share code, notes, and snippets.

@jhalborg
Last active February 14, 2018 10:55
Show Gist options
  • Save jhalborg/7caaa657c57cd6ff577e731895771872 to your computer and use it in GitHub Desktop.
Save jhalborg/7caaa657c57cd6ff577e731895771872 to your computer and use it in GitHub Desktop.
Gist describing issue with Animated.Value in redux

The problem

Following https://medium.com/appandflow/react-native-collapsible-navbar-e51a049b560a , but trying to move the animated value to redux to reuse it for animations of multiple components, as well as in different tabs on my react-navigation tab router.

However, I get a RangeError: Maximum call stack size exceeded redbox.

Digging deeper

If I remove the style transform and clampedScroll from the state of the Headers, I instead get Invariant Viloation: A state mutation was detected between dispatches from the lib redux-immutable-state-invariant, which detects erroneous mutations of the redux state.

This, in turn, can be fixed by removing the onScroll listener from the FlatList, in which case the app runs fine again - but no animations are present, obviously.

I can't explain the first error - maybe there's something about Animated.diffClamp or Animated.interpolate I don't understand/know yet. Or maybe it's just a symptom of the second error?

The second error seems to lie with the nativeEvent not being able to be mapped to a redux state prop. I'm under the impression this should be possible, as Redux is just a HOC. Am I wrong?

export const defaultState: ICommonState = {
scrollAnim: new Animated.Value(0),
...
};
export const commonReducer = (
state: ICommonState = defaultState,
action: CommonStateAction
): ICommonState => {
switch (action.type) {
...
default:
return state;
}
};
export default commonReducer;
const AnimatedFlatlist = Animated.createAnimatedComponent(FlatList);
...
render() {
return (
<View style={styles.container}>
<AnimatedFlatlist
scrollEventThrottle={16}
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: { y: this.props.commonState.scrollAnim },
},
},
],
{ useNativeDriver: true }
)}
...
/>
</View>
);
}
const mapStateToProps = (state: IAppState, ownProps) => {
return {
commonState: state.common,
...
};
};
interface IState {
offsetAnim: Animated.Value;
clampedScroll: Animated.AnimatedDiffClamp;
}
class HeaderComponent extends React.Component<IHeaderComponentProps, IState> {
constructor(props) {
super(props);
const offsetAnim = new Animated.Value(0);
this.state = {
offsetAnim,
clampedScroll: Animated.diffClamp(
Animated.add(
this.props.commonState.scrollAnim.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolateLeft: 'clamp',
}),
offsetAnim
),
0,
NAVBAR_HEIGHT - STATUS_BAR_HEIGHT
),
};
}
...
render() {
const { clampedScroll } = this.state;
const navbarTranslate = clampedScroll.interpolate({
inputRange: [0, NAVBAR_HEIGHT - STATUS_BAR_HEIGHT],
outputRange: [0, -(NAVBAR_HEIGHT - STATUS_BAR_HEIGHT)],
extrapolate: 'clamp',
});
return (
<TouchableOpacity onPress={this.scrollToTop} activeOpacity={1.0}>
<Animated.View
style={[
styles.container,
navbarTranslate && {
transform: [{ translateY: navbarTranslate }],
},
]}
>
<Image
source={Images.appIcons.myIcon}
style={styles.logo}
resizeMode={ImageResizeModes.CONTAIN}
/>
</Animated.View>
</TouchableOpacity>
);
}
}
const mapStateToProps = (state: IAppState, ownProps) => {
return {
commonState: state.common,
...
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment