skip to Main Content

Initially, it works if the manually input link is wrong (Ex: "/characters/test"), but if it’s the correct one, it still redirects to error 404. If the link is clicked from Character component, it works normally. Which means no matter what link I put manually on the browser, it results in error 404.

I’ve been trying to implement an error 404 redirect on a CharacterCard component by comparing the id being passed – ‘name’ (as useParams) and comparing it to an characterId list from another component being passed, if it matches, then it renders normally, if not, it redirects to error 404.

Since I’m using React-Router v6, I can’t pass props normally as far as I know. After a bit of research, I found out I can use Link state to pass data to another component it links to and useLocation to get the passed data. I successfully passed data to the component, but then it results to the problem I stated above.

App.js (Route Declarations)

import { Route, Routes } from 'react-router-dom';
import './App.css';
import { Nations } from './components/Nations';
import { Navbar } from './components/Navbar';
import { Characters } from './components/Characters';
import { Home } from './components/Home';
import { Artifacts } from './components/Artifacts';
import { Weapons } from './components/Weapons';
import { CharacterCard } from './components/CharacterCard';
import { NotFound } from './components/NotFound';


function App() {

  return (
    <div className='bg-[#1e1f21]'>
      <Navbar />
      <Routes>
        <Route path='/' element={<Home/>}/>
        <Route path='/nations' element={<Nations/>}/>
        <Route path='/weapons' element={<Weapons/>}/>
        <Route path='/characters' element={<Characters/>}/>
        <Route path='/characters/:name' element={<CharacterCard/>}/>
        <Route path='/artifacts' element={<Artifacts/>}/>
        <Route path="*" element={<NotFound/>}/>
      </Routes>
      
    </div>
  );
}


export default App;

Characters

import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { PuffLoader } from "react-spinners";

const API_URL = 'something.somethin.com';
const CDN_URL = 'something2.somethin2.com';

export const Characters = () => {
  const [characters, setCharacters] = useState([]);
  const [isLoaded, setIsLoaded] = useState(false);

  const validCharacterIds = characters.map((character) => character.id);
  console.log('valid ids:', validCharacterIds);
  const getCharacters = async () => {
    try {
      const response = await fetch({API_URL});
      const charactersData = await response.json();

      const characterDetailsData = await Promise.all(
        charactersData.map(async (character) => {
          const characterDetails = await getCharacterDetails(character);
          return characterDetails;
        })
      );

      setCharacters(characterDetailsData);
      console.log('characters: ', characterDetailsData);
    } catch (error) {
      console.log("Error fetching Characters:", error);
    }
  };

  const getCharacterDetails = async (character) => {
    //code for getCharacterDetails

    setIsLoaded(true);
    return { ...characterDetailsData, id:character, icon: imageData };
  };

  useEffect(() => {
    getCharacters();
    document.title = "Characters | Website Title"
  }, []);

  return (
    <div className='w-full py-[3rem] px-4 bg-white'>
      //Character Card that when clicked opens CharacterCard Component
      <div className='w-[500px] md:w-[1200px] mx-auto grid grid-cols-1 md:grid-cols-5 gap-5'>
        {isLoaded ? (
          (characters.length > 0) ? (
            characters.map((character) => (
              <Link key={character.name} to={`/characters/${character.id}`} state={{charIds: validCharacterIds}} className='w-full shadow-xl flex flex-col my-4 rounded-lg hover:scale-105 duration-300'>
                <div className={
                  character.rarity === 4
                    ? "bg-gradient-to-b from-[#5e5789] to-[#9c75b7]"
                    : character.rarity === 5
                      ? "bg-gradient-to-b from-[#945c2c] to-[#b27330]"
                      : ''
                }>
                  <img className='w-[300px] h-[250px] object-cover' src={character.icon} alt={character.name + ' Icon'} />
                </div>
                <div>
                  <h4 className='text-2xl font-bold text-center py-8'>{character.name}</h4>
                </div>
              </Link>
            ))
          ) : (
            <div className='empty'>
              <h2>No Characters Found!</h2>
            </div>
          )
        ) : (
          <div className="flex justify-center items-center">
            <PuffLoader color="#36d7b7" />
          </div>
        )}
      </div>
    </div>
  );
};

CharacterCard

import React, { useEffect, useState } from 'react';
import { useNavigate, Navigate, useParams, useLocation } from 'react-router-dom';
import { PuffLoader } from 'react-spinners';
import Carousel from './Carousel';

const CDN_URL = 'somethin.somethin.com';
const API_URL = 'somethin2.somethin2.com';

export const CharacterCard = () => {
  const [characterDetails, setCharacterDetails] = useState(null);

  const navigate = useNavigate();
  const { name } = useParams();
  const location = useLocation();
  let validCharIds = location.state;
  console.log(location);

  const getCharacterDetails = async (characterName) => {
    //rest of the api fetch goes here
  };


  useEffect(() => {
    const checkValidCharId = async () => {
      if (!validCharIds || !validCharIds.charIds || !validCharIds.charIds.includes(name)) {
        // Redirect to error 404 if the name is not in the validCharIds array or empty
        navigate('*');
        console.log('error 404');
      } else {
        // Fetch character details only if the name is valid
        await getCharacterDetails(name);
      }
    };

    checkValidCharId();
  }, [name, validCharIds, navigate]);



  if(characterDetails) {
      return (
      //Render component normally

    );
  }

  else {
    return (
      <div className='flex justify-center items-center'>
         <Navigate to='*'/>
      </div>
    )
  }

};

Example Data:
validCharacterIds
validCharacterIds data for mapping

Characters
Characters array after fetch

2

Answers


  1. Route path parameters are always a string type. I suspect the character.id is a non-string value and gets stringified when passed as the name route path parameter.

    The recommendation is to do a type insensitive comparison. String to String is common since it covers non-number strings, e.g. "0123456789abcdef", and number-like strings, e.g. 5 vs "5".

    // Create array of id strings
    const validCharacterIds = characters.map(({ id }) => id).map(String);
    // or const validCharacterIds = characters.map(({ id }) => String(id));
    
    ...
    
    {isLoaded ? (
      characters.length ? (
        characters.map((character) => (
          <Link
            key={character.name}
            to={`/characters/${character.id}`}
            state={{ charIds: validCharacterIds }} // <-- pass array of id strings
            className='....'
          >
            ...
          </Link>
        ))
      ) : (
        <div className='empty'>
          <h2>No Characters Found!</h2>
        </div>
      )
    ) : (
      <div className="flex justify-center items-center">
        <PuffLoader color="#36d7b7" />
      </div>
    )}
    
    export const CharacterCard = () => {
      const [characterDetails, setCharacterDetails] = useState(null);
    
      const navigate = useNavigate();
      const { name } = useParams(); // <-- name is string type
      const location = useLocation();
    
      const { charIds } = location.state || {};
    
      ...
    
      useEffect(() => {
        const checkValidCharId = async () => {
          if (!charIds?.includes(name)) {
            // Redirect to error 404 if the name is not 
            // in the validCharIds array or empty
            navigate('*');
          } else {
            try {
              // Fetch character details only if the name is valid
              setCharacterDetails(await getCharacterDetails(name));
            } catch(error) {
              // handle any errors
            };
          }
        };
    
        checkValidCharId();
      }, [name, charIds, navigate]);
    
      ...
    };
    
    Login or Signup to reply.
  2. The issue you’re facing might be related to the way you’re using the state prop in the Link component. Instead of relying on the state prop of the Link component, you can use a different approach to pass data to the CharacterCard component. You can update the to prop of the Link component to include the character id as a parameter in the route:

    <Link key={character.name} to={`/characters/${character.id}?charIds=${validCharacterIds.join(',')}`} className='w-full shadow-xl flex flex-col my-4 rounded-lg hover:scale-105 duration-300'>
    

    This way, you’re passing the charIds as a query parameter in the URL. Then, in the CharacterCard component, you can retrieve this query parameter using useParams:

    const { name } = useParams();
    const charIds = new 
    URLSearchParams(useLocation().search).get('charIds')?.split(',');
    
    useEffect(() => {
      const checkValidCharId = async () => {
        if (!charIds || !charIds.includes(name)) {
          navigate('*');
      console.log('error 404');
    } else {
      
      await getCharacterDetails(name);
     }
    };
    
    checkValidCharId();
    }, [charIds, name, navigate]);
    

    This way, whether the link is clicked or manually inputted, the necessary data should be available in the CharacterCard component, and the 404 redirection logic should work as expected.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search