1

I'm attempting to map an array of objects, obtained from an axios.get request, as a set of React component children. Here is the full error I'm receiving:

Uncaught Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

My goal here is to create an Admin component where new shows can be created, and existing shows can be edited and deleted. I've confirmed that the axios request's response.data is an array of objects, and I'm not passing the objects themselves as children. I'm passing them as props to <ShowListing /> components, and I know that this component works with those props. Can someone please help me figure out what's going wrong here?

Here is the code for the Admin component. It seems that the error is occurring from lines 107-113:

import { useState, useEffect } from 'react'
import styles from './Admin.module.css'
import axios from 'axios'
import FormData from 'form-data'
import ShowListing from './ShowListing.js'

const Admin = async () => {
    const [formValues, setFormValues] = useState({
        eventTitle: null,
        location: null,
        date: null,
        time: null,
        ticket: null,
        desc: null,
        image: null
    })

    const [currShows, setCurrShows] = useState(null)

    useEffect(async () => {
        axios.get("http://localhost:4000/getShows").then(response => {
            console.log(response.data)
            setCurrShows(response.data)
        })
    })

    const handleSubmit = async (e) => {
        e.preventDefault()

        const formData = new FormData()

        // FILE READER
        const getImageFile = () => {
            return new Promise(resolve => {
                const reader = new FileReader()
                reader.onload = function () {
                    resolve(reader.result)
                    // console.log(`IMAGE FILE:\n ${imageFile}`) // imageFile IS NOT UNDEFINED HERE, BASE64 STRING
                }
                reader.readAsDataURL(document.getElementById("image").files[0])
            })
        }
        
        const imageFile = await getImageFile()
        
        Array.from(document.getElementById("form").elements).forEach(element => {           
            switch (element.name){
                case "image":
                    formData.append(`${element.name}`, imageFile) // UNDEFINED. WHY?
                    break
                case "submit":
                    break
                default:
                    formData.append(`${element.name}`, element.value)
            }
        })

        console.log([...formData])

        try {
            const response = axios.post('http://localhost:4000/uploadShow', formData)
            console.log(response)
            alert('NEW SHOW SUBMITTED')
            document.getElementById("form").reset()

        } catch (e) {
            alert(e)
            console.log(e)
        }
    }

    return (
        <div>
            <div className={styles.main}>
                <div className={styles.titleContainer}>
                    <h1>KMAC ADMIN</h1>
                </div>                
                <div className={styles.formWindow}>
                    <div className={styles.newShowHeader}>
                        <h1>New Show</h1>
                        <form className={styles.showForm} id="form" method="post" encType="multipart/form-data" onSubmit={e => handleSubmit(e)}>
                            <label htmlFor="eventTitle">Event Title: </label>
                            <input className={styles.fieldInput} type="text" name="eventTitle" onChange={e => setFormValues({...formValues, eventTitle: e.target.value})}/>
                            <br />
                            <label htmlFor="location">Location: </label>
                            <input className={styles.fieldInput} type="text" name="location"onChange={e => setFormValues({...formValues, location: e.target.value})} />
                            <br />
                            <label htmlFor="date">Date: </label>
                            <input className={styles.fieldInput} type="date" name="date" onChange={e => setFormValues({...formValues, date: e.target.value})}/>
                            <br />
                            <label htmlFor="time">Time: </label>
                            <input className={styles.fieldInput} type="time" name="time" onChange={e => setFormValues({...formValues, time: e.target.value})}/>
                            <br />
                            <label htmlFor="ticket">Ticket: </label>
                            <input className={styles.fieldInput} type="text" name="ticket" onChange={e => setFormValues({...formValues, ticket: e.target.value})}/>
                            <br />
                            <textarea name="desc" placeholder="Event Description" rows="8" onChange={e => setFormValues({...formValues, desc: e.target.value})}/>
                            <br />
                            <label htmlFor="image">Select Image &#40;15MB or less&#41;: </label>
                            <input type="file" id="image" name="image" accept="image/jpeg" onChange={e => setFormValues({...formValues, image: e.target.files})}/>
                            <br />
                            <button className={styles.submit} name="submit" type="submit">Submit</button>
                        </form>
                    </div>
                </div>
            </div>
            <div>
                {
                    currShows.map(show => {
                        return <ShowListing params={show} />
                    })
                }
            </div>
        </div>
    )
}

export default Admin

Here is the code for the ShowListing component:

import { useState } from 'react'
import axios from 'axios'
import styles from './ShowListing.module.css'

// SHOW OBJECT SHAPE:
//     eventTitle: null,
//     location: null,
//     date: null,
//     time: null,
//     ticket: null,
//     desc: null,
//     image: null

const ShowListing = (props) => {
    // Toggle deletion warning
    const [deleteWarning, setDeleteWarning] = useState(false)
    const [editForm, setEditForm] = useState(false)
    const [formValues, setFormValues] = useState({
        eventTitle: null,
        location: null,
        date: null,
        time: null,
        ticket: null,
        desc: null,
        image: null
    })

    // covert props.params.date to format "year-month-day", call for date input default value
    const dateConvert = () => {
        const dateArr = props.params.date.split('-')
        const year = dateArr.pop()
        dateArr.unshift(year)
        return dateArr.join('-')
    }

    // covert props.param.time to 24-hour format, call for time input default value
    const timeConvert = () => {
        const timeArr = props.params.time.split(' ')
        const time = timeArr[0].split(":")
        if (timeArr[1] === 'PM')
            time[0] = ((parseInt(time[0])) + 12).toString()
        if (parseInt(time[0]) < 10)
            time[0] = "0" + time[0]
        return time.join(":")
    }

    const handleDelete = () => {
        // TODO: delete request with props.params._id

        // Alert deletion and reload page
        alert(`SHOW DELETED:\n${props.params.eventTitle}`)
        window.location.reload()
    }

    const handleEditSubmit = (e) => {     
        e.preventDefault()

        // TODO: post request for show update with props.params._id
        
        console.log(formValues)
        alert(`SHOW EDITED:\n${formValues.eventTitle}\nFORMERLY:\n${props.params.eventTitle}`)
        window.location.reload()
    }

    return (
        <div className={styles.temp}>
        <div className={styles.container}>
            {
                deleteWarning &&
                <div className={styles.deleteWarning}>
                    <div><p>Delete this show listing?</p></div>
                    <div><button className={`${styles.deleteButton} ${styles.deleteYes}`} onClick={handleDelete}>Yes</button></div>
                    <div><button className={`${styles.deleteButton} ${styles.deleteNo}`} onClick={() => setDeleteWarning(false)}>No</button></div>
                </div>
            }
            <div className={styles.title}>
                <p>{props.params.eventTitle}</p>
            </div>
            <div className={styles.date}>
                <p>{`${props.params.date} -- ${props.params.time}`}</p>
            </div>
            <div className={styles.edit} onClick={() => setEditForm(true)}>
                <img src="images/icons8-edit-30.png" />
            </div>
            <div className={styles.delete} onClick={() => setDeleteWarning(true)}>
                <img src="images/icons8-trash-30.png" />
            </div>
            <br/>
        </div>
        {
            editForm &&
            <div className={styles.formContainer}>
                <div className={styles.formFrame}>
                    <form id="editForm" onSubmit={e => handleEditSubmit(e)}>
                        <label className={styles.formLabel} htmlFor="eventTitle">Event Title: </label>
                        <br />
                        <input className={styles.fieldInput} type="text" name="eventTitle" defaultValue={props.params.eventTitle} onChange={e => setFormValues({...formValues, eventTitle: e.target.value})}/>
                        <br />
                        <label className={styles.formLabel} htmlFor="location">Location: </label>
                        <br />
                        <input className={styles.fieldInput} type="text" name="location" defaultValue={props.params.location} onChange={e => setFormValues({...formValues, location: e.target.value})} />
                        <br />
                        <label className={styles.formLabel} htmlFor="date">Date: </label>
                        <br />
                        <input className={styles.fieldInput} type="date" name="date" defaultValue={dateConvert()} onChange={e => setFormValues({...formValues, date: e.target.value})}/>
                        <br />
                        <label className={styles.formLabel} htmlFor="time">Time: </label>
                        <br />
                        <input className={styles.fieldInput} type="time" name="time" defaultValue={timeConvert()} onChange={e => setFormValues({...formValues, time: e.target.value})}/>
                        <br />
                        <label className={styles.formLabel} htmlFor="ticket">Ticket: </label>
                        <br />
                        <input className={styles.fieldInput} type="text" name="ticket" defaultValue={props.params.ticket} onChange={e => setFormValues({...formValues, ticket: e.target.value})}/>
                        <br />
                        <br />
                        <textarea className={styles.formDesc} name="desc" placeholder="Event Description" rows="8" defaultValue={props.params.desc} onChange={e => setFormValues({...formValues, desc: e.target.value})}/>
                        <br />
                        <label className={styles.formLabel} htmlFor="image">Please update image &#40;15MB or less&#41;: </label>
                        <input style={{color: "red"}} type="file" id="image" name="image" accept="image/jpeg" onChange={e => setFormValues({...formValues, image: e.target.files})}/>
                        <br />
                        <br />
                        <button className={styles.submit} name="submit" type="submit">Submit</button>
                        <button name="cancel" onClick={() => setEditForm(false)}>Cancel</button>
                    </form>
                </div>
            </div>
        }
        </div>        
    )
}

export default ShowListing

UPDATE: Per phasma's suggestion, I removed async from the Admin component and the useEffect. This cleared the original error, but now I'm receiving a new error:

Admin.js:107 Uncaught TypeError: Cannot read properties of null (reading 'map')

Why is currShows null when it's being set in the useEffect?

1 Answers1

2

You're marking your Admin component async, which is not valid for a React component. Remove that modifier and the error should clear up.

edit; To answer your new question, currShows is null until the call is completed, which is likely after the first render. You should write something like

currShows === null ? null : currShows.map(show => { /* ... */

to handle that case explicitly. The null could be a loading spinner or something to indicate something's working in the background, but hopefully that helps clear your error.

phasma
  • 624
  • 6
  • 18
  • Side note, you can also remove the `async` modifier in your `useEffect` – phasma Nov 03 '22 at 01:08
  • Thanks for your prompt response. Removing those asyncs did clear that error, but now I'm getting a new error. It seems that I'm either not retrieving the data correctly through the axios request, or it's somehow being lost before I attempt to map it on line 107. Updating the question with the new error... – Pleasant Nightmares Nov 03 '22 at 01:12
  • Just FYI, your line numbers didn't make it to the question. It might be helpful to reference the calls by name. – phasma Nov 03 '22 at 01:16
  • @PleasantNightmares To answer your new question, `currShows` is `null` until the call is *completed* (when `then` callback is fired), which is likely _after_ the first render. You should write something like `currShows === null ? null : currShows.map(show => { /* ... */ ` to handle that case explicitly. (updated answer) – phasma Nov 03 '22 at 01:18
  • Fantastic. It works. Thanks very much for your help. One final question, though. It seems to be retrieving the data twice, based console logs from the useEffect hook. Is there a reason for that? – Pleasant Nightmares Nov 03 '22 at 01:51
  • Your `useEffect` does not have a dependency array, so any time a prop changes, it will fire again. However, I don't see any props in `Admin`, so that's probably not it. Alternatively, there may be a parent component that is unmounting and remounting. My hunch -- React 18 made a change that, when in Strict Mode, all components mount and unmount, then mount again. If you're using React 18 in strict mode, that may be the culprit. See https://medium.com/geekculture/the-tricky-behavior-of-useeffect-hook-in-react-18-282ef4fb570a for more info on that. – phasma Nov 03 '22 at 02:00