0

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

2 Answers2

0

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
Nugget
  • 81
  • 1
  • 6
  • 23
  • I tried doing what you said, but I'm not really sure what you mean. I tried putting this in my code `useEffect(() => { const matchedUserIds = user?.matches.map(({user_id}) => user_id).concat(userId) }, [user])` I'm not sure if I'm doing it right. But when I do this, matchedUserIds become undefined elsewhere in my Dashboard file. – Jan Vermeer May 16 '22 at 19:02
  • @JanVermeer I think what you are trying to do is when the component loads, call the functions one after the other and populate the useState while also defining other stuff with it. – Nugget May 16 '22 at 19:38
  • @JanVermeer Again, If I'm right, set with useState instead of defining with const. make a new useState for matchUsersId and then in the useEffect with user dependency, set it with your data. You also have filteredGenderUsers so again make new useState, put inside useEfect with dependency of genderedUsers – Nugget May 16 '22 at 19:49
  • I'm not really sure what you mean with this comment, sorry. Do you maybe have a code example so that I can understand it better? – Jan Vermeer May 17 '22 at 09:21
  • @JanVermeer I've updated the answer with the code. Try to see if it works – Nugget May 17 '22 at 14:39
  • Yes, this answer actually worked out. The error on my dashboard file is gone, but I got the same error again but now for my Matches display file. I included this MatchesDisplay file in the edit. I tried fixing it the same way by using `const [matched, setMatched] = useState(null)` and then writing this down `useEffect(() => { setMatched(matches.map(({user_id}) => user_id)) if (matches) return matched() }, [matches])`. But unfortunately this didn't work. Did I do something wrong? – Jan Vermeer May 17 '22 at 15:22
  • @JanVermeer Yes. I can see a number of them 1. You are trying to map an array (matchedUserIds) but not using the returned array 2. You are calling matched even if you are not setting it with anything so by default it will be null 3. Matched is not a function and you are trying to stringify it. [Will not work](https://stackoverflow.com/questions/12651977/why-cant-you-stringify-a-function-expression) 4. You are still not using useState to set instead of define with const (filteredMatchedProfile) – Nugget May 17 '22 at 18:11
  • Thank you so much for taking the time to explain me this. I get now what I did wrong, but I'm not sure how to implement this into my code. I don't want to bother or impose you, but do you maybe have a code example for me so that I can try to implement it? – Jan Vermeer May 17 '22 at 19:21
  • @JanVermeer updated the answer again. Try to see if it works – Nugget May 18 '22 at 05:30
  • Hmmm, I just tried the new solution and both map functions are giving back the same error again. Any idea what is causing this? – Jan Vermeer May 18 '22 at 16:11
0

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.

Dumitru
  • 384
  • 4
  • 8