I currently have 2 contexts within my React app and I was trying to call a method from my top-level context within my 2nd context.
Here is how the context are nested:
App.js
function App(props) {
return (
<SessionContextProvider>
<APIContextProvider>
// I have some components here
</APIContextProvider>
</SessionContextProviders>
)
}
is there a way to consume the SessionContext within my APIContextProvider?
import { SessionContext } from 'contexts/session'
export const APIContext = createContext();
export default class APIContextProvider extends Component {
static contextType = SessionContext
randomMethod() {
this.context.logoutUser()
}
render() {
return (
<APIContext.Provider value={{randomMethod: this.randomMethod}}>
{this.props.children}
</APIContext.Provider>
)
}
}
The issue is that when running randomMethod within my APIContext doesn't work because this.context is undefined.
Is this feasible or am I missing something?
I created an example for you, where ApiProvider uses logoutUser from SessionContext and providing randomMethod, which calls the function logoutUser.
import React, { createContext } from "react";
const SessionContext = createContext();
const SessionProvider = props => {
const logoutUser = () => {
alert("Logout user, but fast!");
};
return (
<SessionContext.Provider value={logoutUser}>
{props.children}
</SessionContext.Provider>
);
};
export { SessionContext as default, SessionProvider };
Inner context
import React, { createContext, useContext } from "react";
import SessionContext from "./SessionContext";
const ApiContext = createContext();
const ApiProvider = props => {
const logoutUser = useContext(SessionContext);
const randomMethod = () => {
logoutUser();
};
return (
<ApiContext.Provider value={{ randomMethod: randomMethod }}>
{props.children}
</ApiContext.Provider>
);
};
export { ApiContext as default, ApiProvider };
App.js
export default function App() {
return (
<SessionProvider>
<ApiProvider>
<TestComponent />
</ApiProvider>
</SessionProvider>
);
}
https://codesandbox.io/s/late-bush-959st
Related
Here I have declared a basic prototype for my context store and when I try to use this store it is returning undefined even though I double checked the syntax so someone can please help why its returning undefined:
Store:
import { createContext } from "react";
const AuthContext = createContext("Default");
export const AuthContextProvider = (props) => {
const loginHandler = () => {
console.log("Login Handler");
};
const logoutHandler = () => {
console.log("Logout Handler");
};
const contextValue = {
token: "false",
loging: loginHandler,
logout: logoutHandler,
};
return (
<AuthContext.Provider value={contextValue}>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
App:
import { useContext } from "react";
import Main from "./components/Main";
import "./App.css";
import { Provider } from "react-redux";
import store from "./store/DataStore";
import AuthContext from "./store/AuthStore";
const App = () => {
const ctx = useContext(AuthContext);
return (
<Provider store={store}>
{console.log(ctx.token)}
<Main />
</Provider>
);
};
export default App;
1.) Your default value for the context is the string "Default" (https://reactjs.org/docs/context.html#reactcreatecontext)
2.) You are reading the context before using the provider you defined, so you are never setting the context value to your object. (see second paragraph of https://reactjs.org/docs/context.html#reactcreatecontext)
The result is you are reading the token property off the string "Default"...which would be undefined. Linked is an example using your provider and how it changes context in components further down the tree. https://playcode.io/980421
Wrap your App component by the AuthContextProvider component.
index.js
root.render(
<StrictMode>
<AuthContextProvider>
<App />
</AuthContextProvider>
</StrictMode>
);
App.js
import { useContext } from "react";
import AuthContext from "./store/authStore";
const App = () => {
const ctx = useContext(AuthContext);
return (
<div>
{console.log(ctx.token)}
<p>Hello</p>
{console.log(ctx)}
</div>
);
};
export default App;
authStore.js
import { createContext } from "react";
const AuthContext = createContext("Default");
export const AuthContextProvider = (props) => {
const loginHandler = () => {
console.log("Login Handler");
};
const logoutHandler = () => {
console.log("Logout Handler");
};
const contextValue = {
token: "false",
loging: loginHandler,
logout: logoutHandler,
};
return (
<AuthContext.Provider value={contextValue}>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
I am a noob with TypeScript and React Hooks. So I'm trying to learn with a simple todo app, please bear with me:
I've been trying to create a global state using the useContext and useState hooks. I set up some default values when I used useState, however when I try to access it through the Provider, I get an undefined value.
Here is my implementation of the context and Provider component:
import React, { createContext, PropsWithChildren, useState } from "react";
export interface AppContextType {
todos: TodoType[];
addTodo: (todo: TodoType) => void;
}
export interface TodoType {
todo: string;
}
export const AppContext = createContext<AppContextType>({} as AppContextType);
export const AppContextProvider: React.FC<PropsWithChildren> = ({ children }) => {
const [todos, setTodos] = useState<TodoType[]>([
{ todo: "Learn React" },
{ todo: "Create Todo app" },
{ todo: "Learn TypeScript" },
]);
const addTodo = (todo: TodoType) => {
setTodos([...todos, todo]);
}
return (
<AppContext.Provider value={{ todos, addTodo }}>
{children}
</AppContext.Provider>
);
}
And here is my main App.tsx code:
import React, { useContext } from "react";
import TodoForm from "./components/TodoForm";
import {
AppContext,
AppContextProvider,
TodoType,
} from "./context/AppContext";
function App() {
const { todos, addTodo } = useContext(AppContext);
console.log(todos); // Returns undefined.
return (
<AppContextProvider>
<ul>
{todos.map((t: TodoType) => {
return <li key={t.todo}>{t.todo}</li>
})}
</ul>
<TodoForm />
</AppContextProvider>
);
}
export default App;
Am I overlooking anything?
Any help is appreciated, thank you!
EDIT: Thanks to #Erfan's answer, the issue was fixed by removing the code accessing these values in the same place where the Provider is the root, and by putting that code into a child component.
Updated App.tsx code:
import TodoForm from "./components/TodoForm";
import TodoList from "./components/TodoList";
import { AppContextProvider } from "./context/AppContext";
function App() {
return (
<AppContextProvider>
<TodoList />
<TodoForm />
</AppContextProvider>
);
}
export default App;
And the new TodoList component:
import React, { useContext } from "react";
import {
AppContext,
TodoType,
} from "../context/AppContext";
const TodoList = () => {
const { todos, addTodo } = useContext(AppContext);
console.log(todos); // This is ok!
return (
<ul>
{todos.map((t: TodoType) => {
return <li key={t.todo}>{t.todo}</li>
})}
</ul>
)
}
export default TodoList;
You are using AppContextProvider in the same component you want to use AppContext values.
In order to use the values, you need to wrap elements at least from one level higher than the current component.
In your case, you can create a List component and use the context value inside it.
const List = ()=>{
const { todos, addTodo } = useContext(AppContext);
console.log(todos); // Returns undefined.
return (<ul>
{todos.map((t: TodoType) => {
return <li key={t.todo}>{t.todo}</li>
})}
</ul>)
}
App:
function App() {
return (
<AppContextProvider>
<List/>
<TodoForm />
</AppContextProvider>
);}
I have passed down a state variable and function from a context file:
UserContext:
import React, { useState } from 'react';
const UserContext = React.createContext();
function UserProvider({ children }) {
var [userImages, setUserImages] = useState({
avatar: '/static/uploads/profile-avatars/placeholder.jpg'
});
return (
<UserContext.Provider
value={{
userImages,
setUserImages
}}
>
{children}
</UserContext.Provider>
);
}
export default UserContext;
export { UserProvider };
At this point UserImages is just an object with one prop i.e. avatar
This is my App which is being wrapped by the Provider (please disregard the redux implementation, I just wanted to try Context)
import React from 'react';
import { Provider } from 'react-redux';
import { UserProvider } from './UserContext';
import App from 'next/app';
import withRedux from 'next-redux-wrapper';
import { PersistGate } from 'redux-persist/integration/react';
import reduxStore from '../store/index';
import withReactRouter from '../with-react-router/with-react-router';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx)
: {};
return { pageProps };
}
render() {
const { Component, pageProps, store } = this.props;
return (
<UserProvider>
<Provider store={store}>
<PersistGate persistor={store.__PERSISTOR} loading={null}>
<Component {...pageProps} />
</PersistGate>
</Provider>
</UserProvider>
);
}
}
I am trying to update some context using a setState function following this post
However I still get TypeError: Cannot read property 'avatar' of undefined
This is the shape of the state object:
userData:
setUserImages: ƒ ()
userImages:
avatar: "/static/uploads/profile-avatars/placeholder.jpg"
or
userData : {
setUserImages : SetUserImages function,
userImages : {
avatar : "/static/uploads/profile-avatars/placeholder.jpg"
}
}
My component:
function ImageUploader({ userData }) {
var { avatar } = userData.userImages;
var setUserAvatar = userData.setUserImages;
function avatarUpdater(avatarPath) {
setUserAvatar({ userData: { ...userData.userImages.avatar, avatarPath } });
}
}
Does anyone have an idea why this is happening?
UserProvider is the root of your app, so you can directly get it {userImages, setUserImages} in ImageUploader
function ImageUploader() {
const {userImages, setUserImages} = useContext(UserContext)
const { avatar } = userImages;
function avatarUpdater(avatarPath) {
setUserImages({ avatar: avatarPath });
}
}
Typically its good practice to not expose the setState from your context. You wanna wrap it in an explicit method to update state, then add that method to your Provider. Something like:
const userContext = {
avatar: userImages,
updateAvatarUrl: (url) => {
setUserImages(prevState => ({...prevState, avatar: url}))
}
}
return <UserContext.Provider value={userContext}>{children}</UserContext.Provider>
Try adding a hook for your UserContext which you can consume inside your component.
In UserContext add
export const useUserContext = () => useContext(UserContext)
Then inside your component:
import { useUserContext } from '<UserContext import>'
...
function avatarUpdater(avatarPath) {
userCtx.updateAvatarUrl(avatarPath)
}
Cleanest structure for Context in my opinion. Allows for more precise control over context state.
I am trying to learn react and stuck at useContext. I have tried to export Provider and also change value data from provider but still it is returning undefined !
My Context
import React from 'react';
const BlogContext = React.createContext();
export const BlogProvider = ({ children }) => {
const blogPosts = [
{ title: "Blog Post #1" },
{ title: "Blog Post #2" }
];
return (
<BlogContext.Provider value={blogPosts}>
{children}
</BlogContext.Provider>
);
};
export default BlogContext;
My Screen
import React, { useContext } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { BlogContext } from '../context/BlogContext';
const IndexScreen = () => {
const blogPosts = useContext(BlogContext);
console.log(blogPosts);
return (
<View>
<Text>Index Screen</Text>
</View>
);
};
const styles = StyleSheet.create({
});
export default IndexScreen;
When you use export const ... You import it with curly brackets { }
If you used export default ... You should import without the curly brackets.
So you have mixed it:
// just export the context
export const BlogContext = createContext()
const BlogProvider = ({ children }) => {
const blogPosts = [...]
return (
<BlogContext.Provider value={blogPosts}>
{children}
</BlogContext.Provider>
)
}
// export default the component
export default BlogProvider
Second, You need to Wrap your App with your BlogProvider component:
// App.js
import React from "react"
import BlogProvider from "./context/AppContext"
import IndexScreen from "./screens/IndexScreen"
const App = () => {
return (
<BlogProvider>
<IndexScreen />
// More screens...
</BlogProvider>
)
}
export default App
Here is a working codeSandbox example.
I have a context API:
import React, { createContext, useState } from "react";
const UserContext = createContext();
const UserContextProvider = (props) => {
const [userInfo, setUserInfo] = useState({});
return (
<UserContext.Provider value={{ userInfo, setUserInfo }}>
{props.children}
</UserContext.Provider>
)
};
export { UserContextProvider, UserContext };
and use it in App.js:
<UserContextProvider>
// Router,...
</UserContextProvider>
Well, I gonna use context API in component like a service:
import { UserContext } from "...";
function UserService() {
const { userInfo, setUserInfo } = useContext(UserContext);
const updateUserInfo = (newUserInfo) => {
setUserInfo(newUserInfo); // for example: {name:'x'}
}
return null;
}
Now I wanna use UserService inside a component without add <UserService /> ? How can I call UserService.updateUserInfo()?
Your don't need to userService.You can use UserContext directly inside App.js and access to its function but you must wrap App.js inside UserContextProvider like:
<UserContextProvider>
<App />
</UserContextProvider>
Or you can use HOC(higher order component) like:
const withUsersContext = (Comp) => {
return (props) => (
<UserContextProvider>
<Comp {...props} />
</UserContextProvider>
);
};
// then inside App.js:
...
export default withUsersContext(App)
Now inside App.js:
const { userInfo, setUserInfo } = useContext(UserContext);
Note: if you wanna use UserContext inside something like UserService you must write a custom hook for UserService(rules of react-hooks).
Custom hooks for UserService:
function useUserService() {
const { userInfo, setUserInfo } = useContext(UserContext);
const updateUserInfo = (newUserInfo) => {
setUserInfo(newUserInfo);
}
return { updateUserInfo };
}
How to use inside a component:
...
const { updateUserInfo }= useUserService();
...