Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix money request tabs animation #29231

Merged
merged 10 commits into from
Oct 18, 2023
29 changes: 21 additions & 8 deletions src/components/TabSelector/TabSelector.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {View} from 'react-native';
import React from 'react';
import React, {useState} from 'react';
import PropTypes from 'prop-types';
import _ from 'underscore';
import * as Expensicons from '../Icon/Expensicons';
Expand Down Expand Up @@ -53,7 +53,7 @@ const getIconAndTitle = (route, translate) => {
}
};

const getOpacity = (position, routesLength, tabIndex, active) => {
const getOpacity = (position, routesLength, tabIndex, active, affectedTabs) => {
const activeValue = active ? 1 : 0;
const inactiveValue = active ? 0 : 1;

Expand All @@ -62,32 +62,43 @@ const getOpacity = (position, routesLength, tabIndex, active) => {

return position.interpolate({
inputRange,
outputRange: _.map(inputRange, (i) => (i === tabIndex ? activeValue : inactiveValue)),
outputRange: _.map(inputRange, (i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? activeValue : inactiveValue)),
});
}
return activeValue;
};

const getBackgroundColor = (position, routesLength, tabIndex) => {
const getBackgroundColor = (position, routesLength, tabIndex, affectedTabs) => {
if (routesLength > 1) {
const inputRange = Array.from({length: routesLength}, (v, i) => i);

return position.interpolate({
inputRange,
outputRange: _.map(inputRange, (i) => (i === tabIndex ? themeColors.border : themeColors.appBG)),
outputRange: _.map(inputRange, (i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? themeColors.border : themeColors.appBG)),
pecanoro marked this conversation as resolved.
Show resolved Hide resolved
});
}
return themeColors.border;
};

function TabSelector({state, navigation, onTabPress, position}) {
const {translate} = useLocalize();

const defaultAffectedAnimatedTabs = Array.from({length: state.routes.length}, (v, i) => i);
const [affectedAnimatedTabs, setAffectedAnimatedTabs] = useState(defaultAffectedAnimatedTabs);

React.useEffect(() => {
setTimeout(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are usually against using setTimeout, why do we need it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need here to reset affectedAnimatedTabs after transition end, and I think this is the available way to wait transition end.

Removing setTimeout will back the issue to happened again because while moving from tab1 to tabs3 the transition is not end yet and at this time calling reset affectedAnimatedTabs to default will reset it to [tab1, tab2, tab3] while we need it to keep it [tab1, tab3] until transition end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it! Can you leave a comment then explaining why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pecanoro updated.

setAffectedAnimatedTabs(defaultAffectedAnimatedTabs);
}, CONST.ANIMATED_TRANSITION);
// eslint-disable-next-line react-hooks/exhaustive-deps
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why we add this as we do here?

// eslint-disable-next-line react-hooks/exhaustive-deps -- we just want to revalidate the form on update if the preferred locale changed on another device so that errors get translated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I add eslint-disable because putting defaultAffectedAnimatedTabs as deps will reset the affectedAnimatedTabs in every render, because const defaultAffectedAnimatedTabs will re-define the const in every render and is considered deps change because reference identity is changed.
I think we can remove eslint-disable and use useMemo

const defaultAffectedAnimatedTabs = useMemo(() => Array.from({length: state.routes.length}, (v, i) => i), [state.routes.length]);
    
 React.useEffect(() => {
    setTimeout(() => {
        setAffectedAnimatedTabs(defaultAffectedAnimatedTabs);
    }, CONST.ANIMATED_TRANSITION);
}, [defaultAffectedAnimatedTabs, state.index]);

}, [state.index]);

return (
<View style={styles.tabSelector}>
{_.map(state.routes, (route, index) => {
const activeOpacity = getOpacity(position, state.routes.length, index, true);
const inactiveOpacity = getOpacity(position, state.routes.length, index, false);
const backgroundColor = getBackgroundColor(position, state.routes.length, index);
const activeOpacity = getOpacity(position, state.routes.length, index, true, affectedAnimatedTabs);
const inactiveOpacity = getOpacity(position, state.routes.length, index, false, affectedAnimatedTabs);
const backgroundColor = getBackgroundColor(position, state.routes.length, index, affectedAnimatedTabs);
const isFocused = index === state.index;
const {icon, title} = getIconAndTitle(route.name, translate);

Expand All @@ -96,6 +107,8 @@ function TabSelector({state, navigation, onTabPress, position}) {
return;
}

setAffectedAnimatedTabs([state.index, index]);

const event = navigation.emit({
type: 'tabPress',
target: route.key,
Expand Down
Loading