Repository: mui/material-ui (TypeScript)
Date: 2018-06-12 06:22:54+00:00
Task type: Bug Fix

Issue:
[Tabs] Tab indicator in Tabs behaves wrong when tabs are dynamically changed
- [x] I have searched the [issues](https://github.com/callemall/material-ui/issues) of this repository and believe that this is not a duplicate.

## Expected Behavior
The tab indicator must always show the selected tab

## Current Behavior
When the number of tabs in a tab menu changes the indicator have a wrong position until next interaction such as clicking a new tab.

## Steps to Reproduce (for bugs)
1. Go to https://codesandbox.io/s/2pm5jzmk10
2. Select a random tab
3. Click switch
4. Chehck indicator position

## Context
I Noticed this issue when trying to remove some tabs based on the users permission role. I quickly realised that the indicator was not in sync with the amount of tabs inside the menu. 

## Reproduction Environment
| Tech         | Version |
|--------------|---------|
| Material-UI  |1.0.0-beta.11        |
| React        |15.5.3         |
| browser      | Google Chrome v61       |

## Your Environment
| Tech         | Version |
|--------------|---------|
| Material-UI  |1.0.0-beta.6        |
| React        |15.6.1         |
| browser      | Google Chrome v61       |



Issue discussion:
We might want to change this logic:
https://github.com/callemall/material-ui/blob/438dd7f7fc14f504f0e385fef1719ee6674fa3ca/src/Tabs/Tabs.js#L164-L167
@oliviertassinari Would it be too bad performance to remove the if statement arround :-) ?

I would really like to contribute but is relativly new in the open source world :-) Is there any guides to start contributing in generel :-) ?
@thupi This issue might not have been the simpler issue to start contributing with. I have been doing some cleanup along the way. Thanks for your interest. Don't miss the [CONTRIBUTING.md](https://github.com/callemall/material-ui/blob/v1-beta/CONTRIBUTING.md) file and the issues with the `good first issue` flag 👍 
Im getting similar issue in production build.
generated project from "create-react-app"
material-ui: beta 16
react: v16

it working as expected on dev build. but when i use production build, indicator highlighting 2nd tab(my case). but once i start clicking the tabs everything works fine.

any suggestions or workarounds. i tried v0.19.4, it worked great in production build too.

Thanks.. 
Just to add, I'm using objects as my "value" and the indicator refuses to be under the currently selected tab. 

![image](https://user-images.githubusercontent.com/16243383/33672138-1a2721ec-da6f-11e7-8129-7c9b069203ed.png)

Note in the above image, the current selection is "Allergies", but the indicator is highlighing the "Insights" tab. This is in chrome with material-ui 1.0.0.-beta-.20.
The problem is only in indicator, the value props is changing as expected, I am having the same problem of the guy above. Here it is my component: 

```
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Tabs, { Tab } from 'material-ui/Tabs';
import Typography from 'material-ui/Typography';

const styles = theme => ({
  root: {
    flexGrow: 1,
    backgroundColor: theme.palette.background.paper,
  },
  tabsRoot: {
    borderBottom: '1px solid #e8e8e8',
  },
  tabsIndicator: {
    backgroundColor: '#f00',
    display: 'none'
  },
  tabRoot: {
    textTransform: 'initial',
    fontWeight: theme.typography.fontWeightRegular,
    marginRight: theme.spacing.unit * 4,
    '&:hover': {
      color: '#40a9ff',
      opacity: 1,
    },
    '&$tabSelected': {
      color: '#1890ff',
      fontWeight: theme.typography.fontWeightMedium,
      background: 'red',
    },
    '&:focus': {
      color: '#40a9ff',
    },
  },
  tabSelected: {},
  typography: {
    padding: theme.spacing.unit * 3,
  },
});


const TabsItems = (props) => {
    let components = [];

    const {classes, items, onChange, ...other} = props;

    items.map( (item, index) => {
        components.push(
            <Tab
                key={index}
                disableRipple
                onChange={onChange}
                classes={{ root: classes.tabRoot, selected: classes.tabSelected }}
                {...item}
            />
        );
    });


    return components;
};

class CustomTabs extends React.Component {
  state = {
    value: 0,
  };

  handleChange = (event, value) => {
    console.log('bang!');
    this.setState({ value });
  };

  render() {
    const { classes, tabs, ...other } = this.props;
    const { value } = this.state;

    return (
        <div className={classes.root} {...other}>
            <Tabs
            value={value}
            active
            onChange={this.handleChange}
            classes={{ root: classes.tabsRoot, indicator: classes.tabsIndicator }}
            >
                <TabsItems classes={classes} onChange={this.handleChange} items={tabs} />                
            </Tabs>
            <Typography className={classes.typography}>Ant Design</Typography>
        </div>
    );
  }
}

CustomTabs.propTypes = {
  classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(CustomTabs);

```
@lucas-viewup You can try the `action` property of the `Tabs` and pull out an `updateIndicator()` function.
I had the very same problem as @lucas-viewup and @rlyle. My problem was that I was using functions as values for the tabs and that is not supported although the documentation for [`Tab`](https://github.com/mui-org/material-ui/blob/fd84438c33c7aa7a982d11e80bef1d5f7b17c6a9/pages/api/tab.md) and [`Tabs`](https://github.com/mui-org/material-ui/blob/fd84438c33c7aa7a982d11e80bef1d5f7b17c6a9/pages/api/tabs.md) say that the `value` can be any type.

The problem is that the backing implementation [is using an object](https://github.com/mui-org/material-ui/blob/fd84438c33c7aa7a982d11e80bef1d5f7b17c6a9/packages/material-ui/src/Tabs/Tabs.js#L316) to [store the values](https://github.com/mui-org/material-ui/blob/fd84438c33c7aa7a982d11e80bef1d5f7b17c6a9/packages/material-ui/src/Tabs/Tabs.js#L324). Thus the values must provide an unique `string` representation. I see these possible fixes:
- Fix the documentation to say the values will be converted to strings (or even make the API only accept strings) and one should make sure they're unique. We also could probably warn if the `value` was not unique when it's added to `valueToIndex`?
- Change the backing store to a [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). Is that acceptable?

In my case, all of the values were converted to the following `string` and thus they were not unique:
```
"function bound value() {
    [native code]
}"
```
This string representation was only produced in the production build and during development time the values were unique.
@oliviertassinari Hey, I know you're busy, but I noticed I didn't highlight you in my previous comment, so I just wanted to make sure you noticed my comment.
> Change the backing store to a Map. Is that acceptable?

@ljani I confirm the issue you are facing. Yes, using a `Map` would be much better. Do you want to work on it?
@oliviertassinari Great! I'll try and see if I can get something done next week.

Solution patch:
diff --git a/packages/material-ui/src/Tabs/Tabs.js b/packages/material-ui/src/Tabs/Tabs.js
--- a/packages/material-ui/src/Tabs/Tabs.js
+++ b/packages/material-ui/src/Tabs/Tabs.js
@@ -143,7 +143,7 @@ class Tabs extends React.Component {
       const children = this.tabs.children[0].children;
 
       if (children.length > 0) {
-        const tab = children[this.valueToIndex[value]];
+        const tab = children[this.valueToIndex.get(value)];
         warning(tab, `Material-UI: the value provided \`${value}\` is invalid`);
         tabMeta = tab ? tab.getBoundingClientRect() : null;
       }
@@ -152,7 +152,7 @@ class Tabs extends React.Component {
   };
 
   tabs = undefined;
-  valueToIndex = {};
+  valueToIndex = new Map();
 
   handleResize = debounce(() => {
     this.updateIndicatorState(this.props);
@@ -313,7 +313,7 @@ class Tabs extends React.Component {
       />
     );
 
-    this.valueToIndex = {};
+    this.valueToIndex = new Map();
     let childIndex = 0;
     const children = React.Children.map(childrenProp, child => {
       if (!React.isValidElement(child)) {
@@ -321,7 +321,7 @@ class Tabs extends React.Component {
       }
 
       const childValue = child.props.value === undefined ? childIndex : child.props.value;
-      this.valueToIndex[childValue] = childIndex;
+      this.valueToIndex.set(childValue, childIndex);
       const selected = childValue === value;
 
       childIndex += 1;


Test patch:
diff --git a/packages/material-ui/src/Tabs/Tabs.test.js b/packages/material-ui/src/Tabs/Tabs.test.js
--- a/packages/material-ui/src/Tabs/Tabs.test.js
+++ b/packages/material-ui/src/Tabs/Tabs.test.js
@@ -246,6 +246,19 @@ describe('<Tabs />', () => {
         );
       });
 
+      it('should accept any value as selected tab value', () => {
+        const tab0 = {};
+        const tab1 = {};
+        assert.notStrictEqual(tab0, tab1);
+        const wrapper2 = shallow(
+          <Tabs width="md" onChange={noop} value={tab0}>
+            <Tab value={tab0} />
+            <Tab value={tab1} />
+          </Tabs>,
+        );
+        assert.strictEqual(wrapper2.instance().valueToIndex.size, 2);
+      });
+
       it('should render the indicator', () => {
         const wrapper2 = mount(
           <Tabs width="md" onChange={noop} value={1}>