I am currently trying to use context within a react native application. My context code is as follows:
import React, { useState, createContext } from 'react'
export const AuthContext = createContext()
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null)
return (
<AuthContext.Provider value={{ currentUser}}>
{children}
</AuthContext.Provider>
)
}
I then import context into my App.js and wrap my App.js in the provider like so:
import React, { useContext } from 'react'
import { AuthProvider, AuthContext } from './src/Context/AuthContext'
other imports etc...
export default function App() {
const { currentUser } = useContext(AuthContext)
return (
<>
<AuthProvider>
<IconRegistry icons={EvaIconsPack} />
<NavigationContainer>
<RootStackScreen isAuth={false} />
</NavigationContainer>
</AuthProvider>
</>
)
}
when i try to access currentUser in the app.js I get the following error:
component exception: undefined is not an object, evalauting useContext.currentUser
If I try to access current user in other components of my application; however, I do not receive this error and currentUser console logs to the correct value of null. I am wondering then how I can go about accessing context in app.js. is it possible to wrap a react-native application's index.js in the auth provider? or am i doing something wrong context wise.
version of react-native: 0.63.3
version of react: 16.13.1
This happening because you are accessing the context outside the provider.
If you check your createContext, you are not providing a default value.
export const AuthContext = createContext(/*No default value*/)
When the useContext is called outside the provider it will use the default value in your case its 'undefined' and it throws an error when you try to access the property currentUser of undefined.
One way to solve this issue is to use the state in app.js file instead of a separate provider component.
export default function App() {
const [currentUser, setCurrentUser] = useState(null);
const state={currentUser, setCurrentUser};
return (
<>
<AuthContext.Provider value={state}>
<IconRegistry icons={EvaIconsPack} />
<NavigationContainer>
<RootStackScreen isAuth={false} />
</NavigationContainer>
</AuthContext.Provider
</>
)
}
This will not have any impact to other components and also you can access the currentUser variable easily just like you access any state.
I haven't used the above answer, but I moved AuthContext.Provider to the index.js file and wrapped the App component in it. Now it is working fine for me. I did this because in App.js I have large code due to navigation and routing logic.
Using this we can keep our context logic separated. I used this with React.js and not with ReactNative
ex.
import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import * as serviceWorker from "./serviceWorker";
import "./index.css";
import { AuthProvider } from './src/Context/AuthContext'
ReactDOM.render(
<AuthProvider>
<App />
</AuthProvider>
document.getElementById("root")
);
serviceWorker.unregister();
Related
Im trying to use useContext hook to pass variables and functions through different components without lifting them to the main App.js component. I was trying to do this and it seemed to compile correctly but when i go to my browser my app is stucked in a blank page and remains loading.
LoginContext.js: In this component i store the user data in an object using the useState hook and i use jwt_decode to decode the use token and get all the data i need to store.
import React, { createContext, useState } from "react";
import jwt_decode from 'jwt-decode';
const LoginContext = createContext();
export function LoginProvider({children}) {
const [user, setUser] = useState({})
function handleCallbackResponse(response){
var userData = jwt_decode(response.credential); //Token with the login user Data
setUser(userData); //Store user Data
/* console.log(userData) */
document.getElementById('signInDiv').hidden = true;
}
function handleSignOut(event) {
setUser({}) //Empy the user Data
document.getElementById('signInDiv').hidden = false;
}
return(
<LoginProvider value={{user, handleCallbackResponse, handleSignOut}}>{children}</LoginProvider>
);
}
export default LoginContext
The i have my Login.js which uses LoginContext: Here i use the user to show the different data of the logged in use and the handleCallbackResponse to do my Login.
import React, { useContext, useEffect } from 'react'
import LoginContext from '../LoginContext';
const Login = () => {
const {user, handleCallbackResponse, handleSignOut} = useContext(LoginContext)
useEffect(()=>{
/*global google*/
google.accounts.id.initialize({
client_id:"My client ID",
callback: handleCallbackResponse
})
google.accounts.id.prompt();
google.accounts.id.renderButton(
document.getElementById('signInDiv'),
{theme: 'outline', size: 'medium'}
)
}, []);
return (
<div>
<div id="signInDiv"></div>
{
//If user objetc is not empty show sign out button
Object.keys(user).length !== 0 &&
<button onClick={(e)=>handleSignOut(e)}>Sign Out</button>
}
{user &&
<div>
<img src={user.picture} alt="" />
<h3>{user.name}</h3>
</div>
}
</div>
)
}
export default Login
App.js:
import './App.css';
import Login from './atoms/Login';
import { BrowserRouter , Routes, Route } from 'react-router-dom';
import Dashboard from './pages/Dashboard';
import { LoginProvider } from './LoginContext';
import PrivateRoutes from './utils/PrivateRoutes';
function App() {
return (
<LoginProvider>
<BrowserRouter>
<Routes>
{/* <Route element={<PrivateRoutes/>}>
</Route> */}
<Route exact path="/dashboard" element={<Dashboard/>}/>
<Route path="/" element={<Login/>} />
</Routes>
</BrowserRouter>
</LoginProvider>
);
}
export default App;
For some reason my application runs with no error but in the browser it remains loading with a blank page and im not able to inspect the page.
Instead of:
<LoginProvider value={{user, handleCallbackResponse, handleSignOut}}>{children}</LoginProvider>
);
Replace with
<LoginContext.Provider value={{user, handleCallbackResponse, handleSignOut}}>{children}</LoginContext.Provider>
I have my component for notification
For now this component is only visible in my home page. What I need?
I need to be visible in every route, example if I want to change route to be visible in different route, where to put this component?
Right now this is inside Home page and will be triggered in some case. Work good but when. go to different page is not visible..
Where to set this component to be visible in every page?
I am try inside
const root = document.createElement('div');
document.body.appendChild(root);
render(
<StrictMode>
<NotificationComponent />
<App />
</StrictMode>,
root,
);
but this is not solution....
if you want your notification component to visible in every page, use it or import it in your APP.js
Using Context API, wrap the App with the provider and pass the visibility setter to its children, from there you can update the notification state from any of the provider's children.
Notificationprovider
import { render } from 'less'
import React, { createContext, useState } from 'react'
export const NotificationCtx = createContext()
function NotificationProvider({ children }) {
const [isVisible, setIsVisible] = useState(false)
return <NotificationCtx.Provider value={{ setIsVisible }}>
{children}
{isVisible && <div>Notification</div>}
</NotificationCtx.Provider>
}
index
render(
<NotificationProvider>
<App />
</NotificationProvider>, root
)
App
import { useContext } from 'react'
import { NotificationCtx } from "yourpath/to/provider"
function App() {
const { setIsVisible } = useContext(NotificationCtx)
return <button onClick={() => setIsVisible(true)}></button>
}
You can access the setIsVisible from App's children using useContext.
I am using contextApi with nextjs and I'm having some trouble when providing a context just for certain routes. I am able to make the context available for just a few routes, but when I transition from one to the other through linking, I end up losing the state of my application.
I have three files inside my pages folder:
index.tsx,
Dashboard/index.tsx and
SignIn/index.tsx.
If I import the provider inside the files Dashboard/index.tsx and SignIn/index.tsx and go from one page to the other by pressing a Link component from next/link, the whole state is set back to the initial state.
The content of the Dashboard/index.tsx file
import React from 'react';
import Dashboard from '../../app/views/Dashboard';
import { AuthProvider } from '../../contexts/auth';
const Index: React.FC = () => (
<AuthProvider>
<Dashboard />
</AuthProvider>
);
export default Index;
This is the contend of the SignIn/index.tsx file:
import React from 'react';
import SignIn from '../../app/views/SignIn';
import { AuthProvider } from '../../contexts/auth';
const Index: React.FC = () => (
<AuthProvider>
<SignIn />
</AuthProvider>
);
export default Index;
The views folder is where I create the components that will be rendered.
The content of the file views/SignIn/index.tsx is:
import React, { useContext } from 'react';
import Link from 'next/link';
import { AuthContext } from '../../../contexts/auth';
const SignIn: React.FC = () => {
const { signed, signIn } = useContext(AuthContext);
async function handleSignIn() {
signIn();
}
return (
<div>
<Link href="Dashboard">Go back to Dashboard</Link>
<button onClick={handleSignIn}>Click me</button>
</div>
);
};
export default SignIn;
And the content of the file views/Dashboard/index.tsx is:
import React, { useContext } from 'react';
import Link from 'next/link';
import { AuthContext } from '../../../contexts/auth';
const Dashboard: React.FC = () => {
const { signed, signIn } = useContext(AuthContext);
async function handleSignIn() {
signIn();
}
return (
<div>
<Link href="SignIn">Go back to sign in page</Link>
<button onClick={handleSignIn}>Click me</button>
</div>
);
};
export default Dashboard;
I am able to access the context inside both /Dashboard and /SignIn, but when I press the link, the state comes back to the initial one. I figured out that the whole provider is rerenderized and therefore the new state becomes the initial state, but I wasn't able to go around this issue in a "best practices manner".
If I put the provider inside _app.tsx, I can maintain the state when transitioning between pages, but I end up providing this state to the / route as well, which I am trying to avoid.
I was able to go around this by doing the following, but it really does not seem to be the best solution for me.
I removed the Providers from Pages/SignIn/index.tsx and Pages/Dashboard/index.tsx and used the following snippet for the _app.tsx file:
import React from 'react';
import { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { AuthProvider } from '../contexts/auth';
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
const router = useRouter();
const AuthProviderRoutes = ['/SignIn', '/Dashboard'];
return (
<>
{AuthProviderRoutes.includes(router.pathname) ? (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
) : <Component {...pageProps} />}
</>
);
};
export default App;
Does anyone have a better solution?
Im new to react hooks and are experimenting a bit. I can display my values that are generated in Provider.js in App.js through Comptest.js. My problem is that the structure of my project with css etc makes it inconvenient to have a structure in the App.js like this:
<Provider>
<Comptest />
</Provider>
is it possible to fetch the data without displaying the components in that way in the app? just passing it between the components.
Here is a compact version of my application:
App.js
import React, { useContext } from "react";
import Provider from "./Provider";
import Comptest from "./Comptest";
import DataContext from "./Context";
function App() {
return (
<div className="App">
<h2>My array!</h2>
<Provider>
<Comptest />
</Provider>
</div>
);
}
export default App;
Provider.js
import React, { useState } from "react";
import DataContext from "./Context";
const Provider = props => {
const data = ["item1", "item2"];
return (
<DataContext.Provider value={data}>{props.children}</DataContext.Provider>
);
};
export default Provider;
Comptest.js
import React from "react";
import DataContext from "./Context";
const Comptest = () => {
const content = React.useContext(DataContext);
console.log(content);
return <div>{(content)}</div>;
};
export default Comptest;
Context.js
import React from "react";
const DataContext = React.createContext([]);
export default DataContext;
I have an app composed of two pages, a Home page and a Callback page. After authentication on a tiers service, such as a social network or other, the user is redirected to the Callback page of my app. The app uses the react-context api and I would like to store all the data found in the query string of the callback page. Shortly I want to open a page of my app, not the home page, and update the context of the app from this page.
This is what is achieved in this tutorial for Auth0. The app is composed of 5 very short files, I will reproduce them here.
The problem is that I get a warning when I access http://127.0.0.1:3000/callback in a browser. The warning is Warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
In the Auth0 tutorial, this warning doesn't show up but the state update is made within a dedicated function, I don't know how to reproduce this work with my own components.
To reproduce the warning, use create-react-app and use the files at the end of the post.
The goal of this toy example is to set unused_data in the state of Context component, but without triggering the warning (unused_data is actually set).
EDIT : Well, I learned redux and everything works fine when I use redux, so I still don't know why I got an error, but everything is just so much simpler now :)
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Context from "./Context";
import Callback from './Callback'
import Home from './Home'
class App extends Component {
render() {
return (
<div className="App">
<Context>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/callback" component={Callback} />
</Switch>
</Router>
</Context>
</div>
);
}
}
export default App;
Context.js
import React, {Component} from "react";
import {Provider} from "./contextDefs";
class Context extends Component {
state = {
unused_data: ""
};
changeState = () => {
this.setValue()
}
setValue = () => {
this.setState({
unused_data: "has been set up",
})
}
render() {
const providerValue = {
...this.state,
changeState: this.changeState,
};
return (
<Provider value={providerValue}>
{this.props.children}
</Provider>
);
}
}
export default Context;
Callback.js
import React from "react";
import {Redirect} from "react-router-dom";
import {Consumer} from "./contextDefs";
const callback = props => (
<Consumer>
{({changeState}) => {
changeState();
return <Redirect to="/"/>;
}
}
</Consumer>
)
export default callback;
contextDefs.js
import { createContext } from "react";
const Context = createContext({
unused_data: "",
changeState: () => {},
});
export const Provider = Context.Provider;
export const Consumer = Context.Consumer;
Home.js
import React from "react";
const AuthTwitter = props => {
return (
<div>
Test
</div>
);
}
export default AuthTwitter;