0

So I have this Display() function which fetches events from the Google Calendar via an API and store each event's name via element.summary into the events set. And then once the events set is populated, I iterate through the set via for (let item of events) and create a new <a> tag for each event/item in the set using the name as the text via <a>{item}</a> (for e.g. <a>call<a>, then I push each <a> tag into a new array called tabs and then finally return the tabs array. The events set contains three items and when I console.log, I see the correct items ("call", "kist", & "go") in the set. However, once I console.log the tabs array, it only contains one <a> tag whose value is null whereas it is supposed to contain three <a> tags since it iterates through the events set which has three items and is supposed to create an <a> tag for each. Also, I get the error that item is not defined for the line for (let item of events), somehow I cannot iterate through the events set. See console output here.

function Display() {
    let events = new Set()
    let tabs = []
    ApiCalendar.listUpcomingEvents(10)
        .then(({result}: any) => {
            result.items.forEach(element => {
                    console.log(element.summary)
                    events.add(element.summary)
                }
            );
            console.log(events)
            for (let item of events)
                console.log(item)
                tabs.push(<a>{item}</a>)
            console.log(tabs)
            return tabs
        });
}

This is the class that I made in the same file as the above function, which basically renders a 'Log In' button if user is not logged in to their calendar, or renders the array of <a> tags returned by the Display() function if user is already logged in. However, even though the Display() function above does return something (i.e. an array of <a> tags) and the render() function inside the class also returns a <div> element with the corresponding component inside the div, I get the error Uncaught Error: Display(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null. I am new to JavaScript and have no idea what I'm doing wrong. Any help is greatly appreciated and thank you in advance.

export default class LoginControl extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            sign: ApiCalendar.sign,
        };
    }

    render() {
        const isLoggedIn = this.state.sign;
        let ele;

        if (isLoggedIn) {
            ele = <Display/>;
        } else {
            ele = <Button>'Sign In'</Button>;
        }

        return (
            <div>
                {ele}
            </div>
        );
    }
}
Heart Killer
  • 7
  • 1
  • 7
  • The `return` statement is not inside the `Display` functio but the callback in given to `then` – VLAZ Feb 04 '20 at 12:01
  • Always use curly braces on multi-line constructs, like your for-of here, since it's easy to add a line, like your `console.log(item)`, and break the logic since now, the actual body of the construct is outside its scope, like `tabs.push({item})` in your snippet. – Emile Bergeron Feb 04 '20 at 18:03

2 Answers2

0

Your Display function calls an async method and returns nothing. You will need to utilize state and effect inside Display to render returned data. But then, you will encounter errors if user navigates away from page before data is fetched.

Best solution for this problem would be to utilize redux and redux-thunk


Caution, untested code below

If you feel like you don't need redux, try this approach

async function fetchItems() {
  const result = await ApiCalendar.listUpcomingEvents(10);
  return result.result.items.map(({summary}) => summary);
}

function Display() {
  const [items, saveItems] = useState([]);
  const isMounted = useRef(true);

  useEffect(() => {
    return () => {
      isMounted.current = false;
    };
  }, []);

  useEffect(() => {
    (async () => {
      const items = await fetchItems();
      //Do not update state if component is unmounted
      if (isMounted.current) {
        saveItems(items);
      }
    })();
  }, []);

  return <>{items.map(item => <a key={item}>{item}</a>)}</>
}

If you want to render more than summary, you can do it like this

async function fetchItems() {
  const result = await ApiCalendar.listUpcomingEvents(10);
  return result.result.items.map(({summary, somethingElse}) => ({summary, somethingElse}));
  //can be replaced with return [...result.result.items]; to get all props
}

function Display() {

  //... Main logic of Display component is the same, 
  //so I wouldn't duplicate it here

  return <>{items.map(item => <div key={item.summary}>{item.summary} {item.somethingElse}</div>)}</>
}
Gennady Dogaev
  • 5,902
  • 1
  • 15
  • 23
  • I get the error: ```Uncaught (in promise) TypeError: Cannot read property 'map' of undefined at fetchItem``` – Heart Killer Feb 04 '20 at 12:41
  • @HeartKiller well, "untested" means exactly that - it can throw errors. This specific one means `result` does not have `items`. Looking at code again, I think it should be `result.result.items.map` – Gennady Dogaev Feb 04 '20 at 12:59
  • It seems to work now but when i click button on the page to go to next page, I get this error: ```func.apply``` is not a function along with this warning: It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately: ```useEffect(() => { async function fetchData() { // You can await here const response = await MyAPI.getData(someId); // ... } fetchData(); }, [someId]); // Or [] if effect doesn't need props/state``` – Heart Killer Feb 04 '20 at 13:47
  • @HeartKiller see updated answer, that should solve it – Gennady Dogaev Feb 04 '20 at 14:00
  • thank you so much, it finally works perfect. Just a quick question, if I wanted more than one item (i.e, more than just the summary of an event from ```ApiCalendar.listUpcomingEvents()```, such as startTime and endTime of event), how would I adjust the code so that I can retrieve that information as well? I'm assuming I'll have to change something around ```return result.result.items.map(({summary}) => summary); }```? – Heart Killer Feb 04 '20 at 14:26
  • 1
    thank you so much for your patience and knowledge. I tried to upvote your solution but I couldn't since I didn't have enough reputation but I really appreciate your help and I have accepted your solution. – Heart Killer Feb 04 '20 at 15:41
0

It seems that you are not returning anything on Display component.

You can't return a promise on a component so you need to make it inside useEffect using react hooks or component lifecycle - and no, you don't need redux just to achieve this.

function Display() {
    let events = new Set()
    let tabs = [];
    const [items, setItems] = useState([]);

    const getList = async () => {
      const res = await  ApiCalendar.listUpcomingEvents(10);

       setItems(res.items);
    }

    useEffect(async () => {
      getList();
    }, []);

  return items.map(item => <div>{item}</div>);
}
I am L
  • 4,288
  • 6
  • 32
  • 49
  • 1
    Yeah, but: 1) Your code will fetch data on every render 2) React will show warning about memory leak if component is unmounted in process – Gennady Dogaev Feb 04 '20 at 12:14
  • oops I forgot to add the second argument `[]` empty array. it should only call the API once the component did mounted and not on every render – I am L Feb 04 '20 at 12:16
  • @IamL I get error ```setItems``` is not defined. Also I was wondering, is it supposed to be ```
    {item.summary}
    ``` since I want the name of the event and not the entire object? Thanks.
    – Heart Killer Feb 04 '20 at 12:25
  • @IamL I also get error ```Uncaught TypeError: Cannot read property 'map' of null```. – Heart Killer Feb 04 '20 at 12:50
  • @HeartKiller I updated the answer - I never tested it and just wrote from scratch – I am L Feb 04 '20 at 13:33
  • React will show a warning regardless because [you should **not** be returning a promise in the `useEffect` callback](https://stackoverflow.com/q/53332321/1218980). – Emile Bergeron Feb 04 '20 at 18:41