Search code examples
react-reduxreact-routerreddit

Refreshing page gives 'Page not found'. (React BrowserRouter)


I built a simple reddit app with React-Redux and when I go to a post page like /posts/mlxft5 and refresh the page it says that the post is not found. I can't figure out how to fix it. Here's my code on codesandbox. here's my app.js code

function App() {
  return (
    <Router > 
      <div className="App">
      <div className="App-navbar">
        <SubredditsList />
      </div>
        <Switch>
          <Route  exact path="/" component={StartMessage}/>
          <Route  exact path="/search" component={SearchPostsList}/>
          <Route exact path="/posts" component={PostsList} />
          <Route  path="/posts/:postId" component={PostDetailRender}/>
          <Route  path="/search/:postId" component={SingleSearchPostRender}/>
          <Redirect to="/" />
        </Switch>
      </div>
    </Router>
  )
}

export default App`

Solution

  • Right now you are only requesting posts from the API when we are on the /posts page for a subreddit. There are no requests initiated by /posts/mlxft. We need to add an additional thunk action that can fetch and store a single post from the id.

    We don't want to fetch posts that are already in Redux when we click on a single post from the list on the /posts page. We we will use the condition setting of the createAsyncThunk function to conditionally cancel the fetch if the data already exists.

    export const fetchSinglePost = createAsyncThunk(
      "posts/fetchSingle",
      async (postId) => {
        const response = await fetch(
          `https://api.reddit.com/api/info/?id=t3_${postId}`
        );
        const json = await response.json();
        return json.data.children[0].data;
      },
      {
        condition: (postId, { getState }) => {
          const { posts } = getState();
          if (posts.entities[postId]) {
            return false;
          }
        }
      }
    );
    

    You need to add additional cases in your reducer to handle this thunk. Note: if you use builder callback notation instead of reducer map object notation then you could combine your two "rejected" cases.

    [fetchSinglePost.pending]: (state, action) => {
      state.status = "loading";
    },
    [fetchSinglePost.fulfilled]: (state, action) => {
      state.status = "succeeded";
      postsAdapter.upsertOne(state, action.payload);
    },
    [fetchSinglePost.rejected]: (state, action) => {
      state.status = "failed";
      state.error = action.error.message;
    }
    

    Inside of your PostDetailRender component you need to dispatch the fetchSinglePost action. It's ok to dispatch it all cases because the thunk itself will cancel the fetching.

    useEffect(() => {
      dispatch(fetchSinglePost(postId));
    }, [dispatch, postId]);
    

    You could potentially have a status for each post rather than one for the whole slice. I explain how to do that in this answer.

    Updated CodeSanbox

    I also made some changes so that you don't fetch the same subreddit's posts more than once.