Agnes Palit
Agnes Palit

Reputation: 254

Uncaught (in promise) TypeError: Cannot read property of undefined, when try to catch API data in ReactJs

I am developing Currency Converter application using ReactJS. The program is about convert from one currency to another. The application has one form consist of a field with submit button. Moreover it also has initial currency 'USD 10' as a default.

When user type the an abbreviation of currency (e.g.: KRW) in the field and click the submit button. There will be a result below the form that shows the result of the conversion from USD to KRW. If the user want to add more currency, another selection currency will be shown below "KRW" conversion result.

When I am trying to make function to show the result when user submit the abbreviation of currency in the field.

This is the error:

Uncaught (in promise) TypeError: Cannot read property 'rates' of undefined
    at Currency.render (App.js:402)
    at finishClassComponent (react-dom.development.js:15319)
    at updateClassComponent (react-dom.development.js:15274)
    at beginWork (react-dom.development.js:16262)
    at performUnitOfWork (react-dom.development.js:20279)
    at workLoop (react-dom.development.js:20320)
    at renderRoot (react-dom.development.js:20400)
    at performWorkOnRoot (react-dom.development.js:21357)
    at performWork (react-dom.development.js:21267)
    at performSyncWork (react-dom.development.js:21241)
    at requestWork (react-dom.development.js:21096)
    at scheduleWork (react-dom.development.js:20909)
    at Object.enqueueSetState (react-dom.development.js:11595)
    at App.push../node_modules/react/cjs/react.development.js.Component.setState (react.development.js:336)
    at Object.App.addCurrency [as onSubmit] (App.js:34)
    at Form.handleSubmit (App.js:204)

And this is my code:

class Currency extends React.Component {
render() {
        const p = this.props;
        return (
            <div className="currency">
                {/*<span className="currencyText">&#x3C;Currency/&#x3E;</span>*/}
                <div className="currencyInfo">
                    <div>{"1 "+p.base + " = " }</div>
                    <div>{p.data.rates}</div> {/*this is (App.js:402)*/}

                </div>
            </div>
        );
    }
}

Instead of that, If I code of Apps.js:402 like this:

<div>{p.data}</div>

and run the app, and check the console in chrome, the result for the line is empty.

The data that I mean is on the picture:

console result

And this is the result of console prop p:

result of prop p in console.log

Any solution for this, so that I can retrieve the data rates from the currency?

Upvotes: 0

Views: 6011

Answers (2)

deowk
deowk

Reputation: 4318

Ideally you want some way to manage loading and no-data states in your component, depending on what your api returns when there is no data - you might have to manage it differently but that is also a scenario you should cover. Your data seems to be coming from a prop called rates not data which is an object with multiple keys.

I am not certain of the structure of your code, but this iteration and checking for loading/no-data states might also be done in the parent component.

    class Currency extends React.Component {
    render() {
            const p = this.props;
            const { rates = {} } = p;
            const hasData = Object.keys(rates).length > 0;
            return !p.loading ? (
                <div className="currency">
                    <div className="currencyInfo">
                        { hasData ?
                        <>
                          <div>{"1 "+p.base + " = " }</div>
                          {Object.entries(rates).map(([k, v]) => <div key={k}>{`${k}: ${v}`}</div>)}
                        </> : <p>No data available</p>
                        }
                    </div>
                </div>
            ) : <Loader />
        }
    }

In a parent component you could do something like:

    class ParentComp extends React.Component {
      public state = { loading: true, rates: {} };

      componentDidMount() {
        await this.fetchData(...); // could set loading to false on success
      }

      render() {
        const { rates = {} } = this.state;
        const hasData = Object.keys(rates).length > 0;
        return (
          !this.state.loading ?
            hasData ?
              Object.entries(rates).map(([k, v]) => <Currency key={k} name={k} value={v} ... />)
              : <p>No data available</p>
            : <Loader />
        );
      }
    }

Then in your child component:

class Currency extends React.Component {
  render() {
    return  (
      <div className='currency'>
        <div className='currencyInfo'>
            <div>{`${this.props.name}: ${this.props.value}`}</div>
        </div>
      </div>
    );
  }
}

CREDITS:

Thanks to @Codebling for pointing out the unnecessary addition of v (value) in the key's

Upvotes: 2

Mike Musni
Mike Musni

Reputation: 171

I am assuming that your Currency component is a Child and it has a Parent component.

Just initialise a value to your "data" before you pass it to child component.

const Child = props => {
  return(
    <div>
      Child
      {props.data.country}
    </div>
  )
}

const Parent = props => {

  // initiate state value to your variables that you are using.
  let [data, set_data] = useState(
    // this is where your error reside, you did not initiate a value.
    { country: 'abc'}
  );

  // api call
  useEffect(()=>{
    axios.get('http://ip-api.com/json/24.48.0.1',{})
    .then(function (response) {
        console.log(response.data);
      set_data(response.data)
    })
    .catch(function (error) {
      console.log(error.config);
    });
  }, [])

  return(
    <div>
      Parent
      <Child data={data}></Child>
    </div>
  )
}
export default Parent;

Upvotes: 0

Related Questions