'Heroku ReactJS app. Error when using map function, cannot read properties of undefined

I'm trying to make a sport/tinder like app for a school project from a friend of mine. It came together well on my localhost, but for him it was a requirement to host it online. Not really a professional in hosting, but I was a bit familiar with Heroku. I used a client and a server side for my application, so I build the client side and put it into the server side folder. This server side is hosted on the Heroku page. But whenever I try to login, it won't work and I get this error message in my console.

TypeError: Cannot read properties of undefined (reading 'map')

I know there are a lot of other people with the same issue. I tried many solutions and I think something might be wrong with my code. I'm very new to ReactJS, that's why I used a tutorial for making this application. And I'm more like a Data Scientist then a Software Engineer, but I'm always eager to learn. That's why I took the oppurtunity to learn this new 'language'. So I might be wrong when it comes to the problem.

The error says it is caused by this line of code.

const matchedUserIds = user?.matches.map(({user_id}) => user_id).concat(userId)

This is the whole Dashboard file I used for making this page. I'm using a MongoDB for the storage of my users.

import TinderCard from 'react-tinder-card';
import {useEffect, useState} from 'react';
import {useCookies} from 'react-cookie';
import ChatContainer from '../components/ChatContainer'
import axios from "axios";

const Dashboard = () => {
const [user, setUser] = useState(null)
const [genderedUsers, setGenderedUsers] = useState(null)
const [cookies, setCookie, removeCookie] = useCookies(['user'])
const [lastDirection, setLastDirection] = useState()

const userId = cookies.UserId
const getUser = async () => {
    try {
        const response = await axios.get('https://[app].herokuapp.com/user', {
            params: {userId}
        })
        setUser(response.data)
    } catch (error) {
        console.log(error)
    }
}

const getGenderedUsers = async () => {
    try {
        const response = await axios.get('https://[app].herokuapp.com/gendered-users', {
            params: {gender: user?.gender_interest}
        })
        setGenderedUsers(response.data)
    } catch (error) {
        console.log(error)
    }
}

useEffect(() => {
    getUser()
}, [])

useEffect(() => {
    if (user) {
        getGenderedUsers()
    }
}, [user])

const updateMatches = async (matchedUserId) => {
    try {
        await axios.put('https://[app].herokuapp.com/addmatch', {
            userId,
            matchedUserId
        })
        getUser()
    } catch (error) {
        console.log(error)
    }
}

const swiped = (direction, swipedUserId) => {
    console.log(direction, swipedUserId)
    if (direction === 'right') {
        updateMatches(swipedUserId)
    }

    setLastDirection(direction)
}

const outOfFrame = (name) => {
    console.log(name + ' left the screen!')
}

const matchedUserIds = user?.matches.map(({user_id}) => user_id).concat(userId)

const filteredGenderedUsers = genderedUsers?.filter(
    genderedUser => !matchedUserIds.includes(genderedUser.user_id)
)

return (<>
    {user && <div className="dashboard">
        <ChatContainer user={user}/>
        <div className="swipe-container">
            <div className="card-container">

                {filteredGenderedUsers?.map((genderedUser) =>
                    <TinderCard
                        className='swipe'
                        key={genderedUser.user_id}
                        onSwipe={(dir) => swiped(dir, genderedUser.user_id)}
                        onCardLeftScreen={() => outOfFrame(genderedUser.first_name)}>
                        <div style={{backgroundImage: 'url(' + genderedUser.url + ')'}} className='card'>
                            <h3>{'Name: ' + genderedUser.first_name} <br/> {'Sport: ' + genderedUser.about}</h3>
                        </div>
                    </TinderCard>)}
                <div className="swipe-info">
                    {lastDirection ? <p>You swiped {lastDirection}</p> : <p/>}
                </div>
            </div>
        </div>
    </div>}
</>)
} 
export default Dashboard

Any help is welcome. If you need more code examples, please reply ;)

EDIT

My index.js file from my server, GET request for my user

app.get('/user', async (req, res) => {
const client = new MongoClient(uri)
const userId = req.query.userId

try {
    await client.connect()
    const database = client.db('app-data')
    const users = database.collection('users')

    const query = {user_id: userId}
    const user = await users.findOne(query)
    res.send(user)
} finally {
    await client.close()
}
})

My index.js file from my server, GET request for my gendered-users

app.get('/gendered-users', async (req, res) => {
const client = new MongoClient(uri)
const gender = req.query.gender

try {
    await client.connect()
    const database = client.db('app-data')
    const users = database.collection('users')
    const query = {gender_identity: {$eq: gender}}
    const foundUsers = await users.find(query).toArray()

    res.send(foundUsers)
} finally {
    await client.close()
}
})

A picture from my MongoDB users (Dummy data)

EDIT 2

The line of code that causes the new error

const matchedUserIds = matches.map(({user_id}) => user_id)

My MatchesDisplay file.

import axios from "axios";
import {useEffect, useState} from 'react';
import { useCookies } from "react-cookie";

const MatchesDisplay = ({matches, setClickedUser}) => {
const [matchedProfiles, setMatchedProfiles] = useState(null)
const [cookies, setCookie, removeCookie] = useCookies(null)
const [matched, setMatched] = useState(null)

const matchedUserIds = matches.map(({user_id}) => user_id)

const userId = cookies.UserId

const getMatches = async () => {
    try {
        const response = await axios.get('https://[app].herokuapp.com/users', {
            params: {userIds: JSON.stringify(matched())}
        })
        setMatchedProfiles(response.data)
    } catch (error) {
        console.log(error)
    }
}

useEffect(() => {
    getMatches()
}, [matches])

const filteredMatchedProfiles = matchedProfiles?.filter(
    (matchedProfile) =>
        matchedProfile.matches.filter((profile) => profile.user_id === userId).length > 0
)

return (
    <div className="matches-display">
        {filteredMatchedProfiles?.map((match) => (
            <div key={match.user_id} className="match-card" onClick={() => setClickedUser(match)}>
                <div className="img-container">
                    <img src={match?.url} alt={match?.first_name + 'profile'}/>
                </div>
                <h3>{match?.first_name}</h3>
            </div>
        ))}
    </div>
)
}
export default MatchesDisplay


Solution 1:[1]

By the default the user is null. And I already know that you have other methods that follow the same approach, and this might result the same error. Just make a simple check on the fields that are asynchronous. I think the quickest and easiest example would be:

import TinderCard from 'react-tinder-card';
import {useEffect, useState} from 'react';
import {useCookies} from 'react-cookie';
import ChatContainer from '../components/ChatContainer'
import axios from "axios";

const Dashboard = () => {
const [user, setUser] = useState(null)
const [genderedUsers, setGenderedUsers] = useState(null)
const [cookies, setCookie, removeCookie] = useCookies(['user'])
const [lastDirection, setLastDirection] = useState()

const userId = cookies.UserId
const getUser = async () => {
    try {
        const response = await axios.get('https://[app].herokuapp.com/user', {
            params: {userId}
        })
        setUser(response.data)
    } catch (error) {
        console.log(error)
    }
}

const getGenderedUsers = async () => {
    try {
        const response = await axios.get('https://[app].herokuapp.com/gendered-users', {
            params: {gender: user?.gender_interest}
        })
        setGenderedUsers(response.data)
    } catch (error) {
        console.log(error)
    }
}

useEffect(() => {
    getUser()
}, [])

useEffect(() => {
    if (user) {
        getGenderedUsers()
    }
}, [user])

const updateMatches = async (matchedUserId) => {
    try {
        await axios.put('https://[app].herokuapp.com/addmatch', {
            userId,
            matchedUserId
        })
        getUser()
    } catch (error) {
        console.log(error)
    }
}

const swiped = (direction, swipedUserId) => {
    console.log(direction, swipedUserId)
    if (direction === 'right') {
        updateMatches(swipedUserId)
    }

    setLastDirection(direction)
}

const outOfFrame = (name) => {
    console.log(name + ' left the screen!')
}

const matchedUserIds = user?.matches.map(({user_id}) => user_id).concat(userId)

const filteredGenderedUsers = genderedUsers?.filter(
    genderedUser => !matchedUserIds.includes(genderedUser.user_id)
)

if (user && genderedUsers) return (<>
    {user && <div className="dashboard">
        <ChatContainer user={user}/>
        <div className="swipe-container">
            <div className="card-container">

                {filteredGenderedUsers?.map((genderedUser) =>
                    <TinderCard
                        className='swipe'
                        key={genderedUser.user_id}
                        onSwipe={(dir) => swiped(dir, genderedUser.user_id)}
                        onCardLeftScreen={() => outOfFrame(genderedUser.first_name)}>
                        <div style={{backgroundImage: 'url(' + genderedUser.url + ')'}} className='card'>
                            <h3>{'Name: ' + genderedUser.first_name} <br/> {'Sport: ' + genderedUser.about}</h3>
                        </div>
                    </TinderCard>)}
                <div className="swipe-info">
                    {lastDirection ? <p>You swiped {lastDirection}</p> : <p/>}
                </div>
            </div>
        </div>
    </div>}
</>)
    return <>Loading ...</>
} 
export default Dashboard

If it doesn't work, probably there's something wrong with the data. Provide more info about request.

Solution 2:[2]

I'm still a react beginner myself and can't comment yet so to the answer section it is lol.

If I'm right, you are probably getting Cannot read properties of undefined (reading 'map') error because user is null when its first initialized and therefore not have map() yet. You probably didn't error on user?.matches because of optional chaining. The solution here is putting const matchedUserIds = user?.matches.map(({user_id}) => user_id).concat(userId) in a useEffect hook with user as dependency.

If that doesn't work, reply back

Code

import TinderCard from 'react-tinder-card';
import {useEffect, useState} from 'react';
import {useCookies} from 'react-cookie';
import ChatContainer from '../components/ChatContainer'
import axios from "axios";

const Dashboard = () => {
    const [user, setUser] = useState(null)
    const [genderedUsers, setGenderedUsers] = useState(null)
    const [lastDirection, setLastDirection] = useState(null)
    const [cookies, setCookie, removeCookie] = useCookies(['user'])
    const [matchedUserIds, setMatchedUserIds] = useState(null)
    const [filteredGenderedUsers, setFilteredGenderedUsers] = useState(null)

    const userId = cookies.UserId
    const getUser = async () => {
        try {
          const response = await axios.get('https://[app].herokuapp.com/user', {
            params: {userId}
          })
          return setUser(response.data)
        } catch (error) {
            console.log(error)
        }
    }
    const getGenderedUsers = async () => {
        try {
          const response = await axios.get('https://[app].herokuapp.com/gendered-users', {
              params: {gender: user?.gender_interest}
          })
          return setGenderedUsers(response.data)
        } catch (error) {
            console.log(error)
        }
    }

    useEffect(() => {
        getUser()
    }, [])

    useEffect(() => {
        setMatchedUserIds(user?.matches.map(({user_id}) => user_id).concat(userId))
        if (user) return getGenderedUsers()
    }, [user])

    useEffect(() => {
        if (genderedUsers) {
            return setFilteredGenderedUsers(genderedUsers?.filter(
                genderedUser => !matchedUserIds.includes(genderedUser.user_id)
            ))
        }
    }, [genderedUsers])

    const updateMatches = async (matchedUserId) => {
        try {
          await axios.put('https://[app].herokuapp.com/addmatch', {
            userId,
            matchedUserId
        })
        return getUser()
        } catch (error) {
            console.log(error)
        }
    }

    const swiped = (direction, swipedUserId) => {
        console.log(direction, swipedUserId)
        if (direction === 'right') {
            updateMatches(swipedUserId)
        }
        return setLastDirection(direction)
    }

    const outOfFrame = (name) => {
        console.log(name + ' left the screen!')
    }


    return (<>
      {user && <div className="dashboard">
          <ChatContainer user={user}/>
          <div className="swipe-container">
              <div className="card-container">
                  {filteredGenderedUsers?.map((genderedUser) =>
                      <TinderCard
                          className='swipe'
                          key={genderedUser.user_id}
                          onSwipe={(dir) => swiped(dir, genderedUser.user_id)}
                          onCardLeftScreen={() => outOfFrame(genderedUser.first_name)}>
                          <div style={{backgroundImage: 'url(' + genderedUser.url + ')'}} className='card'>
                              <h3>{'Name: ' + genderedUser.first_name} <br/> {'Sport: ' + genderedUser.about}</h3>
                          </div>
                      </TinderCard>)}
                  <div className="swipe-info">
                      {lastDirection ? <p>You swiped {lastDirection}</p> : <p/>}
                  </div>
              </div>
          </div>
      </div>}
  </>)
} 
export default Dashboard

Update 2: what I've done is made it a chain reaction of useEffects when the state is updated.

Flow of the code:

  1. When matches is updated, useEffect is called
  2. matchedUserIds is a new array with mapped ids
  3. useEffect with getMatches gets called because of the change in matchedUserIds
  4. getMatches is called and the axios call is made
  5. matchedProfiles is set to the response
  6. lastly, filteredMatchedProfiles is set to the filter

import axios from "axios";
import {useEffect, useState} from 'react';
import { useCookies } from "react-cookie";

const MatchesDisplay = ({matches, setClickedUser}) => {
const [matchedProfiles, setMatchedProfiles] = useState([])
const [cookies, setCookie, removeCookie] = useCookies(null)
const [matchedUserIds, setMatchedUserIds] = useState([])
const [filteredMatchedProfiles, setFilteredMatchedProfiles] = useState([])

const userId = cookies.UserId

const getMatches = async () => {
    try {
        const response = await axios.get('https://[app].herokuapp.com/users', {
            params: {userIds: matchedUserIds}
        })
        return setMatchedProfiles(response.data)
    } catch (error) {
        console.log(error)
    }
}

useEffect(() => {
    setMatchedUserIds(matches.map(({user_id}) => user_id))
}, [matches])
useEffect(() => {
    getMatches()
}, [matchedUserIds])

useEffect(() => {
    setFilteredMatchedProfiles(
        matchedProfiles?.filter(
            (matchedProfile) =>
                matchedProfile.matches.filter((profile) => profile.user_id === userId).length > 0
        )
    )
}, [matchedProfiles])


return (
    <div className="matches-display">
        {filteredMatchedProfiles?.map((match) => (
            <div key={match.user_id} className="match-card" onClick={() => setClickedUser(match)}>
                <div className="img-container">
                    <img src={match?.url} alt={match?.first_name + 'profile'}/>
                </div>
                <h3>{match?.first_name}</h3>
            </div>
        ))}
    </div>
)
}
export default MatchesDisplay

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2