在函数组件主体之外访问 React Context 值
Access React Context value outside of the body of a function component
案例
我想使 isLoading
(使用 React Context 的全局状态)值和 changeIsLoading
函数(它从 IsLoadingContext.js 文件更改函数)对所有文件(函数组件和简单的 javascript 函数)。
我知道 React Hooks 只能在函数组件的内部调用。
问题:所以在我这里的例子中,我怎么能在 util 文件(非函数组件或只是一个简单的 javascript函数)?
我应该从代码中更改什么?
代码流
- (位置:SummariesPage.js)点击
SummariesPage
组件里面的button
- (location:SummariesPage.js)调用
SummariesPage
组件中的onApplyButtonIsClicked
函数
- (location: SummariesPage.js) 将
isLoading
全局状态更改为 true
然后调用 fetchAPISummaries
函数
- (位置:fetchAPISummaries.js)调用
fetchAPICycles
函数
- (位置:fetchAPICycles.js)调用
exportJSONToExcel
函数
- (location: 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
我相信您面临的真正问题是管理异步调用。如果您使用 async/await 关键字,它的可读性会更高。
const onApplyButtonIsClicked = async () => {
changeIsLoading(true)
await fetchAPISummaries(BEGINTIME, ENDTIME)
changeIsLoading(false)
}
您需要重写 fetchAPICycles
以使用 async/await 关键字而不是承诺。
const fetchAPICycles = async (
inputBeginTime,
inputEndTime,
inputSummariesData
) => {
const COMPLETDEVICETRIPSURL = ...;
const response = await fetch(COMPLETDEVICETRIPSURL, {
method: "GET",
});
const responseJson = await response.json();
exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson));
};
案例
我想使 isLoading
(使用 React Context 的全局状态)值和 changeIsLoading
函数(它从 IsLoadingContext.js 文件更改函数)对所有文件(函数组件和简单的 javascript 函数)。
我知道 React Hooks 只能在函数组件的内部调用。
问题:所以在我这里的例子中,我怎么能在 util 文件(非函数组件或只是一个简单的 javascript函数)?
我应该从代码中更改什么?
代码流
- (位置:SummariesPage.js)点击
SummariesPage
组件里面的button
- (location:SummariesPage.js)调用
SummariesPage
组件中的onApplyButtonIsClicked
函数 - (location: SummariesPage.js) 将
isLoading
全局状态更改为true
然后调用fetchAPISummaries
函数 - (位置:fetchAPISummaries.js)调用
fetchAPICycles
函数 - (位置:fetchAPICycles.js)调用
exportJSONToExcel
函数 - (location: 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
我相信您面临的真正问题是管理异步调用。如果您使用 async/await 关键字,它的可读性会更高。
const onApplyButtonIsClicked = async () => {
changeIsLoading(true)
await fetchAPISummaries(BEGINTIME, ENDTIME)
changeIsLoading(false)
}
您需要重写 fetchAPICycles
以使用 async/await 关键字而不是承诺。
const fetchAPICycles = async (
inputBeginTime,
inputEndTime,
inputSummariesData
) => {
const COMPLETDEVICETRIPSURL = ...;
const response = await fetch(COMPLETDEVICETRIPSURL, {
method: "GET",
});
const responseJson = await response.json();
exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson));
};