3

I have the following code where I am making a REST call and assigning the result to a variable.

Then I am using the result to map over and create components with props.

But at present it throws an error because the value for list is undefined.

I believe this is because the value of the list is not set yet when I am attempting to map due to axios async call not completed yet.

Thus 2 queries.

  1. How should I use the response value. Is my method of assigning it to the variable 'list' correct or it should be done differently?

  2. How do I wait for list to be populated and then map over it?

You can see how the response.data will look by looking at following endpoint: https://sampledata.free.beeceptor.com/data1

Sample response data:

[
    {
        "word": "Word of the Day",
        "benevolent": "be nev o lent",
        "adjective": "adjective",
        "quote": "well meaning and kindly.<br/>a benevolent smile",
        "learn": "LEARN MORE"
    },
    {
        "word": "Word of the Day",
        "benevolent": "be nev o lent",
        "adjective": "adjective",
        "quote": "well meaning and kindly.<br/>a benevolent smile",
        "learn": "LEARN MORE"
    }
]

Client code:

const App = () => {

  // const cardData = useSelector(state => state.cardData)
  let list;

  useEffect(() => {
    axios.get('https://sampledata.free.beeceptor.com/data1')
      .then(response => {
        list = response.data;
        list.forEach(l => console.log(l))
      })
      .catch(error => {
        console.log(error)
      })
  }, [])

  return (
    <>
      <ButtonAppBar/>
      <div className='container'>
        <div className='row'>
          {
            list.map((data) => {
              const {word, bene, adj, well, learn} = data;
              return (
                <div className='col-lg-3 col-md-6 format'>
                  <SimpleCard word={word} bene={bene} adj={adj} well={well} learn={learn} />
                </div>
              )
            })
          }
        </div>
      </div>
    </>
  );
}

export default App;
terrymorse
  • 5,564
  • 1
  • 14
  • 19
kar
  • 3,679
  • 8
  • 37
  • 53

4 Answers4

3

You need to make use of useState to store the data that you get from the API.

For example

const [state, setState] = useState({ list: [], error: undefined })

Because the API call is asynchronous and the data will not be available until the component mounts for the first time. You need to use a conditional to check for state.list.length otherwise it will throw an error cannot read property ..x of undefined.

const App = () => {

  // create a state variable to store the data using useState
  const [state, setState] = useState({ list: [], error: undefined });

  useEffect(() => {
    axios
      .get("https://sampledata.free.beeceptor.com/data1")
      .then(response => {
        setState(prevState => ({
          ...prevState,
          list: [...prevState.list, ...response.data]
        }));
      })
      .catch(error => {
        setState(prevState => ({ ...prevState, list: [], error: error }));
      });
  }, []);

  return (
    <>
      <ButtonAppBar/>
      <div className='container'>
        {
           // you can show a loading indicator while your data loads
           !state.list.length && <div>The data is loading....</div>
        }

        <div className='row'>
          {
            state.list.length && state.list.map((data) => {
              const {word, bene, adj, well, learn} = data;
              return (
                <div className='col-lg-3 col-md-6 format'>
                  <SimpleCard word={word} bene={bene} adj={adj} well={well} learn={learn} />
                </div>
              )
            })
          }
        </div>
      </div>
    </>
  );
}

subashMahapatra
  • 3,537
  • 1
  • 6
  • 16
  • I have removed the codesandbox example because the API URL that you have provided has a max limit of 50 requests per day. Here is a similar example using jsonplaceholder API. [codesandbox](https://codesandbox.io/s/material-demo-z8rhj?file=/App.js) – subashMahapatra May 30 '20 at 08:56
0

You could benefit from using useState hook here.

For example:

const App = () => {
  const [list, setList] = useState([]);

  useEffect(() => {
    axios.get('https://sampledata.free.beeceptor.com/data1')
      .then(response => {
        setList(response.data);
      })
      .catch(error => {
        console.log(error)
      })
  }, [])

  return (
    <>
      <ButtonAppBar/>
      <div className='container'>
        <div className='row'>
          {
            list.map((data) => {
              const {word, bene, adj, well, learn} = data;
              return (
                <div className='col-lg-3 col-md-6 format'>
                  <SimpleCard word={word} bene={bene} adj={adj} well={well} learn={learn} />
                </div>
              )
            })
          }
        </div>
      </div>
    </>
  );
}

export default App;
segFault
  • 3,110
  • 1
  • 16
  • 28
0

Do not use let to save fetched values instead use state or props in case you want to generate UI from that. In react component rerender if state or props value changed. Reason of getting error is, you are doing asynchronous call and because of that your component is parallely rendering and inside the return list will be null and it will throw error .

Correct way is :

const App = () => {

  const [list, setlist]= React.useState([])


  useEffect(() => {
    axios.get('https://sampledata.free.beeceptor.com/data1')
      .then(response => {
        setlist (response.data)
      })
      .catch(error => {
        console.log(error)
      })
  }, [])

  return (
    <>
      <ButtonAppBar/>
      <div className='container'>
        <div className='row'>
          {
            list.map((data) => {
              const {word, bene, adj, well, learn} = data;
              return (
                <div className='col-lg-3 col-md-6 format'>
                  <SimpleCard word={word} bene={bene} adj={adj} well={well} learn={learn} />
                </div>
              )
            })
          }
        </div>
      </div>
    </>
  );
}

export default App;
Rahul
  • 89
  • 1
  • 1
  • 10
0

This can be solved in two ways (since you are using hooks)

  1. useRef() (I would not recommend doing this)
  2. useState() (as the example I have given)

I will show you by using the useState method, but you should keep in mind that since it's a state it will re-render (I don't think it will be an issue here).

import React, { useEffect, useState } from 'react';
import axios from 'axios';

const App = () => {
  let [list, setList] = useState(<>LOADING</>);
  useEffect(() => {
      // You can use your link here
      // I have created corsenabled.herokuapp.com just to bypass the CORS issue. It's only for testing and educational purpose only. No intention to infringe any copyrights or other legal matters
      // I have used jsonplaceholder.typicode.com as an example
    axios.get('https://corsenabled.herokuapp.com/get?to=https://jsonplaceholder.typicode.com/posts')
      .then(response => {
        let tempData = response.data;
        let anotherData = tempData.map(data => {
            return (<div>{data.userId}<br/>{data.id}<br/>{data.title}<br/>{data.body} <br/><br/></div>)
            })
        // tempData = tempData.map(data => <div> {JSON.stringify(data)} </div>)
        console.log(tempData)
        setList(anotherData)
      })
      .catch(error => {
        console.log(error)
      })
  }, [])
  return (
    <>
      <div className='container'>
        <div className='row'>
          {
            list
          }
        </div>
      </div>
    </>
  );
}
export default App;
OMKAR AGRAWAL
  • 108
  • 1
  • 6