简体   繁体   English

在上下文中反应悬念

[英]React Suspense within a Context

Those days I had to solve a problem for my react app, I have all my data to be displayed in JSONs, those are served by an express API using FS of node in order to read those JSONs and return them depending on the path you're passing in.那些日子我不得不为我的反应应用程序解决一个问题,我的所有数据都以 JSON 格式显示,这些数据由使用节点 FS 的快速 API提供服务,以便读取这些 JSON 并根据您的路径返回它们重新传入。

The real problem came when I tried to render my components, the components are useless without that JSON information, so I'll have to always wait till I get those.当我尝试渲染我的组件时,真正的问题出现了,如果没有 JSON 信息,这些组件将毫无用处,所以我必须一直等到我得到这些信息。

My first idea was to call the API synchronously with XMLHttpRequest but I saw in the docs that it is deprecated .我的第一个想法是使用XMLHttpRequest同步调用 API ,但我在文档中看到它已被弃用 So I read about the new features of Suspense and Transitions but I do not understand it as well as placing them into my DataContext.因此,我阅读了Suspense 和 Transitions的新功能,但我不理解它以及将它们放入我的 DataContext 中。 I'll share with you all code I think is relevant in order to let you see where I reached:我将与您分享我认为相关的所有代码,以便让您了解我到达的位置:

// jsonLoader.js
// Here I try to get the data with XMLHttpRequest, I replaced to sync to async

export const loadJSON = async (path) => {
    const json = await readFile(path, 'application/json');
    return json ? JSON.parse(json) : undefined;
};

const readFile = async (path, mimeType) =>
    new Promise((resolve) => {
        const xmlHttp = new XMLHttpRequest();
        xmlHttp.open('GET', path, true);
        if (!!mimeType && xmlHttp.overrideMimeType) xmlHttp.overrideMimeType(mimeType);
        xmlHttp.send();
        if (xmlHttp.status == 200 && xmlHttp.readyState == 4) resolve(xmlHttp.responseText);
        else return resolve(undefined);
    });

Then I use that module in my DataContext:然后我在我的 DataContext 中使用该模块:

// DataContext.js
// I'm trying to serve a "useQuery" called "getData" in order to fetch the API
let data = {};

const index = (obj, path) => path.split('.').reduce((o, i) => o[i], obj);
const setter = (obj, path, value) => {
    if (typeof path == 'string') return setter(obj, path.split('.'), value);
    else if (path.length == 1 && value !== undefined) return (obj[path[0]] = value);
    else if (path.length == 0) return obj;
    else return setter(obj[path[0]], path.slice(1), value);
};

const DataContextInstance = createContext({
    getData: async (...paths) => ({}),
    getTranslations: () => ({}),
});

export const DataContext = ({dataLoaded, children}) => {
    if (dataLoaded) data = dataLoaded;

    const {lang} = useContext(UserContext);
    const [prevLang, setPrevLang] = useState();

    const loadData = (path) => {
        if (Object.keys(data).length > 0) {
            const foundData = index(data, path);
            if (foundData?.then) throw foundData;
            if (foundData) return data;
        }
        const filePath = `/data/${path || `translations/${lang}`}.json`;
        const json = loadJSON(filePath).then((newData) => (data[path] = newData));
        data[path] = json;
    };

    const getData = (...paths) => {
        if (paths.every((p) => index(data, p))) return data;
        paths.forEach((p) => loadData(p));
        return data;
    };

    useEffect(() => {
        if (lang === prevLang && Object.keys(data).length > 0) return;
        if (Object.keys(data).length > 0) return;
        loadData();
        setPrevLang(lang);
    }, [lang, prevLang, setPrevLang, data]);

    const contextValue = useMemo(
        () => ({getData, getTranslations: () => getData()}),
        [data, lang]
    );

    return (
        <DataContextInstance.Provider value={contextValue}>
            <Suspense fallback={<span>Loading...</span>}>{children}</Suspense>
        </DataContextInstance.Provider>
    );
};

export const useDataContext = () => {
    const context = useContext(DataContextInstance);
    if (!context) throw new Error('Context must be used within a Provider');
    return context;
};

And then, I use that "getData" in my components in order to get the data needed for that one:然后,我在我的组件中使用该“getData”来获取该组件所需的数据

// NavBar.js
// Here I use my hook to get the DataContext context and get the "getData" func
function NavBar() {
    const {getData} = useDataContext();
    const {pages} = getData('menu').menu;
[...]

As you can see, I specify the json I want in every component in order to avoid loading all of them, so I have the "data" variable in my DataContext as a "cache", so if it is loaded I simply return it .如您所见,我在每个组件中指定了我想要的 json,以避免加载所有组件,因此我在 DataContext 中将“数据”变量作为“缓存”,因此如果加载它,我只需返回它

My problem is that I'm not able to make that work, it gets into a loop of calls and never getting on suspense (I think).我的问题是我无法完成这项工作,它进入了一个呼叫循环并且永远没有悬念(我认为)。

EDIT : I managed to capture an error log:编辑:我设法捕获了一个错误日志:

Warning: Cannot update a component (`DataContext`) while rendering a different component (`AppBase`). To locate the bad setState() call inside `AppBase`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render

The JSX structure is: JSX 结构是:

<DataContext dataLoaded={data}>
    <AppBase data={data} statusCode={statusCode} />
</DataContext>

If solved it by adding the following lines, after adding the data[path] = json (inserting a promise) I had to throw that promise in order to tell React it is loading a promise:如果通过添加以下行来解决它,在添加data[path] = json (插入承诺)之后,我必须抛出该承诺以告诉 React 它正在加载承诺:

[...]
const filePath = `/data/${path || `translations/${lang}`}.json`;
if (!path) path = lang;
const json = loadJSON(filePath).then((newData) => (data[path] = newData));
data[path] = json;
if (data[path]?.then) throw data[path];
[...]

I followed the instructions from CSS Tricks .我按照CSS Tricks的说明进行操作。

Thanks everyone for you help!谢谢大家的帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM