Home > Blockchain >  I can not get the state from react router Link component using useLocation. So how can I pass it?
I can not get the state from react router Link component using useLocation. So how can I pass it?

Time:12-04

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:

enter image description here

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.

Link

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();
  • Related