Something is supposed to be null, but it's undefined

As in object, I’m writing a React app, but cannot figure out why when I click on the card, the first click returns undefined, and then the next click returns the element I clicked on before that. I leave my code here.

app.js calls the main component (I want to make the app component as functional as possible).

import React, { Component } from 'react';
import Main from './components/MainComponent';

class App extends Component {
  render() {
    return(
      <div className='App'>
        <Main />
      </div>
    )
  }
}

export default App;

MainComponent.js contains everything logic related (it is still at a very early stage)

import React, { Component } from 'react';
import { Navbar, Icon } from 'react-materialize';

import Dashboard from './DashboardComponent';
import Details from './DetailsComponent';

import { ITEMS } from '../shared/items';
import { SUPPLIERS } from '../shared/suppliers';

class Main extends Component {
    constructor(props) {
        super(props);
        this.state = {
            items: ITEMS,
            suppliers: SUPPLIERS,
            selectedItem: null
        };
        this.onItemSelect = this.onItemSelect.bind(this);
    }

    onItemSelect(itemId) {
        this.setState({ selectedItem: itemId });
        console.log(this.state.items.filter(item => item.id === this.state.selectedItem)[0]);
    }

    componentDidMount() {
        console.log(this.state.selectedItem);
    }

    render() {
        return(
            <div>
                <Navbar
                    alignLinks='right'
                    menuIcon={<Icon>menu</Icon>}
                    fixed
                    className='green'
                    search
                >
                </Navbar>
                <Dashboard items={this.state.items} onClick={(itemId) => this.onItemSelect(itemId)} />
                <Details item={this.state.items.filter(item => item.id === this.state.selectedItem)[0]} />
            </div>
        )
    }
}

export default Main;

DashboardComponent.js this is where the problem occurs. Whenever I reload the page, if item is null (which is supposed to be), I get an error of it being undefined. If in the main component I hard code the selected item to be one of the ids (which is a field of items), I get that if I click on a card, I get undefined back. Then if I click on another card, I get back the informations of the one I clicked before that.

import React, { Component } from 'react';
import { Row, Col, Icon, Card, CardTitle, Container } from 'react-materialize';

class Dashboard extends Component {
    render() {
        const dashboard = this.props.items.map(item => {
            return (
                <div key={item.id}>
                    <Col m={6} s={12}>
                        <Card
                            actions={[
                                <a key={item.id} href='#'>Details</a>
                            ]}
                            onClick={() => this.props.onClick(item.id)}
                            closeIcon={<Icon>close</Icon>}
                            header={<CardTitle image="https://materializecss.com/images/sample-1.jpg" />}
                            horizontal
                            revealIcon={<Icon>more_vert</Icon>}
                            title={item.name}
                            >
                                
                            </Card>
                    </Col>
                </div>
            );
        });

        return(
            <Container>
                <Row>
                    {dashboard}
                </Row>
            </Container>
        );
    }
}

export default Dashboard;

DetailsComponent.js is what is supposed to be shown when I click on a card. But, as mentioned before, I can’t understand why.

import React, { Component } from 'react';
import { Col, Card, CardTitle, Icon, Row, Container } from 'react-materialize';

class Details extends Component {
    constructor(props) {
        super(props);
    }

    renderItem(item) {
        switch (item) {
            case null:
                return (<div></div>);

            default:
                return (
                    <Col m={6} s={12}>
                        <Card
                            actions={[
                                <a key={item.id} href='/'>Details</a>
                            ]}
                            closeIcon={<Icon>close</Icon>}
                            header={<CardTitle image="https://materializecss.com/images/sample-1.jpg" />}     
                            revealIcon={<Icon>more_vert</Icon>}
                            title={item.name}
                        >
                        </Card>
                    </Col>
                );
        }
    }

    renderDetails(item) {
        switch(item) {
            case null:
                return (<div></div>);
            
            default:
                return (
                <Col m={6} s={12}>
                    <p>{item.name}</p>
                    <p>{item.location}</p>
                    <p>{item.supplier}</p>
                    <p>{new Intl.DateTimeFormat('it-IT', { year: 'numeric', month: 'short', day: '2-digit', hour12: true, hour: 'numeric', minute: 'numeric', second: 'numeric' }).format(new Date(item.insertDate))}</p>
                    <p>{new Intl.DateTimeFormat('it-IT', { year: 'numeric', month: 'long', day: '2-digit' }).format(new Date(item.purchaseDate))}</p>
                    <p>{(item.inUse).toString()}</p>
                    <p>{item.invoiceNumber}</p>
                </Col>
                );
        }   
    }
    
    render() {
        return(
            <Container>
                <Row>
                    {this.renderItem(this.props.item)}
                    {this.renderDetails(this.props.item)}
                </Row>
            </Container>
        );
    }
}

export default Details;

I know I put a lot of code, but if somebody could help me it would be really appreciated.

Hey Giulio,

it would be awesome to see a (working) example of your project on codesandbox, so that we can fiddle around with it.

If you got your code on GitHub/GitLab, you can even import it on codesandbox.

Looking forward to seeing it! :slightly_smiling_face:

Hello and thank you for your feedback!
I leave the link to the codesandbox, hoping it is the right one

1 Like

Just like @miku86 pointed out, it is difficult to tell where the problem is unless someone plays with your code on Codesandbox. From what you are saying, item should be null on page reload but still you are accessing its id property from item.id. Don’t you see a problem there?

Not quite, it’s selectedItem which is supposed to be null at reload. I hard-coded it to be 0 just to have something to see, but selectedItem is supposed to be null in the state. Anyway I left the link above

The code you have linked to is not exactly the same as the one you posted above. Above,
selectedItem is set to null in state of Main but in the link it is set to 0. When i change it to null in codesandbox, the error message is quite clear.

Cannot read property 'id' of undefined

from

<a key={item.id} href='/'>Details</a>

That means item is undefined. But what is item? It is a prop passed from Main after filtering in the code below:

 <Details item={this.state.items.filter(item => item.id === this.state.selectedItem)[0]} />

Is there an item in this.state.items whose id is equal to null?
As far as i can tell, from the 3 items in this.state.items array all of them are objects of the shape:

{
id: 0
name: "Test 1"
location: "Location 1"
inUse: false
supplier: "Supplier 1"
purchaseDate: "01/01/1993"
insertDate: "01/01/1993"
invoiceNumber: "1234
}

None of them has an id of null, therefore filtering this.state.items array returns [] and the value of item is set to the first element of [] which is undefined. I think that is where the problem could be.

Yes, but if you look at the DetailsComponent.js, both the methods renderItem(item) and renderDetails(item) have a switch(item) { case null: ... } in them, which is what should happen since item (which is selectedItem passed as prop, hence null) is actually null at the beginning. What am I missing in this reasoning?

That is what i am trying to tell you in the above post. You have a case of null in your switch statement but item is not null. It is undefined. First of all if you set state to

 this.state = {
            items: ITEMS,
            suppliers: SUPPLIERS,
            selectedItem: null
        };

inside Main component, essentially you are rendering Details and passing it item as a prop. Look at the code which generates item:

<Details item={this.state.items.filter(item => item.id === this.state.selectedItem)[0]} />

item is undefined inside Details not null therefore your witch will return the code under default case.
It is important to understand the following:

  1. item is not null as you have set it in state because you are not passing it to Details but you are instead passing the result of filtering this.state.items.
  2. switch doesn’t return the code under null case because the value of item is undefined as a result it will return the value under default case. null is not the same as undefined.

EDIT
To clearly see what i am talking about do the following:

  1. set the value of selectedItem to null like you are already doing
  2. change the case null to case undefined in switch statement in both renderItem and renderDetails methods.
  3. Include console.log("renderItem", item) at the top of renderItem method
  4. Include console.log("renderDetails", item) at the top of renderDetails method

Yes yes, I got it, it’s just that I had a past experience in which I did the exact same thing as I did here and it worked, guess I should take a look at how that code was actually done. Thank you anyway! :v:

1 Like