I'm trying to pass the videoTitle from Link's state to my Quiz page. But I can not pass it through useLocation. It throws me this error: TypeError: Cannot destructure property 'state' of 'location' as it is undefined.
From this link component:
To this: enter image description here
here is the full code of Videos page:
import React, { useState } from 'react';
import InfiniteScroll from 'react-infinite-scroll-component';
import { Link } from 'react-router-dom';
import useVideoList from '../hooks/useVideoList';
import classes from '../styles/Videos.module.css';
import Video from './Video';
export default function Videos() {
const [page, setPage] = useState(1);
const { loading, error, videos, hasMore } = useVideoList(page);
return (
<div className={classes.videos}>
{videos.length > 0 && (
<InfiniteScroll
dataLength={videos.length}
next={() => setPage(page 9)}
hasMore={hasMore}
loader={<h4>Loading...</h4>}
endMessage={
<p style={{ textAlign: 'center' }}>
<b>Yay! You have seen it all</b>
</p>
}
>
{videos.map((video) =>
video.noq > 0 ? (
<Link
to={{
pathname: `/quiz/${video.youtubeID}`,
state: {
videoTitle: video.title,
},
}}
key={video.youtubeID}
>
<Video
title={video.title}
id={video.youtubeID}
noq={video.noq}
/>
</Link>
) : (
<Video
title={video.title}
id={video.youtubeID}
noq={video.noq}
key={video.youtubeID}
/>
)
)}
</InfiniteScroll>
)}
{!loading && videos.length === 0 && <div>No data found!</div>}
{error && <div>There was an error!</div>}
{loading && <div>Loading...</div>}
</div>
);
}
Here is the full code of Quiz page and where I want to pass the videoTitle from Link component:
import { getDatabase, ref, set } from 'firebase/database';
import _ from 'lodash';
import React, { useEffect, useReducer, useState } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router';
import { useAuth } from '../../context/AuthContext';
import useQuestions from '../../hooks/useQuestions';
import Answers from '../Answers';
import MiniPlayer from '../MiniPlayer';
import ProgressBar from '../ProgressBar';
const initialState = null;
const reducer = (state, action) => {
switch (action.type) {
case 'questions':
action.value.forEach((question) => {
question.options.forEach((option) => {
option.checked = false;
});
});
return action.value;
case 'answer':
const questions = _.cloneDeep(state);
questions[action.questionID].options[action.optionIndex].checked =
action.value;
return questions;
default:
return state;
}
};
export default function Quiz() {
const { id } = useParams();
const { loading, error, questions } = useQuestions(id);
const [currentQuestion, setCurrentQuestion] = useState(0);
const [qna, dispatch] = useReducer(reducer, initialState);
const navigate = useNavigate();
const { location } = useLocation();
const { state } = location;
const { videoTitle } = state;
const { currentUser } = useAuth();
useEffect(() => {
dispatch({
type: 'questions',
value: questions,
});
}, [questions]);
function handleAnswerChange(e, index) {
dispatch({
type: 'answer',
questionID: currentQuestion,
optionIndex: index,
value: e.target.checked,
});
}
// handle when user hit the next button to get next questions
function nextQuestion() {
if (currentQuestion 1 < questions.length) {
setCurrentQuestion((prevCurrent) => prevCurrent 1);
}
}
// handle when user hit the prev button to get the prev questions
function prevQuestion() {
if (currentQuestion >= 1 && currentQuestion <= questions.length) {
setCurrentQuestion((prevCurrent) => prevCurrent - 1);
}
}
// submit quiz
async function submit() {
const { uid } = currentUser;
const db = getDatabase();
const resultRef = ref(db, `result/${uid}`);
await set(resultRef, {
[id]: qna,
});
navigate(`/result/${id}`, {
state: {
qna,
},
});
}
//calculate percentage of progress
const percentage =
questions.length > 0 ? ((currentQuestion 1) / questions.length) * 100 : 0;
return (
<>
{loading && <div>Loading...</div>}
{error && <div>There was an error!</div>}
{!loading && !error && qna && qna.length > 0 && (
<>
<h1>{qna[currentQuestion].title}</h1>
<h4>Question can have multiple answers</h4>
<Answers
input
options={qna[currentQuestion].options}
handleChange={handleAnswerChange}
/>
<ProgressBar
next={nextQuestion}
prev={prevQuestion}
submit={submit}
progress={percentage}
/>
<MiniPlayer id={id} title={videoTitle} />
</>
)}
</>
);
}
CodePudding user response:
const { location } = useLocation();
to
const location = useLocation();
const { state } = location;
or
const { state }= useLocation();
Just destructure error
CodePudding user response:
In react-router-dom
v6 the Link
component API changed a bit, route state is now a top-level prop instead of being nested in a to
prop's object.
interface LinkProps extends Omit< React.AnchorHTMLAttributes<HTMLAnchorElement>, "href" > { replace?: boolean; state?: any; to: To; reloadDocument?: boolean; }
Move the nested state
property out to be a link state
prop.
<Link
to={`/quiz/${video.youtubeID}`}
state={{ videoTitle: video.title }} // <-- state prop
key={video.youtubeID}
>
<Video
title={video.title}
id={video.youtubeID}
noq={video.noq}
/>
</Link>
You also are not accessing the location
object correctly, the useLocation
hook returns a location
object, not an object with a location
property.
const location = useLocation();
const { state } = location;
or destructure state
directly
const { state } = useLocation();