Monday, May 20, 2024
 Popular · Latest · Hot · Upcoming
192
rated 0 times [  198] [ 6]  / answers: 1 / hits: 5873  / 4 Years ago, wed, september 9, 2020, 12:00:00

I've been fighting this for a few days now and I can't get the righ way of doing what I need.
Basically I have a context that provides a theme fetched from an API. The context has the values of theme, loading, and functions to fetch and update the loading state. It looks like so:



import React, { useState } from "react";

import ThemeService from "../services/GetThemes";

const AvailableThemesContext = React.createContext();
const { Provider, Consumer } = AvailableThemesContext;

const ThemesProvider = ({ children }) => {
const [contextTheme, setTheme] = useState({});
const [loading, setLoading] = useState(true);

function handleData(data) {
setTheme(data);
setLoading(false);
}

function fetchThemes() {
setLoading(true);
new ThemeService().getData(handleData);
}

function refresh(data) {
setTheme(data);
}

return (
<Provider value={{ contextTheme, fetchThemes, refresh, setLoading, }}>
{children}
</Provider>
);
};

export { ThemesProvider, Consumer as ThemeConsumer, AvailableThemesContext };



This is how I provide said context to the App in the index.js file:



import { ThemesProvider } from "./context/ThemeProvider";

ReactDOM.render(
<ThemesProvider value={{}}>
<App />
</ThemesProvider>,
document.getElementById("root")
);


In the child component, there is a useEffect that looks at the loadingstate to render a spinner or some other stuff. As default renders the spinner. Then I try to change the state of loading to false but I can't manage to do so.


Here is my test:



import React from "react";
// import { render } from "@testing-library/react";
import { render, screen } from "./test-utils";
import CalendarView from "../views/CalendarView";
import AvailableThemesContext from "../context/ThemeProvider";

const { loading } = AvailableThemesContext;

describe("<CalendarView />", () => {
test("It renders without crashing", async () => {
const { getByLabelText } = render(<CalendarView />);
expect(getByLabelText("audio-loading")).toBeInTheDocument();
// ---> This looks for the loader ands resolves OK
});

test("It renders without crashing", () => {
const renderComponent = render(
<ThemeProvider value={{ loading: false}}>
<CalendarView />);
</ThemesProvider>
expect(renderComponent.getByTestId("picker-component")).toBeInTheDocument();
});

});



For more that I try to inject the new state the component never sees the change.


Help please?


More From » reactjs

 Answers
1

So I finally managed to find a solution that I think is ok and hope is a good practice. I've basically streamlined my context provider. As it was right now, it was written by a colleague and he had destructured the Provider and Cosumer of the context.
It makes for a nice looking code but hella complicated to debug.


Now my context provider looks like so:



import React, { useState } from "react";
import PropTypes from "prop-types";

import ThemeService from "../services/GetThemes";

export const AvailableThemesContext = React.createContext();

const ThemesProvider = ({ children }) => {
const [contextTheme, setTheme] = useState([]);
const [loading, setLoading] = useState(true);

function handleData(data) {
setTheme(data);
setLoading(false);
}

function fetchThemes() {
setLoading(true);
new ThemeService().getData(handleData);
}

function refresh(data) {
setTheme(data);
}

return (
<AvailableThemesContext.Provider
value={{ loading, contextTheme, fetchThemes, refresh }}
>
{children}
</AvailableThemesContext.Provider>
);
};

export default ThemesProvider;

ThemesProvider.propTypes = {
children: PropTypes.element.isRequired,
};



The default export is the context function holding the state and actions. And the Context itself is a const.


And the test now goes as it should. Injecting the value directly with the Context.Provider to it works as expected:


import ThemesProvider, {
AvailableThemesContext,
} from "../context/ThemeProvider";

describe("<CalendarView />", () => {
test("It renders without crashing", async () => {
const { getByLabelText } = render(
<ThemesProvider>
<CalendarView />
</ThemesProvider>
);
expect(getByLabelText("audio-loading")).toBeInTheDocument();
});

test("It renders without crashing", () => {
const { getByTestId } = render(
<AvailableThemesContext.Provider value={{ loading: false }}>
<CalendarView />
</AvailableThemesContext.Provider>
);
expect(getByTestId("picker-component")).toBeInTheDocument();
});
});



For those test that doesn't need injecting new state to the context, the component can be wrapped directly with the function holding the state as the first test shows.


That way I can quickly identify which is just consuming the state and which modifying it.


[#2713] Friday, September 4, 2020, 4 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
nestorjarettg

Total Points: 451
Total Questions: 108
Total Answers: 108

Location: Rwanda
Member since Thu, Feb 10, 2022
2 Years ago
nestorjarettg questions
;