在函数组件主体之外访问 React Context 值

Posted

技术标签:

【中文标题】在函数组件主体之外访问 React Context 值【英文标题】:Access React Context value outside of the body of a function component 【发布时间】:2021-08-22 03:10:35 【问题描述】:

案例

我想让 isLoading(使用 React Context 的全局状态)值和 changeIsLoading 函数(其从 IsLoadingContext.js 文件中更改的函数)可供所有文件(函数组件和简单的 javascript 函数)访问。

我知道 React Hooks 只能在函数组件的主体内部调用。

问题:在我的例子中,我如何在 util 文件(非函数组件或只是一个简单的 javascript 函数)中调用 isLoadingchangeIsLoading

我应该从代码中更改什么?

代码流程

    (位置:SummariesPage.js)点击button里面的SummariesPage组件 (位置:SummariesPage.js)在SummariesPage组件中调用onApplyButtonIsClicked函数 (位置:SummariesPage.js)将isLoading全局状态更改为true,然后调用fetchAPISummaries函数 (位置:fetchAPISummaries.js)调用fetchAPICycles函数 (位置:fetchAPICycles.js)调用exportJSONToExcel函数 (位置:exportJSONToExcel.js)将 JSON 导出到 Excel 文件中,然后将 isLoading 全局状态更改为 false IsLoadingContextProvider 组件将被重新渲染,SummariesPage 中的 isLoading 值将是 true

错误日志

Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

代码

IsLoadingContext.js:

import React,  useState  from 'react'

const IsLoadingContext = React.createContext()

const IsLoadingContextProvider = (props) => 
    const [isLoading, setIsLoading] = useState(false)

    const changeIsLoading = (inputState) => 
        setIsLoading(inputState)
    

    return(
        <IsLoadingContext.Provider
            value=
                isLoading,
                changeIsLoading
            
        >
            props.children
        </IsLoadingContext.Provider>
    )


export  IsLoadingContextProvider, IsLoadingContext 

SummariesPage.js:

import React,  useContext  from 'react'

// CONTEXTS
import  IsLoadingContext  from '../../contexts/IsLoadingContext'

// COMPONENTS
import Button from '@material-ui/core/Button';

// UTILS
import fetchAPISummaries from '../../utils/export/fetchAPISummaries'

const SummariesPage = () => 
    const  isLoading, changeIsLoading  = useContext(IsLoadingContext)

    const onApplyButtonIsClicked = () => 
        changeIsLoading(true)
        fetchAPISummaries(BEGINTIME, ENDTIME)
    

    console.log('isLoading', isLoading)

    return(
        <Button
            onClick=onApplyButtonIsClicked
        >
            Apply
        </Button>
    )


export default SummariesPage

fetchAPISummaries.js:

// UTILS
import fetchAPICycles from './fetchAPICycles'

const fetchAPISummaries = (inputBeginTime, inputEndTime) => 
    const COMPLETESUMMARIESURL = .....
    
    fetch(COMPLETESUMMARIESURL, 
        method: "GET"
    )
    .then(response => 
        return response.json()
    )
    .then(responseJson => 
        fetchAPICycles(inputBeginTime, inputEndTime, formatResponseJSON(responseJson))
    )


const formatResponseJSON = (inputResponseJSON) => 
    const output = inputResponseJSON.map(item => 
        .....
        return ...item
    )
    return output


export default fetchAPISummaries

fetchAPICycles.js

// UTILS
import exportJSONToExcel from './exportJSONToExcel'

const fetchAPICycles = (inputBeginTime, inputEndTime, inputSummariesData) => 
    const COMPLETDEVICETRIPSURL = .....

    fetch(COMPLETDEVICETRIPSURL, 
        method: "GET"
    )
    .then(response => 
        return response.json()
    )
    .then(responseJson => 
        exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson))
    )


const formatResponseJSON = (inputResponseJSON) => 
    const output = inputResponseJSON.map(item => 
        .....
        return ...item
    )
    return output


export default fetchAPICycles

exportJSONToExcel.js

import  useContext  from 'react'

import XLSX from 'xlsx'

// CONTEXTS
import  IsLoadingContext  from '../../contexts/IsLoadingContext'

const ExportJSONToExcel = (inputSummariesData, inputCyclesData) => 
    const  changeIsLoading  = useContext(IsLoadingContext)

    const sheetSummariesData = inputSummariesData.map((item, index) => 
        let newItem = 
        .....
        return ...newItem
    )

    const sheetSummaries = XLSX.utils.json_to_sheet(sheetSummariesData)
    
    const workBook = XLSX.utils.book_new()

    XLSX.utils.book_append_sheet(workBook, sheetSummaries, 'Summaries')
    
    inputCyclesData.forEach(item => 
        const formattedCycles = item['cycles'].map((cycleItem, index) => 
            .....
            return ...newItem
        )

        const sheetCycles = XLSX.utils.json_to_sheet(formattedCycles)
        XLSX.utils.book_append_sheet(workBook, sheetCycles, item['deviceName'])
    )

    XLSX.writeFile(workBook, `......xlsx`)

    changeIsLoading(false)


export default ExportJSONToExcel

【问题讨论】:

【参考方案1】:

我相信您面临的真正问题是管理异步调用。如果您使用 async/await 关键字,它的可读性会更高。

    const onApplyButtonIsClicked = async () => 
        changeIsLoading(true)
        await fetchAPISummaries(BEGINTIME, ENDTIME)
        changeIsLoading(false)
    

您需要重写 fetchAPICycles 以使用 async/await 关键字而不是 Promise。

const fetchAPICycles = async (
  inputBeginTime,
  inputEndTime,
  inputSummariesData
) => 
  const COMPLETDEVICETRIPSURL = ...;

  const response = await fetch(COMPLETDEVICETRIPSURL, 
    method: "GET",
  );
  const responseJson = await response.json();
  exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson));
;

【讨论】:

以上是关于在函数组件主体之外访问 React Context 值的主要内容,如果未能解决你的问题,请参考以下文章

React.withContext和getChildContext

react-data-components 渲染组件(按钮),但无法访问在渲染方法之外定义的状态或函数

无效的挂钩调用。是不是可以在函数组件的主体之外调用钩子或异步函数?

如何使用 React js 中的上下文将函数从 FUNCTIONAL 传递给 CLASS 组件并在渲染之外(没有 prop)访问它?

React 组件模块(从本地 repo 导入)导致错误“只能在函数组件的主体内部调用 Hooks。”

React - 错误:无效的钩子调用。 Hooks 只能在函数组件的主体内部调用