用于动态路由的 React Router 无法实时运行

问题描述 投票:0回答:1

我有带有虚拟数据的示例 React 博客,但是当我将其托管在 netlify 上时,动态 url 路由不起作用。它的设计有两个js页面,一个列出了所有帖子(索引),另一个是帖子的详细信息页面(单个帖子)。因为我使用无头 CMS,所以单个帖子模板是通用的,它根据所选的 slug 更改数据,根据您选择的 slug “生成”单个帖子页面。

构建成功并且索引有效,您也可以单击帖子,它将生成,但是当您单击“下一篇帖子”(使用与 slug 不同的数据生成相同的模板)时,它无法识别 URL 并且显示错误:

找不到页面 您似乎访问了损坏的链接或输入了此网站上不存在的 URL。

“返回”也不起作用,并且同样的错误仍然存在。您只能通过在浏览器中输入索引 URL 来返回博客。从索引中,您可以在单击所有帖子时访问它们,但每个帖子的动态路由不起作用。

# index.js
import React from 'react';
import { createRoot } from 'react-dom/client'; 
import { BrowserRouter, Route, Routes } from 'react-router-dom'; // dyanmic URL routing

import AllPosts from './pages/AllPosts';
import SinglePost from './pages/SinglePost';
import Footer from './Footer';
import Header from './Header';

// environment variables
require('dotenv').config();

// Use the createRoot API to render your app
const rootElement = document.getElementById('root');
const root = createRoot(rootElement); 

root.render(
  <React.StrictMode>
  <BrowserRouter>
  <Header />
    <Routes> {/*AllPosts is basically index.html */}
      <Route exact path="/" element={<AllPosts />} />
      <Route path='/posts/:slug' element={<SinglePost />} />
    </Routes>
  <Footer />
  </BrowserRouter>
  </React.StrictMode>

);

# SinglePost.js 
import React, { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';

const graphqlToken = process.env.GRAPHQL_TOKEN;
const endpoint = graphqlToken;

const SinglePost = () => {
  const { slug } = useParams(); // obtained from AllPost when clicking, slug is variable for dynamic rendering
  const [post, setPost] = useState(null); // fetch post data
  const [pcedges, setPcedges] = useState([]); // Create a state for PostConnect edges (next and prev posts)
  const [currentPostIndex, setCurrentPostIndex] = useState(0); // Initialize currentPostIndex to 0

  // Your GraphQL query, slug is variable when fetching
  // Query also PostConnection for suggesting other posts.
  const query = `query MyQuery($slug: String!) {
    posts(where: {slug: $slug}) {
      title
      excerpt
      id
      date
      slug
      content {
        markdown
      }
      coverImage {
        url
      }
      author {
        name
        title
        picture {
          url
        }
      }
    }
    postsConnection (orderBy: date_DESC){
      edges {
        node {
          title
          slug
          date
        }
      }
    }
  }
  `;
    const variables = { //this const 'variables' is not used because dynamic variable { slug } defined below is for dynamic content
      "slug": slug,
    };

    const formatDate = (dateString) => { //Date formatting
      const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
      const formattedDate = new Date(dateString).toLocaleDateString(undefined, options);
      return formattedDate;
    };
  
  // fetchData defined outside useEffect hook, for dynamic calling for every different slug
  const fetchData = async (slug) => {
    try {
      const response = await fetch(endpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
        body: JSON.stringify({
          query,
          variables: { slug }, // Pass the slug as the variable
        }),
      });
  
      const responseData = await response.json();
      const edges = responseData.data.posts; // define post data
      const pcEdges = responseData.data.postsConnection.edges; // define next and prev posts from PostConnect (in query)
  
      setPcedges(pcEdges);
      // next and prev posts routing by setting the post slug
      setCurrentPostIndex(pcEdges.findIndex((pcedge) => pcedge.node.slug === slug));
  
      if (edges && edges.length > 0) {
        setPost(edges[0]); //first value([0]) of query always is post data
      } else {
        setPost(null);
      }
    } catch (error) {
      console.error('Error fetching data:', error);
      setPost(null);
    }
  };
  
  // React hook
  useEffect(() => {
    fetchData(slug); //call fetchData with post content depending on slug
    // second arg for useEffect is slug to check for changes.
  }, [slug]);

  if (!post) {
    return <div>Loading...</div>;
  }

  // set next and prev post; (orderBy: date_DESC) in graphQL query is necessary for this order to work, otherwise links are random
  const nextPost = pcedges[currentPostIndex + 1]?.node;
  const previousPost = pcedges[currentPostIndex -1]?.node;

  //set slug in order for post content to fetch
  const handleNextPostClick = () => {
    if (currentPostIndex + 1 < pcedges.length) {
      const nextPostSlug = pcedges[currentPostIndex + 1]?.node.slug;
      if (nextPostSlug) {
        fetchData(nextPostSlug); 
      }
    }
  };
  
  const handlePreviousPostClick = () => {
    if (currentPostIndex - 1 >= 0) {
      const previousPostSlug = pcedges[currentPostIndex - 1]?.node.slug;
      if (previousPostSlug) {
        fetchData(previousPostSlug);
      }
    }
  };
  

  return (
    <main class='post'>

        <section class='post__header'>
          <div class="formatedDate">
            <dl>{formatDate(post.date)}</dl>
          </div>

        <h2>{post.title}</h2>
        </section>

        <section class='post__content'>

          <div class='post__content__column'>

            <div class="description">


              <div class="detail">

                <div class="img-container">
                  {/* if no picture from query use hardcoded placeholder */}
                  {post.author.picture && post.author.picture.url ? ( <img src={post.author.picture.url}/> ) : ( <img src='https://26159260.fs1.hubspotusercontent-eu1.net/hubfs/26159260/personalBlog/cv-big-photo2.png'/> )}
                </div>

                <div>
                  <p class='author'>{post.author.name}</p>
                  <p class='title'>{post.author.title}</p>
                </div>

              </div>

            </div>
            <hr />
            
            <div class="footer">

              <div class='footer__other-posts'>
                {/* if there is no nextPost or prevPost, dont show link option */}
                {nextPost && (
                  <div>
                    <p>NEXT POST</p>
                    <a href={`/posts/${nextPost.slug}`} onClick={handleNextPostClick}>{nextPost.title}</a>
                  </div>
                )}

                {previousPost && (
                  <div>
                    <p>PREVIOUS POST</p>
                    <a href={`/posts/${previousPost.slug}`} onClick={handlePreviousPostClick}>{previousPost.title}</a>
                  </div>
                )}

              </div>
              <hr />

              <div class='footer__backlink'>
                <a href='/'>← Back to the blog</a>
              </div>

            </div>

          </div>

          <div className="post__content__post-content">
            <div class='post__content__post-content__img-container'>
              {/* if no picture from query use hardcoded placeholder */}
              {post.coverImage && post.coverImage.url ? (<img id='post-img' src={post.coverImage.url} /> ) : ( <img id='post-img' src='https://26159260.fs1.hubspotusercontent-eu1.net/hubfs/26159260/personalBlog/cover-img2.jpg' />)}
            </div>
            <hr />
            <ReactMarkdown>{post.content.markdown}</ReactMarkdown> {/* render MarkDown content (post body) */}
          </div>

        </section>
    </main>
  );
};

export default SinglePost;


// AllPosts.js
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Link } from 'react-router-dom';

// Access the GraphQL token using the environment variable
const graphqlToken = process.env.GRAPHQL_TOKEN;

// GraphQL endpoint URL
const endpoint = graphqlToken;

const AllPosts = () => {
  // get title and ID when clicking on a post (see below)
  const handleClick = (id, title) => {
      console.log('Title:', title);
      console.log('ID:', id);
  };
  
  const [data, setData] = useState([]);

  // Your GraphQL query and variables
  const query = `query MyQuery {
      posts (orderBy: date_DESC){
        title
        excerpt
        id
        date
        slug
      }
    }`;
  const variables = {
  // "first": 25,
  };

  // format date from 15.07.2020 to weekday, Month dayNum, year.
  const formatDate = (dateString) => {
      const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
      const formattedDate = new Date(dateString).toLocaleDateString(undefined, options);
      return formattedDate;
  };
    
  // React hook   
  useEffect(() => {
    // fetchData: asynchronous function to fetch data
    async function fetchData() {
      try {
        // fetch: send POST request to endpoint
        const response = await fetch(endpoint, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
          },
          body: JSON.stringify({
            // include query and variables
            query,
            variables,
          }),
        });
        // Response from server is parsed as JSON
        const responseData = await response.json();
        // assuming response is object with data, page property and edge array
        const edges = responseData.data.posts;
        // console.log(edges)

        // new response state
        setData(edges);

      } catch (error) {
        console.error('Error fetching data:', error);
      }
    }

    fetchData();
    // By passing an empty dependency array (second argument of useEffect), 
    // you tell React not to watch for any changes in specific dependencies 
    // and only run the effect once. 
    // For example see dynamic routing depending on slug (see SinglePost.js )

  }, []); // Run the effect only once on component mount

  return (
    // rendering the fetched data

    <main> {/* top-level container */}

      {/* Display the fetched data here */}
      <section class="title">
        <h1>Latest</h1>
        <p>Our latest blog posts.</p>
      </section>

      <section>

        <ul>

          {data.map((edge, index) => (
          <li key={index} onClick={() => handleClick(edge.id, edge.title)}> {/* handle click for post title and ID*/}

            <div class="article">

              <div class="formatedDate">
                  <dl>{formatDate(edge.date)}</dl> {/* formatted date */}
              </div>

              <div class="article__text">
                <div>
                  <h2><Link to={`/posts/${edge.slug}`}>{edge.title}</Link></h2> {/* use Link for dynamic routing */}
                  <p>{edge.excerpt}</p>
                </div>
                <span><a href={`/posts/${edge.slug}`}>Read more →</a></span> 
              </div>

            </div>
          
          </li>
          ))}

        </ul>

      </section>
    </main>
  );
};

export default AllPosts;

此配置适用于本地主机

我认为问题可能出在 SinglePost.js 中:

 // React hook
  useEffect(() => {
    fetchData(slug); //call fetchData with post content depending on slug
    // second arg for useEffect is slug to check for changes.
  }, [slug]);

或者使用我的查询中的 const 变量:

const variables = { //this const 'variables' is not used because dynamic variable { slug } defined below is for dynamic content
      "slug": slug,
    };
reactjs react-hooks react-router-dom netlify dynamic-routing
1个回答
1
投票

react-router-dom
使用
Link
组件进行 SPA 路由。 SPA 路由意味着你的应用程序只有 1 个 HTML 文件,路由应该通过 JS 处理状态、内存中的值(在 React 中存储在虚拟 dom 上)来完成。在您的代码中,您使用
<a>
(锚标记),这将更改
window.location
,加载整个新页面,并且 React 虚拟 dom 被清除,这意味着所有数据都将丢失。

<a href={`/posts/${nextPost.slug}`} onClick={handleNextPostClick}>{nextPost.title}</a>

您可能想尝试用

a
元素替换
Link
标签。

© www.soinside.com 2019 - 2024. All rights reserved.