ProximanovaMetropolis
ProximanovaMetropolis

Reputation: 55

React State: Uncaught TypeError: Cannot read property 'value' of undefined

I am following along with Wes Bos' React for Beginners course, and in the State video, he writes the following code:

import React, { Component } from 'react';

class AddFishForm extends Component {

    nameRef = React.createRef();
    priceRef = React.createRef();
    statusRef = React.createRef();
    descRef = React.createRef();
    imageRef = React.createRef();

    createFish = event => {
        event.preventDefault();
        const fish = {
            nameRef: this.nameRef.value.value,
            priceRef: parseFloat(this.priceRef.value.value),
            statusRef: this.statusRef.value.value, 
            descRef: this.descRef.value.value,
            imageRef: this.imageRef.value.value,
        }
        console.log(this); //or console.log(fish)
    }
    render() {
        return (
            <form className="fish-edit" onSubmit={this.createFish}>
                <input name="name" ref={this.nameRef} type="text" placeholder="Name"/>
                <input name="price" ref={this.priceRef} placeholder="Price"/>
                <select name="status" ref={this.statusRef}>
                    <option value="available">Fresh!</option>
                    <option value="unavailable">Sold Out!</option>
                </select>
                <textarea name="desc" ref={this.descRef} placeholder="Desc"/>
                <input name="image" ref={this.imageRef} type="text" placeholder="Image"/>
                <button type="submit">+ Add Fish</button>
            </form>
        )
    }
}

export default AddFishForm;

Whenever I try to click the Add Fish button, it gives an error:

AddFishForm.js:14 Uncaught TypeError: Cannot read property 'value' of undefined
    at AddFishForm._this.createFish (AddFishForm.js:14)
    at HTMLUnknownElement.callCallback (react-dom.development.js:149)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
    at invokeGuardedCallback (react-dom.development.js:256)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:270)
    at executeDispatch (react-dom.development.js:561)
    at executeDispatchesInOrder (react-dom.development.js:583)
    at executeDispatchesAndRelease (react-dom.development.js:680)
    at executeDispatchesAndReleaseTopLevel (react-dom.development.js:688)
    at forEachAccumulated (react-dom.development.js:662)
    at runEventsInBatch (react-dom.development.js:816)
    at runExtractedEventsInBatch (react-dom.development.js:824)
    at handleTopLevel (react-dom.development.js:4820)
    at batchedUpdates$1 (react-dom.development.js:18932)
    at batchedUpdates (react-dom.development.js:2150)
    at dispatchEvent (react-dom.development.js:4899)
    at interactiveUpdates$1 (react-dom.development.js:18987)
    at interactiveUpdates (react-dom.development.js:2169)
    at dispatchInteractiveEvent (react-dom.development.js:4876)

There are certain things that I would like to point out in his code. He does not use a constructor (props) function with its associated binding statement, and instead opts for turning createFish into an arrow function. Almost every other question on State I see uses a constructor function. Should I be following suit, or is it okay to use the arrow notation (though it is not standard yet)?


Moreover, in the related StorePicker.js file, he writes the following code which is supposed to get you from the StorePicker page to the App page via routing. Yet, when I click the Go To Store button in my app, the app only reloads and does not go forth. What should I be doing differently?

Code for StorePicker.js:

import React, { Fragment } from 'react';
import { getFunName } from '../helpers';

class StorePicker extends React.Component { //OR: ...extends Component (if you import { Component }).
    constructor () {
            super();
            console.log('Create a component!')
            this.goToStore = this.goToStore.bind(this);
    }
    
    myInput = React.createRef();
    goToStore(event) {
        //1. Stop the form from submitting.
            event.preventDefault();
        //2. Get the text from that input. Not going to select using document.querySelector or jQuery here.
        /*IMPORTANT!
            We need to bind ‘this’ to the StorePicker component so that it remains accessible from a member function.
            Either declare a constructor, as shown at the top, or turn goToStore into a function w/ arrow notation.
            goToStore = (event) => {}
        */
            const storeName = this.myInput.value.value;
        //3. Change the page to /store/whatever-they-entered. This uses push state instead of actually moving to a new page.
            this.props.history.push(`/store/${storeName}`);
    }

    render() {
        return (
            <Fragment>
                <h1>StorePicker</h1>
                <form className="store-selector">
                    <h2>Please enter a store.</h2>
                    <input type="text" 
                        ref={this.myInput}
                        required placeholder="Store name"
                        defaultValue={getFunName()}>
                    </input>
                    <button type="submit">Visit Store »
                    </button>
                </form>
            </Fragment>
        )
    }
}

export default StorePicker;

And for Router.js:

import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import StorePicker from './StorePicker';
import App from './App';
import NotFound from './NotFound';

const Router = () => (
    <BrowserRouter>
        <Switch>
            <Route exact path="/" component={StorePicker} />
            <Route path="/store/:storeId" component={App} />
            <Route component={NotFound} />  {/*Default catch-all. */}
        </Switch>
    </BrowserRouter>
);

export default Router;

Upvotes: 1

Views: 1471

Answers (1)

Drew Reese
Drew Reese

Reputation: 202836

Issue Refs

When accessing refs, you need to access the current attribute.

Accessing Refs

When a ref is passed to an element in render, a reference to the node becomes accessible at the current attribute of the ref.

ref.current.<property>

Code

const fish = {
  nameRef: this.nameRef.current.value,
  priceRef: parseFloat(this.priceRef.current.value),
  statusRef: this.statusRef.current.value, 
  descRef: this.descRef.current.value,
  imageRef: this.imageRef.current.value,
}

Question 1

He does not use a constructor (props) function with its associated binding statement, and instead opts for turning createFish into an arrow function. Almost every other question on State I see uses a constructor function. Should I be following suit, or is it okay to use the arrow notation (though it is not standard yet)?

AddFishForm has no state and all the class properties that are functions are arrow functions so they don't need to explicitly have this bound to them in the constructor. Additionally, you can declare the state class property sans a constructor too.

class Foo extends Component {
  state = {...}
  ...

Question 2

...code which is supposed to get you from the StorePicker page to the App page via routing. Yet, when I click the Go To Store button in my app, the app only reloads and does not go forth. What should I be doing differently?

The problem here is the button has type="submit" and is within a form, which when clicked will make the form take default actions, i.e. try to submit the form and reload the page. The this.goToStore also doesn't appear to be used at all.

<form className="store-selector">
  <h2>Please enter a store.</h2>
  <input type="text" 
    ref={this.myInput}
    required
    placeholder="Store name"
    defaultValue={getFunName()}
  />
  <button type="submit"> // <-- causes form to submit and take default actions
    Visit Store »
  </button>
</form>

Solution

  1. Change the button type to type="button" and add an onClick handler, onClick={this.goToStore}
  2. Attach this.goToStore to the form's onSubmit handler <form onSubmit={this.goToStore}>

I'm going to guess goToStore was intended to be used by the form.

<form
  className="store-selector"
  onSubmit={this.goToStore}
>
  <h2>Please enter a store.</h2>
  <input type="text" 
    ref={this.myInput}
    required
    placeholder="Store name"
    defaultValue={getFunName()}
  />
  <button type="submit">
    Visit Store »
  </button>
</form>

Upvotes: 3

Related Questions