反应在某种情况下悬念

发布于 2025-02-13 00:32:32 字数 4426 浏览 0 评论 0原文

那些日子,我不得不解决我的React应用程序的问题,我将所有数据显示在JSON中,这些数据由 Express API ,以读取这些JSON并返回它们取决于您经过的路径。

真正的问题是在我尝试渲染组件时,组件没有JSON 信息,所以我必须始终等到我得到那些。

我的第一个想法是xmlhttprequest同步地将API称为,但我在文档中看到它是 defboreed 。因此,我阅读了悬念和过渡的新功能,但我不了解它,也不像将它们放入DataContext。我将与您分享所有我认为与您相关的代码,以便您查看到达的位置:

// 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);
    });

然后,我在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;
};

然后,我在组件中使用该模块,以获取为此,所需的数据

// 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;
[...]

如您所见,我在每个组件中指定了我想要的JSON,以避免加载所有组件,因此我在我的“数据”上有“数据”变量DataContext 作为“缓存”,因此,如果加载了,我简单地将其返回

我的问题是我无法完成这项工作,它进入了通话循环,并且永远不会悬疑(我认为)。

编辑:我设法捕获了错误日志:

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

JSX结构是:

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

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.

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.

My first idea was to call the API synchronously with XMLHttpRequest but I saw in the docs that it is deprecated. 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. 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.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:

// 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.

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:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

春花秋月 2025-02-20 00:32:33

在添加data [path] = JSON(插入诺言)之后,我已经通过添加以下行进行了解决,我不得不 thr promplot nope 才能告诉React 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];
[...]

我遵循 css技巧

感谢大家的帮助!

I've 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:

[...]
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.

Thanks everyone for you help!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文