I am trying to pass a state to another component but it doesn't work. Here is my app.js file. Can someone please advise me on what's wrong here?
I'm creating the userContext in App.js and I want to use it in other files. In this code, I'm trying to use the userContext in the Navbar component.
function App() {
const [login, setLogin] = useState(0);
return (
<userContext.Provider value={[setLogin, login]}>
<BrowserRouter>
<Navbar />
<Routes>
{...routes}
</Routes>
</BrowserRouter>
</userContext.Provider>
);
}
export default App;
const Navbar = () => {
const [login, setLogin] = useContext(userContext);
const logout = () => {
setLogin(0);
};
if (login) {
return <button onClick={() => setLogin(0}>Logout</button>
}
return <button onClick={() => setLogin(1}>Login</button>
};
You are setting the context value in the App.js as:
<userContext.Provider value={[setLogin, login]}>
And when you are using the context value you are destructuring it in different order:
const [login, setLogin] = useContext(userContext);
Which means that your setLogin function and login state get mixed up. Change the oder of the elements in the provider to:
<userContext.Provider value={[login, setLogin]}>
Related
new to React Native.
I'm using Context hook state to use an array in two tab screens of an application.
One of the screens displays the context array in the form of a FlatList, the other one inserts items into the array. What would be the proper way to rerender when the array changes?
Here's the code:
APP.JS
const App = () => {
const Tab = createBottomTabNavigator();
return(
<NavigationContainer>
<Tab.Screen name="HomeScreen" component={Home} />
<Tab.Screen name="DiscoverScreen" component={Discover} />
</NavigationContainer>
);
}
export default function AppWrapper() {
return (
<AuthProvider>
<App />
</AuthProvider>
);
}
AUTHCONTEXT.JS
const AuthContext = createContext()
const AuthProvider = ({ children }) => {
const [array, setArray] = useState([]);
return (
<AuthContext.Provider value={{ array, setArray }}>
{children}
</AuthContext.Provider>
)
}
export {AuthContext, AuthProvider}
HOME.JS
export default Home = () => {
const {array} = useContext(AuthContext);
return(
<View>
<FlatList
data={array}
renderItem={(item)=>{
return(
<Text>{item}</Text>
)}}
/>
</View>
)
}
DISCOVER.JS
export default Discover = () => {
const {setArray} = useContext(AuthContext);
setArray((currentArray)=>[...currentArray,'test']);
return(
<View></View>
);
}
You shouldn't invoke setArray unconditionally in Discover function component. If you need to add value to the array when component appears, use useEffect hook (and don't forget to setup second argument, probably it will be empty array, otherwise you get into infinite loop and component crash with an error)
export default Discover = () => {
const {setArray} = useContext(AuthContext);
useEffect(() => {
setArray((currentArray)=>[...currentArray,'test']);
}, [])
return(
<View></View>
);
}
And you don't need to worry about rerender the component, React handle this for you. Once setArray invokes it automatically rerender all component that use AuthContext and their children.
I'm trying to use the Context Api to save the user data that comes from the firebase api, but when I get these values in the component Index, it always returns the error:
TypeError: Object is not iterable (cannot read property Symbol(Symbol.iterator))
Below is my code
Where do I create the Context
import React, { createContext, useState } from "react";
const Context = createContext();
function AuthProvider({children}) {
const [userLogin, setUserLogin] = useState({});
return (
<Context.Provider value={{ userLogin, setUserLogin }} >
{ children }
</Context.Provider>
);
}
export { Context, AuthProvider }
Route File
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path='/' element={<Login />} />
<Route path='/cadastrar' element={<Register />} />
<Route path='/recuperar-senha' element={<Recovery />} />
<Route path='/anuncios' exact element={<Index />} />
</Routes>
</AuthProvider>
</BrowserRouter>
</React.StrictMode>
Here where I set the context with firebase data
const {setUserLogin} = useContext(Context);
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = data => {
const auth = getAuth();
signInWithEmailAndPassword(auth, data.email, data.password)
.then((userCredential) => {
const user = userCredential.user;
setUserLogin({user});
navigate('/anuncios');
})
.catch((error) => {
let erroMessage = localizeErrorMap(error.code)
Toast('error', erroMessage);
})
};
I want to save the data in the context api and be able to take, for example, the component below and the others
import React, {useContext} from "react";
import { Context } from '../providers/auth';
export default function Index() {
const [user, setUserLogin] = useContext(Context);
//console.log(user);
return (
<h1>Logado {user}</h1>
)
}
I saw that in the browser console when I click on the component, it informs that the problem is in the line: const [user, setUserLogin] = useContext(Context);
Your context value is { userLogin, setUserLogin }:
<Context.Provider value={{ userLogin, setUserLogin }}>
so you cannot destructure it into an array. Use const { userLogin, setUserLogin } = useContext(Context) instead:
export default function Index() {
const { userLogin, setUserLogin } = useContext(Context);
console.log(userLogin);
...
I don't know why, changing the props state inside useEffect causes infinite loop of errors. I used them first locally declaring within the function without using props which was running ok.
EDIT:
Home.js
import Axios from "axios";
import React, { useEffect, useState } from "react";
function Home(props) {
// const [details, setDetails] = useState({});
// const [login, setLogin] = useState(false);
useEffect(() => {
try {
const data = localStorage.getItem("expensesAccDetails");
if (data) {
Axios.post("http://localhost:3001/eachCollectionData", {
collection: data,
}).then((res) => {
if (res.data.err) {
console.log("Error");
} else {
console.log(res.data[0]);
props.setLogin(true);
props.setUserdetails(res.data[0]);
}
});
}
} catch (err) {
console.log(err);
}
}, []);
return props.login ? (
<div>
<div>Welcome {props.setUserdetails.FullName}</div>
</div>
) : (
<div>You need to login first</div>
);
}
export default Home;
App.js
function App() {
const [login, setLogin] = useState(false);
const [userdetails, setUserdetails] = useState({});
return (
<Router>
<Routes>
<Route
path="/Home"
element={
<>
<Home
setLogin={setLogin}
login={login}
setUserdetails={setUserdetails}
userdetails={userdetails}
/>
<Bars login={login} />
</>
}
/>
<Routes>
<Router>
);
Here I initialized the states directly in App.js so I don't have to declare it on every page for the route renders. I just passed them as props to every component.
I suggest to create a componente Home with the post and two sub-component inside:
const Home = () => {
const [userDetails, setUserDetails] = useState({});
const [login, setLogin] = useState(false);
useEffect(() => {
// api call
}, []);
return (
<>
<Welcome login={login} details={userDetails} />
<Bars login={login} details={userDetails} />
</>
);
};
where Welcome is the following:
const Welcome = ({ userdetails, login }) => (
<>
login ? (
<div>
<div>Welcome {userdetails.FullName}</div>
</div>
) : (
<div>You need to login first</div>
);
</>
);
A better solution is to use only one state variable:
const [userDetails, setUserDetails] = useState(null);
and test if userDetails is null as you test login is true.
An alternative if you have to maintain the call as you write before, you can use two state as the follow:
function App() {
const [userdetails, setUserdetails] = useState(null);
return (
<Router>
<Routes>
<Route
path="/Home"
element={
<>
<Home
setUserdetails={setUserdetails}
/>
<Bars login={!!userdetails} />
</>
}
/>
<Routes>
<Router>
);
and on Home component use a local state:
const Home = ({setUserdetails}) => {
const [userDetailsLocal, setUserDetailsLocal] = useState(null);
useEffect(() => {
// api call
// ... on response received:
setUserdetails(res.data[0]);
setUserDetailsLocal(res.data[0]);
// ...
}, []);
userDetailsLocal ? (
<div>
<div>Welcome {userDetailsLocal.FullName}</div>
</div>
) : (
<div>You need to login first</div>
);
};
I advise to follow Max arquitecture for your solution. the problem lies in the Router behavior. React Router is not part of React core, so you must use it outside your react logic.
from documentation of React Router:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render.
https://v5.reactrouter.com/web/api/Route/component
Edit:
ok, you make me write it. A solution could be like:
function App() {
const [login, setLogin] = useState(false);
const [userdetails, setUserdetails] = useState({});
useEffect(() => {
try {
const data = localStorage.getItem("expensesAccDetails");
if (data) {
Axios.post("http://localhost:3001/eachCollectionData", {
collection: data,
}).then((res) => {
if (res.data.err) {
console.log("Error");
} else {
console.log(res.data[0]);
setLogin(true);
setUserdetails(res.data[0]);
}
});
}
} catch (err) {
console.log(err);
}
}, []);
return (
<Router>
<Routes>
<Route
path="/Home"
element={
<>
<Home
login={login}
userdetails={userdetails}
/>
<Bars login={login} />
</>
}
/>
<Routes>
<Router>
);
I used a history.push("/") to redirect to my homepage from another page and the homepage has a ProjectList component that dispatches an action:
App.js
function App(){
return(
<Router>
<NavBar></NavBar>
<Switch>
<Route
path="/createProject"
component={withAuthentication(CreateProjectPage)}
></Route>
<Route
exact
path="/"
component={withAuthentication(HomePage)}
></Route>
</Switch>
</Router>
);
}
export default App
CreateProjectPage
function CreateProject(){
const dispatch = useDispatch();
const history = useHistory();
const submitHandler = (e) => {
...
dispatch(createNewProject({...}));
history.push("/");
}
return (
<div>
...
<Button
onClick={submitHandler}
variant="contained"
color="primary"
>
Submit
</Button>
</div>
)
}
HomePage
export default function HomePage(props){
return (
<div>
<ProjectList level="one"/>
...
</div>
);
}
ProjectList Component:
export default function ProjectList({level}){
console.log(level); //onRedirect, this gets called
const dispatch = useDispatch();
useEffect(() => {
console.log("dispatch returnAllProjects called") //onRedirect, this does not get called
dispatch(returnAllProjects());
}, [dispatch]);
...
return ...
}
only on refresh this returnAllProjects is called.
This results in getting undefined objects.
Any help would be greatly appreciated!
You should add location as dependency if you want the effect to get called after redirect:
import { useLocation } from 'react-router-dom';
export default function ProjectList({level}){
const location = useLocation(); // <--- listen to location
const dispatch = useDispatch();
useEffect(() => {
dispatch(returnAllProjects());
}, [dispatch, location]); //<--- location is added as dependency
...
return ...
}
I'm using React contexts in order to hold my authentication state for my application. Currently, I'm having an issue where whenever I try and hit /groups/:id, it always redirects me to /login first and then to /UserDash. This is happening because the context of my AuthProvider isn't updating fast enough, and my Private Route utilized the AuthContext to decide whether to redirect or not.
<AuthProvider>
<Router>
<Switch>
<LoggedRoute exact path = "/" component = {Home}/>
<Route exact path = "/login" component = {Login}/>
<PrivateRoute exact path = "/groups/:id" component = {GroupDash}/>
<PrivateRoute exact path = "/UserDash" component = {UserDash}/>
</Switch>
</Router>
</AuthProvider>
In another file:
export const AuthContext = React.createContext();
export const AuthProvider = ({children}) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
firebaseApp.auth().onAuthStateChanged(setCurrentUser);
},[]);
return (
<AuthContext.Provider
value = {{currentUser}}
>
{children}
</AuthContext.Provider>
);
My private route:
const PrivateRoute = ({component: RouteComponent, ...rest}) => {
const {currentUser} = useContext((context) => AuthContext);
return (
<Route
{...rest}
render={routeProps => (currentUser) ? (<RouteComponent{...routeProps}/>) : (<Redirect to={"/login"}/>)}
/>
)
};
And my login page
const {currentUser} = useContext(AuthContext);
if (currentUser) {
return <Redirect to = "/UserDash" />
}
return (
<button onClick={(e) => {googleLogin(history)}}>Log In</button>
);
Is there any way to force the context to load before the private route redirects the user to the login page? I've tried using firebase.auth().currentUser inside my PrivateRoute instead, but that also fails and I still get redirected to "/login", before getting pushed to "/UserDash".
Since useEffect runs after a render and the value being fetched in useEffect is from a async call what can do is to actually maintain a loading state till data is available like
export const AuthContext = React.createContext();
export const AuthProvider = ({children}) => {
const [currentUser, setCurrentUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
firebaseApp.auth().onAuthStateChanged((user) => {
setCurrentUser(); setIsLoading(false)
});
},[]);
return (
<AuthContext.Provider
value = {{currentUser, isLoading}}
>
{children}
</AuthContext.Provider>
);
}
Then in privateRoute
const PrivateRoute = ({component: RouteComponent, ...rest}) => {
const {currentUser, isLoading} = useContext((context) => AuthContext);
if(isLoading) return <div>Loading...</div>
return (
<Route
{...rest}
render={routeProps => (currentUser) ? (<RouteComponent{...routeProps}/>) : (<Redirect to={"/login"}/>)}
/>
)
};