React + Reac-router-dom + ts / uselocationの記述で特定条件でエラー + リダイレクトさせるとcomponentの中身が表示されない

前提

reactアプリを構築しています。
ルーティングはreact-routerで実装しており、ログインの処理をログインページで実装、usecontextでログイン有無情報を保持しています。

実現したいこと

[1]・・・ログインが必要な特定のページ(/about)にアクセスすると、ログインが必要なのでログインページ(/login)にリダイレクトさせる。
[2]・・・ログインページでログイン情報を入れ投稿することでapiサーバー(laravel側で実装)と通信し、ログインできた際には前いたaboutページにリダイレクトする、という処理を作ろうとしています。

発生している問題・エラーメッセージ

以下の2点が上手く行きません。

[1] 直接loginページのurlにアクセスすると下記のエラー1。

Uncaught TypeError: location.state is null

後述する「試したこと」にあるif記述をしても、別のエラーとなります。
locationの設定方法、箇所などが誤っているのでしょうか・・・?

[2] 直接loginページではなく、aboutにアクセスすると→ログインページにリダイレクトし、ログインをするとaboutへのリダイレクトまでは行える(やりたい遷移ができている)が、outletで指定したコンポーネントが表示されない。

この時には下記の状態となります。(consoleにもエラーは特になし。[outletです。の文字列は表示されている])

イメージ説明

なお、privateルートで囲まず普通にaboutをルーティングに設定した場合は、下記のように正常にaboutコンポーネントの中身が表示されるためプライベートルートで囲み、リダイレクトの際にどこかで上手く行っていないのだとは思います。。。
イメージ説明

該当のソースコード(import系は省略します)

**LoginProvider.tsx **
ここで囲む大枠を作成

typescript

type LoggedInContextType ={ setUserAuth:(value:boolean) => void; userAuth:boolean; setUserName: (value: string) => void; username: string; setUseremail: (value: string) => void; useremail: string; } //ログイン関連のcontextを作成export const LoggedInContext = createContext<LoggedInContextType>({} as LoggedInContextType); //認証情報とセットするコンテキストexport const LoggedInProvider = (props) => { const { children } = props; // 全体のステートオブジェクト作成 const [userAuth, setUserAuth] = useState<boolean>(false);//ログイン有無 const [username, setUserName] = useState<string>(''); const [useremail, setUseremail] = useState<string>(''); return ( <LoggedInContext.Provider value={{username,setUserName,useremail,setUseremail,userAuth,setUserAuth}}> {children} </LoggedInContext.Provider> )}

App.tsx
ルーティングを記載。
/aboutはプライベートルートで囲み、ログインしていないとloginページにリダイレクトするようにしています。

typescript

function App() { return ( <> <LoggedInProvider> <main> <Routes> <Route index element={<Home />} /> <Route path="/about" element={<PrivateRoute />}> <Route element={<About/>}/> </Route> <Route path="/login/" element={<Login/>} /> </Routes> </main> </LoggedInProvider> </> );} export default App;

PrivateRoute.tsx
usecontextで取得したuserAuth情報がtrueなら子コンポーネント、そうでなければログインページにリダイレクトしている。
ここのoutletに当たるところが表示されない、ということ上手くいかないことの一つ目ですが、
エラーの箇所にも書いた通り、「outletです」の文字列は表示されているのでifの分岐とreturnはできていると考えられます。。

typescript

export const PrivateRoute = () => { const { userAuth } = useContext(LoggedInContext); const location = useLocation(); if(userAuth){ return( <> outletです <Outlet/> </> ) }else{ return <Navigate to="/login/" state={{url: location.pathname }} replace/> } };

About.tsx
シンプルに文字列を返すのみ

typescript

export const About:FC = memo(() => { return <section> <p> アバウトです。testです。 </p> </section>;})

最後にログインページ Login.tsx
このページでログインをクリック時に入力情報でapiと認証を行います。(正常なログイン情報を入力すれば、認証情報は正常に取れている)

typescript

type LoginParams = { email:string; password:string;} type State = { url?:string} export const Login = () => { const location = useLocation(); const { url } = location.state as State;//どのページからきたかをここで取得。 //以下のconsole.logではaboutからリダイレクトされた際には[/about]が出力されます。 //直接/loginを叩いた場合は画面全体がエラーとなります。 //(※エラー1。「試したこと」で記述したようにここをifで囲むとまた別のエラーとなります。) console.log(url) const navigate = useNavigate() //ログイン状態 const IsLogged = useContext(LoggedInContext); const [email,setEmail] = useState('') const [password,setPassword] = useState('') const { username,setUserName } = useContext(LoggedInContext); const { userAuth,setUserAuth } = useContext(LoggedInContext); const { useremail,setUseremail } = useContext(LoggedInContext); const changeEmail = (e:ChangeEvent<HTMLInputElement>) => { setEmail(e.target.value) } const changePassword = (e:ChangeEvent<HTMLInputElement>) => { setPassword(e.target.value) } const handleLoginClick = () => { const loginParams:LoginParams = {email,password} axios//csrf保護の初期化 .get('http://localhost:8888/sanctum/csrf-cookie', { withCredentials: true }) .then((response) => { //ログイン処理 axios .post( 'http://localhost:8888/api/login', loginParams, {withCredentials:true} ) .then((response) => { setUserName(response.data.name); setUseremail(response.data.email); setUserAuth(true); navigate(url) //ここでリダイレクトをさせている認識です。 }) }) } return( <section className="login"> {/* <button onClick={() => setCount(count + 1)}>+</button> */} <h1> ログインページです。 </h1> {username} <div> メールアドレス <input onChange={changeEmail}/> </div> <div> パスワード <input onChange={changePassword}/> </div> <div> <button onClick={handleLoginClick}>ログイン</button> </div> </section> )}

試したこと

エラー1に対して。
location.stateがないというエラーになっているので、
ifでlocationがある時のみlocation情報を取ろうとするが、下記のエラーになります。

TS2552: Cannot find name 'url'. Did you mean 'URL'? 72 | setUseremail(response.data.email); 73 | setUserAuth(true); > 74 | navigate(url)

補足情報(FW/ツールのバージョンなど)

開発環境です。
"react": "^18.1.0",
"react-router-dom": "^6.3.0",
"typescript": "^4.7.2",

ログイン配下ページのルーティングの処理は下記のページを参考に実装しました。
https://sunday-programmers.com/2022/01/22/%E3%80%90react-router-dom-v6%E3%80%91%E8%AA%8D%E8%A8%BC%E7%8A%B6%E6%85%8B%E3%81%A7%E3%83%AA%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88%E3%81%95%E3%81%9B%E3%82%8B/

react,TS共に実務では使わず独学のため、認識が誤っているところなどがあるとは思いますが、
思い当たる点や怪しい点など、ご教授いただけますと幸いです。

よろしくお願いいたします!

コメントを投稿

0 コメント