1

I have a React app that has children of tabs and the display.

<App>
    <Nav>
        <Tab1></Tab1>
        <Tab2></Tab2>
        <Tab3></Tab3>
    </Nav>
    <Section>Display Stuff Here</Section>
    <Footer></Footer>
</App>

When I made the tabs within the app, I have a for loop that appends the tabs in a Map variable (which is the child of the App React Component).

for(...) {
    tab = React.createElement(Tab, {changeDisplay: () => {this.handleClick(this.changeDisplay(i)}});
    myMap.set(i, tab);
}

What it should be doing is passing the i variable which increments from the for-loop. I even did a console.log to make sure it is pass 1, 2, 3, 4. However, what is actually displaying is the last variable: 4.

Within the Tab class, all I have access to use calling the props:

class Tab extends React.Component {
    constructor(props) {
        super(props);
        this.tab = React.Component('div', null);
    }
    render() {
        return this.tab;
    }
    componentDidMount() {
        this.forceUpdate();
    }
    componentWillUpdate(nextProps, nextState) {
        this.tab = React.Component('div', {onClick: nextProps.changeDisplay});
    }
}

The value for changeDisplay should be unique to each Tab, so Tab2 should only have a value of 2 and Tab3 should only have 3, etc. The for-loop should not be passing the next value to any previous Tabs already created since they are already made and appended to the map variable.

Any advice on how to make this work?

I could make custom functions:

changeDisplay1() { changeDisplay(1) };
changeDisplay2() { changeDisplay(2) };
changeDisplay3() { changeDisplay{3) };
changeDisplay4() { changeDisplay(4) };

However, I want to do it the right way instead of making hacks.

Cit5
  • 400
  • 5
  • 19

1 Answers1

0

React is VERY powerful and you can find multiple ways of rendering a list of components based on parent state. Here's a quick version that I've pulled together.

You can view a working copy here: https://codesandbox.io/s/zzqLPrjO

Explanation:

We create a parent component that stores data such as the tabs available and active tab. We load the tabs available by creating an array from the keys on the Library object. (This is totally optionally, but makes it easier to add additional tabs)

Once the tabs are loaded into the parent component, I've also created a function that sets/changes the active tab. This will be passed down to our tabs in our map, which renders each tab that was picked up from Library object.

In our Tab component, we just render the name along with a click event that is hooked to our parent component which will change the active tab to the tab that has been clicked on. (Note: Notice how we avoid function binding in the render method so that we don't create a new function every time the component is rendered (this is considered bad practice, which is why I avoid it.)

Once the active tab has changed, we make a basic lookup to the Library and render the component attached to that index.

import React from 'react';
import { render } from 'react-dom';

// *---------------------
// | Tab Component
// *---------------------

const activeTab = {
  background: 'green',
}

class Tab extends React.Component {
  change = () => this.props.change(this.props.name)
  render = () => {
    const {
      name: NAME,
      active: ACTIVE,
    } = this.props;
    const isActive = ACTIVE === NAME ? activeTab : null;
    return <button style={isActive} onClick={this.change}>{this.props.name}</button>
  }
}
  
// *---------------------
// | Mock list of Components
// *---------------------
  
const Home = () => <div>Home</div>
const About = () => <div>About</div>

// *---------------------
// | Library of Components
// *---------------------

const Library = {
  Home,
  About
}

// *---------------------
// | Display Components
// *---------------------
  
const Display = ({screen}) => {
  const Comp = Library[screen];
  return screen === null ? <div>Please select a tab</div> : <Comp/>
}

// *---------------------
// | Main App
// *---------------------

class App extends React.Component {
  state = {
    tabs: [],
    activeTab: null,
  }
  componentWillMount() {
    const tabs = Object.keys(Library);
    this.setState({
      tabs: tabs
    });
  }
  changeTab = (tab) => {
    this.setState({ activeTab: tab });
  }
  render() {
    return (
      <div>
        { this.state.tabs.map((tab,index) => {
          return <Tab key={index} name={tab} active={this.state.activeTab} change={this.changeTab} />
        }) }
        <Display screen={this.state.activeTab} />
      </div> 
    )
  }
}

render(<App />, document.getElementById('root'));
Win
  • 5,498
  • 2
  • 15
  • 20
  • I want to give Andrew Li best answer for the for-loop fix but this definitely steered me in the right direction conceptually (ie it was a tough decision). I do have issues with updating the Tabs though. I have a css class that shows a tab as clicked/active. So I can show tab 2 is clicked and now active but it does not force tab 1 to update and compare itself to the new active tab (2) to reset as nonactive. – Cit5 Aug 12 '17 at 18:32
  • @DeadSupra The example has been updated with active tab styling. If you hook it from parent state, it will re-render the children component when you change the active tab. Let me know if you need a more information. :) – Win Aug 12 '17 at 18:58
  • Perfect. Made slight modification for my own code but overall put me in the right path. Thank you so much – Cit5 Aug 15 '17 at 06:12