Home > database >  React rendering before async function finishes running
React rendering before async function finishes running

Time:07-24

The program below reads data from a Firebase db and then stores it via setState to a variable. On the first render, no {problem} is undefined and so it cannot be used in the code. This means that React will just render a page with the hardcoded text. As soon as setState is run, the components re-render and the program displays the data. The only problem I have is that when I pass the {problem} variable to a child component, it returns the following error (error occurs on last line in AdditionalInfo.jsx shown).

AdditionalInfo.jsx:45 Uncaught TypeError: Cannot read properties of undefined (reading 'map')

Why does the program only return an error for the child component and how can I fix this?

Problem.jsx

const [problem, setProblem] = useState({});


    useEffect(() => {
        const getProblem = async () => {
            const problemsRef = collection(db, "problems");
            const q = query(problemsRef, where("problemNumber", "==", parseInt(params.id)));
            const querySnapshot = await getDocs(q);
            setProblem({...querySnapshot.docs[0].data()});
        }
        getProblem();
    }, [params.id]);
.
.
.

   return (
        <Split
        gutterSize={10}
        gutterAlign="center"
        cursor="col-resize"
        snapOffset={75}
        className='split'
        minSize={0}>
            <div className="bg-gray-100 min-h-screen max-h-screen overflow-y-auto"> {/* min-h-screen causes there to be a small scroll because of the navbar. To fix this use someting like min-h-screen minus height of navbar */}
                <div className='mx-2 my-2 text-1xl font-medium'>
                    {problem.problemNumber}. {problem.problemTitle}
                </div>
                <div className='mx-2 my-2 text-1xl font-normal text-green-600'>
                    {problem.problemType}
                </div>
                <Divider variant="middle" />
                <div className='mx-2 my-2 text-1xl text-base'>
                    {problem.problemQuestion}
                </div>

                <div>
                    {mapToArray(problem.companies).map(([key, value]) => {
                        return <CompanyTag key={key} companyName={value} companyId={key} />
                    }
                    )}
                </div>
                <AdditionalInfo problem={problem}/>
            </div>
                
            <div className="bg-gray-100">
                <div className='mx-7'>
                    <video width="100%" height="100%px" controls>
                        <source src="movie.mp4" type="video/mp4"></source>
                    </video>
                </div>
            </div>

        </Split>
    )   

AdditionalInfo.jsx

const AdditionalInfo = ({ problem }) => {

  return (
    <div>
        {problem.questionTips.map((tip) => {
            return <Tip key={tip.number} number={tip.number} description={tip.description} />
        })} // Code causes an error here
.
.
.

CodePudding user response:

To answer as an answer.

Error is due you initially set const [problem, setProblem] = useState({}); so on first render your problem is just an empty object. In the child component where you passed your problem you are trying to use problem.questionTips.map() fn in jsx and due to on first render problem.questionTips is undefined - trying to access .map on undefined gives you your error.

You should use ? operator to solve this issue.

{problem.questionTips?.map((tip) => {
  return <Tip key={tip.number} number={tip.number} description={tip.description} />
})}

Answering on additional question in the comment: "Is there a way to full wait until all the data is read from firebase before rendering?"

Yes, there is a way, just use

{problem?.questionTips && <AdditionalInfo problem={problem}/>}

That will not render your AdditionalInfo component until problem with questionTips is loaded

  • Related