【问题标题】:How to wait for React context to load before loading page in Next.js如何在 Next.js 中加载页面之前等待 React 上下文加载
【发布时间】:2021-10-12 10:20:29
【问题描述】:

我是 React 新手,这是我第一次使用 Context API。

我正在使用 Next.js 创建一个网站,该网站将列出用户添加的项目,类似于电子商务网站。

我目前正在使用 useState 和 Context API 来模拟默认的项目列表,否则这些项目会从数据库中提取出来。

import { createContext, useContext, useState } from 'react';

const ItemContext = createContext();

export function useItems() {
    return useContext(ItemContext);
}

export function ItemContextProvider ({ children }) {
    const itemContext = useItemContext();

    return (
        <ItemContext.Provider value={itemContext}>
            {children}
        </ItemContext.Provider>
    )
}

function useItemContext () {
    const [items, setItems] = useState([
        {id: 1, name: 'Apple Earphones', img:'CLOUDINARY_IMAGE_LINK', location: 'location',
        type: 'Electronics', contact: { Phone: '1234567890'},
        used: 'Used More Than One Year', broken: 'Unbroken', description: "These are apple earphones with a lightning connector. Well used. Don't need them as I don't use an iPhone anymore."}, 
        {id: 2, name: 'Xbox Controller', img:'CLOUDINARY_IMAGE_LINK', location: 'location',
        type: 'Electronics', contact: { Phone: '1234567890' },
        used: 'Used More Than One Year', broken: 'Unbroken', description: "These are apple earphones with a lightning connector. Well used. Don't need them as I don't use an iPhone anymore."},
        {id: 3, name: 'Standing Fan', img:'CLOUDINARY_IMAGE_LINK', location: 'location',
        type: 'Electronics', contact: { Phone: '1234567890' },
        used: 'Used More Than One Year', broken: 'Unbroken', description: "These are apple earphones with a lightning connector. Well used. Don't need them as I don't use an iPhone anymore."},
        {id: 4, name: 'Apple Earphones', img:'CLOUDINARY_IMAGE_LINK', location: 'location',
        type: 'Electronics', contact: { Phone: '1234567890' },
        used: 'Used More Than One Year', broken: 'Unbroken', description: "These are apple earphones with a lightning connector. Well used. Don't need them as I don't use an iPhone anymore."}, 
        {id: 5, name: 'Xbox Controller', img:'CLOUDINARY_IMAGE_LINK', location: 'location',
        type: 'Electronics', contact: { Phone: '1234567890' },
        used: 'Used More Than One Year', broken: 'Unbroken', description: "These are apple earphones with a lightning connector. Well used. Don't need them as I don't use an iPhone anymore."}, 
    ])

    function getSingleItem (id) {
        return items.find(obj => obj.id == id)
    }

    function addItem (itemObject) {
        setItems([...items, itemObject]);
    }

    return {
        items,
        getSingleItem,
        addItem
    }
}

上下文在 _app.js 中全局提供

import Layout from '../components/Layout'
import { ItemContextProvider } from '../components/ItemContext'
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  const getLayout = Component.getLayout || ((page) => page)
  return (
    <ItemContextProvider>
          {getLayout(<Component {...pageProps} />)}
    </ItemContextProvider>
    )
}

export default MyApp

然后我在嵌套布局中使用此上下文,以便任何使用 /browse 的页面都可以访问项目列表、搜索它们并过滤它们,以便可以在子组件中呈现过滤后的项目列表。

import { createContext, useState, useEffect, useReducer } from 'react'
import { useItems } from './ItemContext'
import Navbar from './Navbar'
import Sidebar from './Sidebar'
import browseStyle from '../styles/Browse.module.css'

export const FilterContext = createContext();

const NestedLayout = ({ children }) => {
    const { items } = useItems();

    const [currentItems, setCurrentItems] = useState(items)
    const [searchTerm, setSearchTerm] = useState('')

    const [filters, setFilters] = useState(
        {used: { Unused: true, UsedLessThanOneMonth: true, UsedFewMonths: true, UsedMoreThanOneYear: true },
        broken: { Unbroken: true, PartiallyBroken: true, CompletelyBroken: true },
        type: { Electronics: true, Books: true, CDs: true, Household: true, Furniture: true, Other: true },
    });
     
    const [filteredItems, setFilteredItems] = useState(currentItems);

    function reducer (searchFilter, action) {
        switch (action.type) {
            case 'search':
                if (action.payload.searchTerm === ('')) {
                    // setSearchTerm('')
                    searchFilter = currentItems;
                } else {
                    // setSearchTerm(action.payload.searchTerm.toLowerCase());
                    searchFilter = currentItems.filter(item => item.name.toLowerCase().includes(action.payload.searchTerm.toLowerCase()))
                }
                return searchFilter;
            default:
                searchFilter = currentItems;
                return searchFilter    
        }
    }
  
    const [searchFilter, dispatch] = useReducer(reducer, currentItems);
    
    function filterReducer (filterFilter, action) {
        switch (action.type) {
            case 'filter':
                filterFilter = currentItems.filter(item => getFilteredItems(item))
                return filterFilter;
            default: 
                filterFilter = currentItems;
                return filterFilter;
        }
    }

    const [filterFilter, dispatchFilter] = useReducer(filterReducer, currentItems);

    useEffect (() => {
        dispatchFilter({type: 'filter', payload: {filters: filters}})
    }, [filters])

    useEffect (() => {
        setFilteredItems( searchFilter.filter(element => filterFilter.includes(element)))
    },[searchFilter, filterFilter])

    useEffect (()=> {
        setCurrentItems(items)
        dispatchFilter({type: 'filter', payload: {filters: filters}})
        dispatch({ type: 'search', payload: { searchTerm: ''} })
    }, [items])

    function getFilteredItems(item) {
        const noWhiteSpaces = item.used.replace(/\s+/g, '');
        const itemProperties = [item.type, noWhiteSpaces, item.broken]
      
        const allKeys = Object.entries(filters).map(category => Object.keys(category[1]))
        const allKeysFlat = allKeys.flat();
        const allEntries = Object.entries(filters).map(category => Object.entries(category[1]))
        const allEntriesFlat = allEntries.flat()
        const finalObject = Object.fromEntries(allEntriesFlat)
        const filtered = allKeysFlat.filter(key => finalObject[key]);
      
        const found = itemProperties.every(r=> filtered.includes(r))

        return found;
    }


    return (
        <FilterContext.Provider value={filteredItems}>
            <main className={browseStyle.main}>
                <Navbar dispatch={dispatch}/>
                <div className={browseStyle.browse}>
                    <Sidebar filters={filters} setFilters={setFilters}/>
                        {children}
                </div> 
            </main>
        </FilterContext.Provider>
    )
}

export default NestedLayout

目前,当从索引页面开始并进入浏览页面时,该站点按预期工作。项目列表被填充并且项目可以成功添加。但是,如果我尝试通过任何页面主动使用上下文(例如:http://localhost:3000/browse)进入该站点,我会遇到此错误:

TypeError:无法解构 '(0 , ItemContext__WEBPACK_IMPORTED_MODULE_1_.useItems)(...)' 的属性 'items',因为它是未定义的。

我假设这是因为页面在上下文之前加载,而从索引开始时,上下文在被访问之前有时间加载。我将如何解决这个错误?我对 Context API 的使用是否存在根本缺陷?

【问题讨论】:

  • 检查这部分const { items } = useItems();
  • 在初始化提供程序时尝试解构itemContext 值:&lt;ItemContext.Provider value={{...itemContext}}&gt;
  • @LucaPizzini 不幸的是我仍然遇到同样的错误
  • @ABDULLOKHMUKHAMMADJONOV 我知道那条线是造成错误的原因,但我不知道如何解决它。任何指针将不胜感激!
  • 不是命名导出,所以const items = useItems();

标签: javascript reactjs next.js frontend react-context


【解决方案1】:

我想您忘了提及您为每个页面使用的布局。你提到了“/browse”,如果你有浏览页面,在组件下

Browse.Layout=NestedLayout // whatever layout you are using

【讨论】:

  • 我认为这与 OP 看到的错误无关。问题很可能与 OP 如何初始化上下文值有关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-07
  • 1970-01-01
相关资源
最近更新 更多