5
votes

I'm using Material UI and React, I'm creating multiple tabs programatically as can be seen below:

return (
    <div>
        <Tabs>
            {this.state.elements.map(elem => {
                return (
                    <Tab key={elem.elemID} label={elem.name} >
                        <div>
                           // rest removed for brevity
                        </div>
                    </Tab>
                )
            })}
        </Tabs>
    </div>
);

This works, and the tabs are displayed, but the problem is that by default, when the component renders, the first tab is the active one. Whereas, I want to set the active tab programatically based on an id value that I get from props. So basically, if this.props.selectedID === elem.elemID then I want that tab be the active tab when the component renders. Of course, once the component is rendered, the user should be free to click and switch between tabs. Any idea how to achieve it?

6
Need some more information on how your <Tabs> and <Tab> components work ie: What happens in <Tabs> when you click on a given tab? - Drum

6 Answers

4
votes

Use the value prop on Tabs and Tab to make this a controlled component:

<Tabs value={this.props.selectedID}>
    {this.state.elements.map(elem => {
            return (
                <Tab key={elem.elemID} label={elem.name} value={elem.elemID}>
                    <div>
                       // rest removed for brevity
                    </div>
                </Tab>
            )
        })}
</Tabs>
0
votes

TL DR; Assuming that you're trying to map it to URLs, the trick in here, is to programatically set the "value" property of <Tab> to the one that the URL currently has. I'm going to assume you're using React Router.

Example:

const baseUrls = ['', 'online-manga-reader', 'admin'];

class TabBar extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      value: this.processPathName(),
    };
  }

  processPathName(){
    const path = this.props.location.pathname.split('/')[1];
    const value =  baseUrls.indexOf(path);
    return value !== -1 ? value : 0; // This is the default one.
  }

  handleChange = (event, value) => {
    this.setState({ value });
    this.returnTabContainer(value);
  }

  returnTabContainer(value) {

    switch (value) {
      case 1:
        this.props.history.push('/online-manga-reader');
        break;
      case 2:
        this.props.history.push('/admin/article');
        break;
      default:
        this.props.history.push('/');
    }
  }

  returnTabAdmin() {
    return this.props.isAdmin ? <Tab label="Admin Tab" /> : '';
  }

  render() {
    const { classes } = this.props;
    const { value } = this.state;

    return (
      <div className={classes.root}>
        <AppBar position="static">
          <Tabs 
            value={value} 
            onChange={(evt, val) => {this.handleChange(evt, val); }}
          >
            <Tab label="Home" />
            <Tab label="Online Manga Reader" />
            {this.returnTabAdmin()}
          </Tabs>
          <LoginButton />
        </AppBar>
      </div>
    );
  }
}
export default connect(mapStateToProps)(withStyles(styles)(withRouter(TabBar)));

Explanation

const baseUrls = ['', 'online-manga-reader', 'admin'];

This is an array that lives outside the class, and holds the first path of the URL (Or the base URL):

This will match the base URL (hence the first one will be selected) http://example.com/

This one, the second will be matched

The third one: - http://example.com/admin - http://example.com/admin/article/create

The "trick" that I'm using in here lies in the processPathName() method. It reads from React-Router the current URL, then it calls the split, which will already have the URL parsed to you in the pieces needed:

http://example.com/admin/article/create 

Will translate to:

/ admin article create

Always returning us in the position 1 the one that we're looking for (this is one from the split).

With that, we use the good ol' indexOf to search inside the array for our path. If it finds it, it's going to return the number that is going to match the position of our tabs. That's why it's important for them to be in the same order.

If by any chance it doesn't find it, we set a default value (in this case we set it for zero, which accounts for the Home Page).

Your items should render normally, without any issues.

0
votes

Here is my answer as a minimal generic example with hooks. I also assume that you are using React Router. It essentially links the url state to the 'selectedness' of the Tab (on change and on render):

const NavTabs = ({ history, location }) => {
  function getInitialTab() {
    switch (true) {
      case /\/componentone/.test(location.pathname):
        return 0
      case /\/componenttwo/.test(location.pathname):
        return 1
      default:
        return 0
    }
  }

  const [value, setValue] = useState(getInitialTab())

  function handleChange(event, val) {
    setValue(val)
    switch (val) {
      case 0:
        history.push('/componentone')
        break
      case 1:
        history.push('/componenttwo')
        break
      default:
        break
    }
  }

  return (
    <NoSsr>
      <div>
        <AppBar position="static">
          <Tabs
            variant="fullWidth"
            value={value}
            onChange={handleChange}
          >
            <Tab label="Component One" />
            <Tab label="Component Two" />
          </Tabs>
        </AppBar>
        {value === 0 && (
          <Typography component="div">
            <ComponentOne history={history} location={location} />
          </Typography>
        )}
        {value === 1 && (
          <Typography component="div">
            <ComponentTwo history={history} location={location} />
          </Typography>
        )}
      </div>
    </NoSsr>
  )
}
0
votes

Jose A's answer is doing it right; Using React Material Tabs you set state using 'useState' hook when initializing the Tabs component:

const initialTabIndex= 0;
const [value, setValue] = React.useState(initialTabIndex);

React Material Tabs

0
votes

You can use the state to set the initial tab select.

const [value, setValue] = React.useState(2);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };
0
votes
const activeIndex = () => {
    const pathname =
      typeof window !== "undefined"
        ? window.location.pathname.split("/")[1]
        : null

    const found = routes.indexOf(
      routes.filter(
        ({ node: { name, link } }) =>
          (link || `/${name.toLowerCase()}`) === `/${pathname}`
      )[0]
    )

    return found === -1 ? false : found
  }