Cannot read property 'map' of undefined - React Custom Hooks / useContext one works / one doesn't

389 Views Asked by At

I am currently following a tutorial on youtube building a whatsapp clone with react and socket.io from webDevSimplified: https://www.youtube.com/watch?v=tBr-PybP_9c

and here the main repo : 'https://github.com/WebDevSimplified/Whatsapp-Clone/tree/master/client

I got stuck halfway through as for some reason my custom useConversation hook returns undefined while my useContacts hook works without any problems.

Here the setup:

App.js :

import Dashboard from "./Dashboard";
import { ContactsProvider } from "../contexts/ContactsProvider";
import { ConversationsProvider } from "../contexts/ConversationsProvider";
import Test from "../components/test";///test

console.log(Test)//test purpose

function App() {
  const [id, setId] = useLocalStorage("id");

  const dashboard = (
    <ContactsProvider>
      <ConversationsProvider id={id}>
        <Dashboard id={id} />
      </ConversationsProvider>
    </ContactsProvider>
  );

  return id ? dashboard : <Login onIdSubmit={setId} />;
}



export default App;

ContactsProvider.js

import React, { useContext } from "react";
import useLocalStorage from "../hooks/useLocalStorage";

const ContactsContext = React.createContext();

export function useContacts() {
  return useContext(ContactsContext);
}

export function ContactsProvider({ children }) {
  const [contacts, setContacts] = useLocalStorage("contacts", []);//initalValue an empty array

  function createContact(id, name) {
    setContacts((prevContacts) => {
      return [...prevContacts, { id, name }];
    });
  }
  return (
    <ContactsContext.Provider value={{ contacts, createContact }}>
      {children}
    </ContactsContext.Provider>
  );
}

Contacts.js - here my useContacts works

import React from "react";
import { ListGroup } from "react-bootstrap";
import { useContacts } from "../contexts/ContactsProvider";

export default function Contacts() {
  const { contacts } = useContacts();
  console.log(`contacts: ${contacts}`); //returns object, object as expected

  return (
    <ListGroup variant="flush">
      {contacts.map((contact) => (//visibly working in UI when commenting out the ListGroup in conversations.js
        <ListGroup.Item key={contact.id}>{contact.name}</ListGroup.Item>
      ))}
    </ListGroup>
  );
}

Here the problematic part:

ConversationsProvider.js

import React, { useContext } from "react";
import useLocalStorage from "../hooks/useLocalStorage";
import { useContacts } from "./ContactsProvider";

const ConversationsContext = React.createContext();

export function useConversations() {
  return useContext(ConversationsContext);
}

export function ConversationsProvider({ children }) {
  const [conversations, setConversations] = useLocalStorage(
    "conversations", []);//as well empty array

  const { contacts } = useContacts();

  function createConversation(recipients) {
    setConversations((prevConversations) => {
      return [...prevConversations, { recipients, messages: [] }];
    });
  }

  const formattedConversations = conversations.map((conversation) => {
    const recipients = conversation.recipients.map((recipient) => {
      const contact = contacts.find((contact) => {
        return contact.id === recipient;
      });
      const name = (contact && contact.name) || recipient;
      return { id: recipient, name };
    });

    return { ...conversation, recipients };
  });

  const value = {
    conversations: formattedConversations,
    createConversation,
  };

  return (
    <ConversationsContext.Provider value={{ value }}>
      {children}
    </ConversationsContext.Provider>
  );
  
}

and the component that causes the error:

Conversations.js:

import React from "react";
import { ListGroup } from "react-bootstrap";
import { useConversations } from "../contexts/ConversationsProvider";

export default function Conversations() {
  const { conversations } = useConversations();
  console.log( `conversations: ${conversations}`)//returns undefined
  

  return (
    <ListGroup variant="flush">
      {conversations.map((conversation, index) => (//can't map because conversations is undefined
        <ListGroup.Item key={index}>
          {conversation.recipients
            .map(r => r.name)
            .join(", ")}
        </ListGroup.Item>
      ))}
    </ListGroup>
  );
}

Here the localStorage setup for clarity:

import { useEffect, useState } from 'react'

const PREFIX = 'whatsapp-clone-'

export default function useLocalStorage(key, initialValue) {
  const prefixedKey = PREFIX + key;
  const [value, setValue] = useState(() => {
    const jsonValue = localStorage.getItem(prefixedKey);
    if (jsonValue != null) return JSON.parse(jsonValue);
    if (typeof initialValue === "function") {
      return initialValue();
    } else {
      return initialValue;
    }
  });

  useEffect(() => {
    localStorage.setItem(prefixedKey, JSON.stringify(value));
  }, [prefixedKey, value]);

  return [value, setValue];
}

I have been trying to solve this problem for hours and running out of ideas. I setup a test.js and imported the hooks in a Test.js function. Both return their objects respectively.

I have created the application using npx create-react-app and running it via yarn.

1

There are 1 best solutions below

1
On BEST ANSWER

The problem lies within your ConversationsProvider.js:

const value = {
  conversations: formattedConversations,
  createConversation,
};

return (
  <ConversationsContext.Provider value={{ value }}>
    {children}
  </ConversationsContext.Provider>
);

The following lines are all the same:

<ConversationsContext.Provider value={{ value }}>
<ConversationsContext.Provider value={{ value: value }}>
<ConversationsContext.Provider value={{ value: { conversations: formattedConversations, createConversation: createConversation } }}>

When you execute:

const { conversations } = useConversations();

conversations will be set to undefined because the object returned will only have the property value.

To fix the issue remove the nesting by changing the line:

<ConversationsContext.Provider value={{ value }}>
// to
<ConversationsContext.Provider value={value}>

PS. Here a tip to improve your debugging skills. You already did:

const { conversations } = useConversations();
console.log(conversations);

Which logged undefined. The next step would be to not destruct the object immediately and log the whole context value instead.

const contextValue = useConversations();
console.log(contextValue);

This would have shown that the object returned by useConversations is not what you expect it to be.