0

I have a react application and I'm trying to build a Navbar component using data from a javascript file.

My NavbarData.js file looks like the following:

const NavbarData = [
    {
        id: 1, 
        text: "Typography"
    },
    {
        id: 2,
        text: "Buttons"
    },
    {
        id: 3,
        text: "icons"
    }
]

export default NavbarData

I'm using .map() to iterate over this data and create NavbarItem components inside my App.js file.

// Build navmenu items
const navbarItems = this.state.navbarData.map(function(item){
  return <NavbarItem key={item.id} text={item.text} id={item.id}></NavbarItem>
});

And here is my NavbarItem.js file

import React, { Component } from 'react';

class NavbarItem extends Component{
    render(){
        return(
            <>
                <li key={this.props.id} id={this.props.id}>{this.props.text}</li>
            </>
        )
    }
}

export default NavbarItem

All of this gives me something that looks like this. Which is great.

Image1

But I want to add a click listener to each of these. As this is a single page application, I would like to render either a typography, buttons, or icons component. To do this, I need a function that will update the state of the parent component which in my case is just App.js

So I put the following function inside App.js

  //This function changes the state so that different components can render
  navClick(id) {
    console.log('changed', id);
  }

And I made sure to bind it in my constructor of App.js

this.navClick = this.navClick.bind(this);

My entire App.js file now looks like this

//React stuff
import React, { Component } from 'react';

//Bootstrap stuff
import { Container, Row, Col } from 'reactstrap';

//Layout
import NavbarItem from './layout/NavbarItem'
import NavbarData from './layout/NavbarData'

//Components
import Typography from './components/Typography/Typography'
import Buttons from './components/Buttons/Buttons'

//Styles
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';

class App extends Component {
  constructor(){
    super();

    // State determines what component is active and loads navbar data
    this.state = {
      navbarData: NavbarData,
      typography: true,
      buttons: false,
      icons: false
    }

    this.navClick = this.navClick.bind(this);
  }
  //This function changes the state so that different components can render
  navClick(id) {
    console.log('changed', id);
  }

  render() {

    // Build navmenu items
    const navbarItems = this.state.navbarData.map(function(item){
      return <NavbarItem key={item.id} text={item.text} id={item.id}></NavbarItem>
    });


    // Determine what component to display in main area using state
    let elementToDisplay;
    if(this.state.typography){
      elementToDisplay = <Typography></Typography>
    }
    else if(this.state.buttons){
      elementToDisplay = <Buttons></Buttons>
    }

    ////////////////////////////////////////////////////


    return (
      <Container fluid={true}>
        <Row>
          <Col>Header</Col>
        </Row>
        <Row>
          <Col xs="12" sm="12" md="1" lg="1" xl="1">
            <ul>
              {navbarItems}
            </ul>
          </Col>
          <Col xs="12" sm="12" md="11" lg="11" xl="11">
            {elementToDisplay}
          </Col>
        </Row>
        <Row>
          <Col>Footer</Col>
        </Row>
      </Container>
    );
  }
}

export default App;

The problem comes when I try to attach the navClick function to the mapped NavbarItem like so.

// Build navmenu items
const navbarItems = this.state.navbarData.map(function(item){
  return <NavbarItem navigationWhenClicked={this.navClick} key={item.id} text={item.text} id={item.id}></NavbarItem>
});

The error I receive is the following:

TypeError: this is undefined

TypeError

When googleing this issue, this is the top post. React: "this" is undefined inside a component function

But that's not my problem as I am making sure to bind my function.

I really have no idea what I'm doing wrong here. Any help would be appreciated.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
onTheInternet
  • 6,421
  • 10
  • 41
  • 74
  • In your above example, you don't seem to use `navClick` are you sure that this code is uptodate? – kemicofa ghost Mar 18 '19 at 18:10
  • I would try converting `navClick`'s definition to arrow syntax, and remove the bind from the constructor. This should work out the box if you're using create react app. an example of the function definition would be `const navClick = id => {};` – technogeek1995 Mar 18 '19 at 18:10
  • 1
    @technogeek1995: Either way works, they are essentially the same. – Felix Kling Mar 18 '19 at 18:13
  • It's `function(item){` that is not bound to proper context, not `navClick`. – Estus Flask Mar 18 '19 at 18:24

1 Answers1

3

The function you pass to .map also has its own this binding. The simplest solution is to pass this as second argument to .map:

const navbarItems = this.state.navbarData.map(function(item) {
  ...
}, this);

this inside the function will be set to whatever you pass as second argument, which in this case is the component instance.

Alternatively you can use an arrow function instead of a function expression, since this is resolved lexically (i.e. like any other variabe) inside arrow functions:

const navbarItems = this.state.navbarData.map(
  item => <NavbarItem navigationWhenClicked={this.navClick} key={item.id} text={item.text} id={item.id} />
});

See also: How to access the correct `this` inside a callback?

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143