yamashee
@yamashee

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Next. jsのApp routerで、useReducerを使ってコンポーネント間でpropsを受け渡したい

これはNext.jsで実装した簡単な英語クイズアプリです。
10問解くと、リザルト画面に自分の回答が表示されます。

question/page.jsxで生成した'addAnswers'をresult/page.jsxで表示させたいのですが、useRudecerへの理解が追いついておらずどう実装すればいいのか分かりません。

おそらくaddAnswers.jsxresult/page.jsxをちょっといじればできるんだと思うのですが、なかなかうまくいきません。

result/page.jsxに自分のこれまでの回答が一覧で表示されるようにするにはあとどこを修正すれば完成しますか?

datas/datas.js
'use client'
const data = [
  {
    'question': 'Ms. Ortega’s development team contributed ——– to the successful launch of the newest mobile application.',
    'choices': ['automatically', 'substantially', 'sharply', 'accordingly'],
    'answer': 'substantially'
  },
  {
    'question': 'In order to meet production targets, inspection equipment that breaks down easily should be checked on a ——– basis.',
    'choices': ['regular', 'regularly', 'regularize', 'regularity'],
    'answer': 'regularly'
  },
  {
    'question': '——– you experience any problems with the building’s equipment, you should contact the property manager.',
    'choices': ['Becauseof', 'Incaseof', 'Immediately', 'If'],
    'answer': 'If'
  },
  {
    'question': 'Employees must park in the ——- parking lot until the renovation of the company building is completed.',
    'choices': ['frequent', 'generous', 'complicated', 'temporary'],
    'answer': 'temporary'
  },
  {
    'question': 'Although the assembly machine has been used for more than 20 years, it is still ——–.',
    'choices': ['annual', 'experienced', 'operational', 'previous'],
    'answer': 'operational'
  },
  {
    'question': 'Medical ——– are supposed to investigate the characteristics and effects of newly developed materials.',
    'choices': ['expert', 'expertise', 'experts', 'expertly'],
    'answer': 'experts'
  },
  {
    'question': 'Darwin Construction, renowned for its ——– designs, has won the bid for the Dalton City Library.',
    'choices': ['innovation', 'innovate', 'innovatively', 'innovative'],
    'answer': 'innovative'
  },
  {
    'question': 'Please make sure that ——– address is correct in the designated section of the order form.',
    'choices': ['yours', 'yourself', 'your', 'you'],
    'answer': 'your'
  },
  {
    'question': 'Mr. Thompson was ——– as the marketing director after a successful market development in Asia.',
    'choices': ['appointing', 'appointed', 'appoint', 'appoints'],
    'answer': 'appointed'
  },
  {
    'question': 'The Irwin City Tourist Information Office is ——– located in the center of the city.',
    'choices': ['particularly', 'unfortunately', 'absolutely', 'conveniently'],
    'answer': 'conveniently'
  },

];

export default data;

addAnswers.jsx
'use cliant'
export const INITIAL = {
    addAnswers: [],
};

export const reducer = (state, action) => {
    switch (action.type) {
        case 'ADD_ANSWER':
            return {
                ...state,
                addAnswers: [...state.addAnswers, action.payload],
            };
        case 'RESULT':
            return {
                ...state,
            };

        default:
            return state;
    }
}
question/page.jsx
'use client'
import Link from "next/link";
import data from "../datas/datas";
import { useState, useReducer } from "react";
import { useSearchParams, } from "next/navigation";
import { INITIAL, reducer} from '../reducer';

const Question = () => {
  const [currentNum, setCurrentNum] = useState(0);
  const [selectedValue, setSelectedValue] = useState(null);
  const [displayExplanation, setDisplayExplanation] = useState(false);
  const [judgment, setJudgment] = useState(false);

  // useReducer
  const [state, dispatch] = useReducer(reducer, INITIAL);
  const seeAnswer = useSearchParams().get('seeAnswer');

  // process of handle selected
  const handleSelect = (e) => {
    setSelectedValue(e.target.value);
    setJudgment(true);
    dispatch({ type: 'ADD_ANSWER', payload: e.target.value });
  }

  const handleDisplayExplanation = () => {
    setDisplayExplanation(true);
  }

  // process of next clicked
  const handleNextButton = () => {
    if (selectedValue !== null) {
      data[currentNum].initialValue = selectedValue;
    }
    setCurrentNum(currentNum + 1);
    setSelectedValue(null);
    const radioButtons = document.querySelectorAll('input[name="drone"]');
    radioButtons.forEach(radioButton => radioButton.checked = false);
    console.log(state.addAnswers);
  }

   // process of prev clicked
  const handlePrevButton = () => {
    setCurrentNum(currentNum - 1);
    const radioButtons = document.querySelectorAll('input[name="drone"]');
    radioButtons.forEach(radioButton => radioButton.checked = false);
  }

  return (
 <div>
  <p>Q{currentNum + 1}. {data[currentNum].question}</p>
  <fieldset>
    <div>
      <input type="radio" id={data[currentNum].choices[0]} name="drone" value={data[currentNum].choices[0]} onChange={handleSelect} />
      <label for={data[currentNum].choices[0]}>A. {data[currentNum].choices[0]}</label>
    </div>
    <div>
      <input type="radio" id={data[currentNum].choices[1]} name="drone" value={data[currentNum].choices[1]} onChange={handleSelect} />
      <label for={data[currentNum].choices[1]}>B. {data[currentNum].choices[1]}</label>
    </div>
    <div>
      <input type="radio" id={data[currentNum].choices[2]} name="drone" value={data[currentNum].choices[2]} onChange={handleSelect} />
      <label for={data[currentNum].choices[2]}>C. {data[currentNum].choices[2]}</label>
    </div>
    <div>
      <input type="radio" id={data[currentNum].choices[3]} name="drone" value={data[currentNum].choices[3]} onChange={handleSelect} />
      <label for={data[currentNum].choices[3]}>D. {data[currentNum].choices[3]}</label>
    </div>
  </fieldset>
  
  {/* if set each, show result every question */}
  {seeAnswer === 'each' && judgment && (
    <button type="button" onClick={handleDisplayExplanation}>True/false judgment</button>
  )}
  {displayExplanation && (
    selectedValue === data[currentNum].answer ? (
      <div></div>
    ) : (
      <div></div>
    )
  )}

  <div>
    <button type="button" onClick={handlePrevButton}>PREV</button>
    {data.length - 1 === currentNum ? (
      <Link href="../result">show result</Link>
    ) : (
      <button type="button" onClick={handleNextButton}>NEXT</button>
    )}
  </div>
</div>

export default Question;
result/page.jsx
'use client'
import {useReducer} from 'react'
import data from "../datas/datas";


const Result = () => {

  return (
    <div>
      <h1>Result</h1>
      <p>Your Answers: {state.addAnswers.length > 0 ? state.addAnswers.join(', ') : 'No answers submitted'}</p>

    </div>
  )
}

export default Result
0

1Answer

useReducer は、あくまでステート機能を使うためのフックです。

機能は useState とほぼ同等で、reducer を使うことでその更新ロジックに制限を掛けて扱いやすくする機能に過ぎません。複数のコンポーネントの間で状態を共有する機能はありません。


本題に移ります。

今回あなたが実現するべきことは、「question ページから result ページに遷移するときに、データを渡す」ことです。そのための方法としては、クエリパラメータを追加するのが手っ取り早いと思います。

クエリパラメータを送る側の書き方としては、

があります。 提示されたコードに追加するなら、useRouter を使う例が書きやすそうなので、概要の例を示しておきます。

const gotoResultPage = () => {
  const params = new URLSearchParams();
  params.append("key", "value");
  // ... 省略
  router.push(`/result?${params}`);
}
// ... 省略
<button onClick={gotoResultPage}>結果を見る</button>

クエリパラメータを受け取る側の書き方としては、

Next.js の機能を使って読み取ることになります。以下の2つの方法のどちらでも可能です。

2Like

Comments

  1. @yamashee

    Questioner

    @honey32

    ご回答ありがとうございます!

    useReducerは複数のコンポーネントの間で状態を共有する機能はないとは知りませんでした😲📝
    ご提案いただいた通り、useRouterとuseSearchParamasを使用してクエリパラメータで渡したら、うまくいきました!

  2. お助けになれて何よりです!

    一応補足します

    • 同一ページ内で、コンポーネントの間のデータ受け渡しは、context または props を使います
    • 今回は、遷移前のページ→遷移後のページなので、それらではなく、クエリパラメータを使います

Your answer might help someone💌