I have a react app with a bunch of state
function App(){
const [activeChoice, setActiveChoice] = useState("flights");
const [overlay, setOverlay] = useState(false);
const [airports, setAirports] = useState<Airport[]>([]);
const [loading, setLoading] = useState(false);
const GlobalState = {
loading,
airports,
};
const MainContext = createContext(GlobalState);
return(
//Some code
);
}
Problem I create context to share data across my application. I cant export it like this
export const MainContext = createContext(GlobalState);
because MainContext
is inside the App function and i get an error Modifiers cannot appear here.
If i create the context outside the App function. I cant reference the GlobalState object because it is inside the App function. Remember GlobalState has state properties that cant be created outside the React component.
I also cant use the createContext function without passing the default value (TypeScript).So i have to pass that GlobalState object. If i try using it empty i get an error n argument for 'defaultValue' was not provided.
I want to useContext inside another component in a different file . I have wrapped the child component with the provider as necessary but in the other component i dont know how to get the context so as to do this.
const {airports} = useContext(MainContext);
CodePudding user response:
MainContext is inside the App function
You can't do that. Not only does it make it impossible to export, but it also means that every time App rerenders, you're creating a brand new context. Not just new values on the existing context, a new type of context unrelated to the old one. So your MainContext.Provider will unmount, and a new one be created in its place. And even if you were somehow consuming the old one in a child component, it will lose that connection when the parent rerenders.
You need to define the context once, outside of App. Inside of app, you can fill out the value.
export const MainContext = createContext(); // We'll get to the types in a sec
function App(){
const [activeChoice, setActiveChoice] = useState("flights");
const [overlay, setOverlay] = useState(false);
const [airports, setAirports] = useState<Airport[]>([]);
const [loading, setLoading] = useState(false);
const GlobalState = {
loading,
airports,
};
return(
<MainContext.Provider value={GlobalState}>
// some code
</MainContext.Provider>
);
}
I also cant use the createContext function without passing the default value (TypeScript).
There are a few solutions to this. One is to just fill in a default value, with things like empty arrays where an array is required, or functions that do nothing (() => {}
) where a function is required.
interface MainContextValue {
loading: boolean,
airports: Airport[]
}
export const MainContext = createContext<MainContextValue>({
loading: false,
airports: []
});
But that can be combersome for large contexts, and in most cases you don't intend for the default value to ever be used. So a second option is to give it a default value that's not technically correct for the type, but just shut typescript up with a type assertion:
export const MainContext = createContext<MainContextValue>(undefined as MainContextValue)
Another option is to set up the context so it can include undefined, but then when you access it, do so through a custom hook that will throw an error if it's undefined. This will exclude undefined from the type, but also show a relevant error message to remind you what needs to be fixed:
export const MainContext = createContext<MainContextValue | undefined>()
MainContext.displayName = 'Main'; // to help with debugging
// Elsewhere:
export function useContextThrowUndefined<T>(
context: Context<T | undefined>
): T {
const value = useContext(context);
if (value === undefined) {
const name = context.displayName ?? "";
throw new Error(
`${name} context consumer was used outside of a context provider.
To fix this, make sure the provider is always higher up the component tree than the consumer.
`);
}
return value;
}
// used like:
const SomeComponent = () => {
const { airports } = useContextThrowUndefined(MainContext);
}
CodePudding user response:
You can define the context outside with its default value to null
or a default value that makes sense e.g. { loading: false, airports: [] }
.
Both can be typed as in the following code snippet.
export GlobalStateType {
loading: boolean;
airports: Airport[];
}
const MainContext = createContext<GlobalStateType | null>(null);
// or with empty state
const MainContext = createContext<GlobalStateType>({ loading: true, airports: [] });
function App(){
const [activeChoice, setActiveChoice] = useState("flights");
const [overlay, setOverlay] = useState(false);
const [airports, setAirports] = useState<Airport[]>([]);
const [loading, setLoading] = useState(false);
const GlobalState: GlobalStateType = {
loading,
airports,
};
return(
<MainContext.Provider value={GlobalState}>
</MainContext.Provider>
);
}