【问题标题】:React SSR router - redux state loss when navigating to sub routeReact SSR路由器-导航到子路由时的redux状态丢失
【发布时间】:2021-10-16 10:14:45
【问题描述】:

我在服务器上渲染反应一切正常,除了导航到子路由时,redux 丢失状态和来自后端的数据不出现。数据显示在 redux 开发工具中。但是,状态不会在页面刷新时丢失。

这是一个说明我的意思的例子:

website.com/category/books to website.com/category/toys

App.js:

import React from 'react'
import { Switch, Route } from 'react-router-dom'
import HomeScreen from './Screens/HomeScreen'
import CategoryScreen from './Screens/CategoryScreen'

const App = () => {
  return (
    <Switch>
      <Route path='/' component={HomeScreen} exact />
      <Route path='/category/:name' component={CategoryScreen} />
    </Switch>

  )
}

export default App;

index.js:

import React from 'react'
import { Provider } from 'react-redux'
import { hydrate } from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import store from './store'
import './index.css'
import App from './App'

hydrate(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById('root')
)

store.js:

import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import { productListReducer } from './reducers/productReducers'
import { categoryListReducer } from './reducers/categoryReducers'

const reducer = combineReducers({
    productList: productListReducer,
    categoryList: categoryListReducer,
})

const initialState = {}

const middleware = [thunk]

const loadState = () => {
    try {
        const serializedState = localStorage.getItem('state')
        if (serializedState === null) {
            return undefined
        }
        return JSON.parse(serializedState)
    } catch (e) {
        return undefined
    }
}

const saveState = (state) => {
    try {
        const serializedState = JSON.stringify(state)
        localStorage.setItem('state', serializedState)
    } catch (e) {

    }
}

const persistedState = loadState()

const store = createStore(
    reducer, persistedState, composeWithDevTools(applyMiddleware(...middleware))
)

store.subscribe(() => {
    saveState(store.getState())
})

export default store

用于 SSR 的前端 server.js:

import path from 'path'
import fs from 'fs'
import express from 'express'
import React from 'react'
import { StaticRouter } from 'react-router'
import ReactDOMServer from 'react-dom/server'
import { Provider } from 'react-redux'
import store from '../src/store'
import App from '../src/App'
import { createProxyMiddleware } from 'http-proxy-middleware'

const PORT = 3000
const app = express()

app.use('/api/products', createProxyMiddleware({ target: 'http://98.51.100.255:5000', changeOrigin: true }))
app.use('/api/categories', createProxyMiddleware({ target: 'http://98.51.100.255:5000', changeOrigin: true }))

const router = express.Router()

const serverRenderer = (req, res, next) => {

  app.get('/*', function (req, res) {
    res.sendFile(path.join(__dirname, '../build/index.html'), function (err) {
      if (err) {
        res.status(500).send(err)
      }
    })
  })

  const context = {}

  fs.readFile(path.resolve('./build/index.html'), 'utf8', (err, data) => {
    if (err) {
      console.error(err)
      return res.status(500).send('An error occurred')
    }
    return res.send(
      data.replace(
        '<div id="root"></div>',
        `<div id="root">
        ${ReactDOMServer.renderToString(
          <Provider store={store}>
            <StaticRouter location={req.url} context={context}>
              <App />
            </StaticRouter>
          </Provider>
        )}
        </div>`
      )
    )
  })
}

router.use('^/$', serverRenderer)

router.use(
  express.static(path.resolve(__dirname, '..', 'build'))
)

app.use(router)

app.listen(PORT, () => {
  console.log(`SSR running on port ${PORT}`)
})

categoryscreen.js:

import React, { useEffect, useState, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import CategoryHeader from '../components/CategoryHeader'
import Product from '../components/Product'
import { listProducts } from '../actions/productActions'

function CategoryScreen({ match }) {
    let ProductMatch = match.params.name

    const dispatch = useDispatch()

    const productList = useSelector(state => state.productList)
    const { products } = productList

    useEffect(() => {
        dispatch(listProducts())
    }, [dispatch])

    return (
        <>
            <h3>{ProductMatch}</h3>
            <div>
                {products.filter(product => product.SubCategory == ProductMatch)
                    .map((product) => (
                        <Product product={product} />
                    ))
                }
            </div>

        </>
    )
}

export default CategoryScreen

产品列表操作:

import axios from 'axios'
import {
    PRODUCT_LIST_REQUEST,
    PRODUCT_LIST_SUCCESS,
    PRODUCT_LIST_FAIL,
} from '../constants/productConstants'


export const listProducts = () => async (dispatch) => {
    try {
        dispatch({ type: PRODUCT_LIST_REQUEST })

        const { data } = await axios.get('/api/products')

        dispatch({
            type: PRODUCT_LIST_SUCCESS,
            payload: data
        })
    } catch (error) {
        dispatch({
            type: PRODUCT_LIST_FAIL,
            payload: error.response && error.response.data.message
                ? error.response.data.message : error.message
        })
    }
}

编辑:添加 categoryscreen.js 和 redux 操作

【问题讨论】:

  • 您能否分享CategoryScreen,因为该组件似乎没有处理新的 URL?
  • @DrewReese 添加了分类屏幕
  • 谢谢。假设您正确地看到 name (ProductMatch) 匹配参数随着路线更改从“书籍”更新为“玩具”是否安全?您能否澄清您所指的丢失状态? productList?
  • @DrewReese 是的 productmatch 确实会随着路线的变化而更新。并且正在从 productlist 操作中发送数据。我将 productList 操作添加到帖子中

标签: reactjs redux react-redux react-router server-side-rendering


【解决方案1】:

所以目前还不清楚问题是什么。 URL 发生变化,组件看到新的ProductMatch 值,它被重新渲染,它应该通过ProductMatch 类别过滤productList 状态并重新映射产品。

我看到这里唯一缺少的部分是为映射提供 React 键。在没有键的情况下,React 基本上将使用孩子的索引作为 React 键,并且由于“书”子类别的索引 n 将与“玩具”子类别的索引 n 相同,我怀疑 React 只是放弃重新渲染你的 UI。

根据每个被映射的 product 的唯一属性提供一个 React 键,例如 id。如果产品没有此保证,那么我建议在处理您的 redux 状态时扩充您的产品数据,以便为此添加 GUID。

{products.filter(product => product.SubCategory == ProductMatch)
  .map((product) => (
    <Product
      key={product.id} // <-- add React key
      product={product}
    />
  ))
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-02-11
    • 1970-01-01
    • 1970-01-01
    • 2016-09-22
    • 1970-01-01
    • 2016-04-06
    • 1970-01-01
    相关资源
    最近更新 更多