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

Time:04-20

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 ( <some JSX> )
}

CodePudding user response:

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.

CodePudding user response:

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.

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
};

You code:

const CharacterDetails = () => {
  const { id } = useParams<string>();
  const navigate = useNavigate();
  const [isLoading, setIsLoading] = React.useState<boolean>(true);
  const [state, dispatch] = useReducer(characterReducer, initialState);
  const { data, episodes, loading } = 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) {
        navigate("/");
      } finally {
        dispatch({ type: "LOADING_OVER" });
      }
    };

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

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

  return ( <some JSX> )
}
  • Related