Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Component shows for a brief moment before redirecting in react router v6

I have a small issue I'm not able to fix. In my react app I use react-router v6 and I have to following routes:

    <Route path="/" element={<App />} />
    <Route path=":id" element={<CharacterDetails/>} />
    <Route path="*" element={<Navigate replace to="/" />} />

As you can see, I have a route that needs an id. This works fine as long as I provide an existing Id so that CharacterDetails component fetches some data successfully. However, if I pass some random text in the URL like "localhost:3000/randomText" the CharacterDetails component still shows for a brief second till the useEffect fires to determine that the request is a 404 and only after that it redirects me to the App component.

How can I check if the URL provided should indeed return some data before rendering the component ? and redirect to the App component directly (without the flickering of the CharacterDetails component) when it is not a valid URL

Thanks!

EDIT: I'm not sure if this is a router issue or should I do it at the component level, I'm waiting for suggestions

EDIT2: Component code

const CharacterDetails = () => {
  const { id } = useParams<string>();
  const navigate = useNavigate();
  const [state, dispatch] = useReducer(characterReducer, initialState);
  const { data, episodes, loading } = state;

  useEffect(() => {
    const fetchData = async (id: string) => {
      dispatch({ type: "LOADING_START" })
      try {
        let response = await getSingleCharacterData(id);
        let URLs = response.data.episode;
        let listOfEpisodes = await getEpisodeName(URLs);
        dispatch({
          type: "FETCH_SUCCESS",
          payload: { data: response.data, episodeList: listOfEpisodes },
        });
        dispatch({ type: "LOADING_OVER" })
      } catch (error) {
        dispatch({ type: "LOADING_OVER" })
        navigate("/");
      }
    };

    if (id) fetchData(id);
  }, [id, navigate]);

  return ( 
    <CharacterDetailsContainer>
    {loading ? <Loading /> :
      data && ( 
         <div> </div> 
    )}
    </CharacterDetailsContainer>
}
like image 633
Cosmin Mocanu Avatar asked Sep 20 '25 08:09

Cosmin Mocanu


2 Answers

This isn't an issue with the router/routes, it's something that routed components need to handle.

In the CharacterDetails component use some "loading" state to conditionally render null or some loading indicator while the id path param is validated. Note that "loading" needs to be the initial state so the code isn't leaking any initial non-loading UI, waiting until the useEffect hook runs at the end of the initial render to dispatch({ type: "LOADING_START" }) is too late unless the initial redux state has it set true.

Example:

const CharacterDetails = () => {
  const { id } = useParams();
  const navigate = useNavigate();
  const [isLoading, setIsLoading] = React.useState(true);

  useEffect(() => {
    setIsLoading(true);
    // logic to validate id param
    if (is404) {
      navigate("/404", { replace: true }); // redirect
    } else {
      setIsLoading(false); // clear loading state so page content renders
    }
  }, [id]);

  if (isLoading) {
    return null; // or loading spinner/etc...
  }

  return page content
};

Your code:

const CharacterDetails = () => {
  const { id } = useParams<string>();
  const navigate = useNavigate();

  const [isLoading, setIsLoading] = React.useState<boolean>(true); // <-- initially true

  const [state, dispatch] = useReducer(characterReducer, initialState);
  const { data, episodes } = state;

  useEffect(() => {
    const fetchData = async (id: string) => {
      setIsLoading(true);
      dispatch({ type: "LOADING_START" });
      try {
        let response = await getSingleCharacterData(id);
        let URLs = response.data.episode;
        let listOfEpisodes = await getEpisodeName(URLs);
        dispatch({
          type: "FETCH_SUCCESS",
          payload: { data: response.data, episodeList: listOfEpisodes },
        });
        setIsLoading(false);
      } catch (error) {
        // handle any errors, etc...

        // redirect home
        navigate("/", { replace: true });
      } finally {
        dispatch({ type: "LOADING_OVER" });
      }
    };

    if (id) fetchData(id);
  }, [id, navigate]);

  if (isLoading) {
    return null; // or loading spinner/etc...
  }

  return ( <some JSX> )
}
like image 111
Drew Reese Avatar answered Sep 22 '25 22:09

Drew Reese


You can use the useParams hook in the child.

const acceptableIDs = ["dog", "cat"];
function CharacterDetails() {
  let { id } = useParams();
  return acceptableIDs.includes(id) ? (
    <div>
      <h3>ID: {id}</h3>
    </div>
  ) : null; // render nothing or redirect
}

If it takes too long to check if the ID is valid, you could show a transition.

Note this is business logic and should probably not bleed into the router.

like image 36
Timon van der Berg Avatar answered Sep 22 '25 23:09

Timon van der Berg