I am trying to create a simple react app for lending phones with this api.
I am trying to grab the mobiles with context api like this:
import React, { useState, useEffect, createContext
} from 'react';
import axios from 'axios';
export const MobileContext = createContext({
mobiles: [],
setMobiles: () => {},
updateMobiles: () => {},
});
export default function MobileProvider(props) {
const [mobiles, setMobiles] = useState([]);
const updateMobiles = (id) => {
axios
.get('https://js-test-api.etnetera.cz/api/v1/phones')
.then((res) => setMobiles(res.data));
};
useEffect(() => {
axios
.get('https://js-test-api.etnetera.cz/api/v1/phones')
.then((res) => setMobiles(res.data));
}, [] );
return (
<MobileContext.Provider value={{ mobiles, setMobiles, updateMobiles }}>
{props.children}
</MobileContext.Provider>
);
}
and reuse them at the main page after logging in
import React from 'react'
import { MobileContext } from './MobileContext';
import { useContext } from 'react';
import Mobile from './Mobile';
import Navbar from './Navbar';
function MobileList() {
const { mobiles } = useContext(MobileContext);
return (
<div>
<Navbar/>
{mobiles.map((item) => (
<Mobile
vendor={item.vendor}
/>
))}
</div>
)
}
export default MobileList
and this is the single mobile component
import React from 'react'
function Mobile(props) {
return (
<div>
<p>{props.vendor}</p>
<p> ssssssssssss</p>
</div>
)
}
export default Mobile
after the correct logging in, it should display both the text and the vendor for each mobile but it isnt displaying anything besides the navbar
this would probably mean, that I am not getting the mobiles from the api in the first place, but I am not sure why is that. The auth token could also be the reason why I am not able to access the phones,never used it before.
Anyway, this is the full code and I would apreciate any help
login.js
import React from 'react'
import axios from 'axios';
import { useState } from 'react';
import { useHistory } from "react-router-dom";
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
let history = useHistory()
const onSubmit = (e) => {
e.preventDefault();
const getIn = {
"login":email,
"password":password,
};
axios
.post('https://js-test-api.etnetera.cz/api/v1/login', getIn,
{
headers: {
'content-type': 'application/json',
}
}).then((res) => {
console.log(res.data);
history.push("/phones");
})
.catch((error) => console.log(error));
};
return (
<div>
<form >
<label>email</label> <input value={email}
onChange={(e) => setEmail(e.target.value)} type="text"/>
<label>password</label> <input type="text" value={password}
onChange={(e) => setPassword(e.target.value)}/>
<button onClick={onSubmit}>login</button>
</form>
</div>
)
}
export default Login
As you said, it's the get api expecting an auth token. You need to first login using the login endpoint and get the token from the login response. Post that you can pass that auth token in each get request in the header.
You can update your context file like so :-
import React, { useState, useEffect, createContext
} from 'react';
import axios from 'axios';
export const MobileContext = createContext({
login:()=>{},
mobiles: [],
setMobiles: () => {},
updateMobiles: () => {},
});
export default function MobileProvider(props) {
const [mobiles, setMobiles] = useState([]);
const [token,setToken] = useState(null);
const login = (username,password) =>{
// do the axios post thing - take ref from docs you shared for request body
// get the token from the response and you can set it in the state
setToken(token);
}
const updateMobiles = (id) => {
//Update this get request with proper header value using token state as well.
axios
.get('https://js-test-api.etnetera.cz/api/v1/phones')
.then((res) => setMobiles(res.data));
};
useEffect(() => {
//Update this get request with proper header value using token state as well.
axios
.get('https://js-test-api.etnetera.cz/api/v1/phones')
.then((res) => setMobiles(res.data));
}, [] );
return (
<MobileContext.Provider value={{ login,mobiles, setMobiles, updateMobiles }}>
{props.children}
</MobileContext.Provider>
);
}
Note - How you wan't to use that login function is upto you but generally its through form submission. In your case I think it's an auto login inside an useEffect, so don't hardcode username and password in the UI. You can use environment variables for the same.
Related
I am trying to fetch some data from a Football API. For example, the countries that are provided by the API. I could get the data in the console.log but as soon as I try to render it, I get this error : Uncaught TypeError: (0 , axios__WEBPACK_IMPORTED_MODULE_0__.useState) is not a function or its return value is not iterable.
Here is the code :
import axios from 'axios';
import './App.css';
import { useState } from 'axios';
import React from 'react';
function Ui() {
const [country, setCountry] = useState('')
const options = {
method: 'GET',
url: 'https://api-football-v1.p.rapidapi.com/v3/countries',
headers: {
'X-RapidAPI-Key': '',
'X-RapidAPI-Host': 'api-football-v1.p.rapidapi.com'
}
};
const getCountry = () => {
axios.request(options).then(function (res) {
setCountry(res.data.response);
}).catch(function (error) {
console.error(error);
})
}
return (
<>
<button onClick={getCountry}>Get Country</button>
<p>{country}</p>
</>
);
}
export default Ui;
You're trying to import useState from Axios instead of React.Change it to this: import React, {useState} from 'react'; and for Axios: import axios from 'axios'; You're also importing axios twice.
You shouldn't add the api-key here either. You might want to look at your code again.
Example to print all the country names (put in the API key where it says API-KEY):
import axios from "axios";
import React, { useState, useEffect } from "react";
function Ui() {
const [country, setCountry] = useState([]);
useEffect(() => {
axios
.get("https://api-football-v1.p.rapidapi.com/v3/countries", {
headers: {
"X-RapidAPI-Key": "API-KEY"
}
})
.then((res) => {
setCountry(res.data.response);
})
.catch((err) => console.log(err));
}, []);
return (
<>
<button>Get Country</button>
<div>
{country.map((data, i) => (
<h1>{data.name}</h1>
))}
</div>
</>
);
}
export default Ui;
Here are 3 basic components searchBar which refers to the search bar form and, the searchPage component which displays the search results, and of course, the app component which contains them all.
mechanism:
the user submits an input in the searchBar component, the
handleSubmit function gets fired, which changes the state of
setSearchedProducts to the input value, by useContext AND
getting pushed to the ("/SearchPage") by history.push() .
import {useState, useContext } from "react";
import { useHistory } from "react-router-dom";
import { LocaleContext } from "../../../LocaleContext";
const SearchBar = () => {
const history = useHistory();
const {setSearchedTerm} = useContext(LocaleContext);
const handleSubmit = (SearchTerm) => {
setSearchedProducts(SearchTerm)
history.push("/SearchPage");
}
return (
<form>
<input onSubmit={(e) => handleSubmit(e.target.value)}>
</input>
<button type="submit">Submit</button>
</form>
)
}
export default SearchBar
the value gets sent to the app component by react context and
the state gets set to the value while still pushing to the
("/searchPage").
import { useState, useMemo } from "react";
import { searchBar, searchPage } from "./components";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import {LocaleContext} from "./LocaleContext"
const App = () => {
const [searchedTerm, setSearchedTerm] = useState("");
const providerValue = useMemo(() => ({searchedTerm, setSearchedTerm}),
[searchedTerm, setSearchedTerm])
return (
<Router>
<LocaleContext.Provider value={providerValue}>
<searchBar />
<Switch>
<Route exact path="/SearchPage">
<SearchPage />
</Route>
</Switch>
</LocaleContext.Provider>
</Router>
);
}
export default (App);
displaying the searchPage component, which gets the state value
by using useContext, and with useEffect, the fetchProducts()
function gets fired, that fetches a set of products based on the
state value.
import {useState, useEffect, useContext} from 'react';
import { LocaleContext } from "../../LocaleContext";
const SearchPage = ({}) => {
const [products, setProducts] = useState([]);
const {searchedTerm} = useContext(LocaleContext);
const fetchProducts = (term) => {
setLoading(true);
const url = new URL(
"https://example/products"
);
let params = {
"query": term
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
let headers = {
"Accept": "application/json",
"Content-Type": "application/json",
};
fetch(url, {
method: "GET",
headers: headers,
})
.then(response => response.json())
.then(json => {
setProducts(json);
});
}
useEffect(() => {
fetchProducts(searchedProducts)
}, [])
return (
{
products.map(product => (
<div>
{product.name}
</div>
))
}
)
}
export default SearchPage
Issues:
when the router changes to the ("/searchPage") component state value get lost, meaning it returns to "" value. ?
lesser problem, if the user sends an empty string (" "), and the API needs a value or it will give an error, what is the solution to that?
is there a way of keeping the value after reloading the page?
import {createContext} from "react";
export const LocaleContext = createContext(null);
this is the localeContext component if needed.
you have to add e.preventDefault() in your onSubmit handler. Otherwise you're getting a page reload which causes a state loss.
I noticed "setSearchedProducts" & "setSearchedTerm" should be the same in your code below. This might be your issue!
const SearchBar = () => {
...
const {setSearchedTerm} = useContext(LocaleContext);
const handleSubmit = (SearchTerm) => {
setSearchedProducts(SearchTerm)
...
}
So I have a basic app running. There's just one button which is used to login with google and I want to store that user's info in firestore and then I want to map through every user and display every single user's which are signed in, in my app. Firebase auth is complete but I don't know how to store that user's auth info.Also I am using useContext to pass authorized users info.Here's the code:
Main Entry Level App.js
import React, { useEffect, useMemo, useState } from "react";
import Login from "./components/Login";
import { User } from "./components/User";
import db, { auth } from "./firebase";
import { UserContext } from "./Contexts/UserContext";
const App = () => {
const [user, setUser] = useState([]);
const value = useMemo(() => ({ user, setUser }), [user, setUser]);
useEffect(() => {
auth.onAuthStateChanged((user) => {
// console.log(user);
setUser(user);
});
}, []);
return (
<UserContext.Provider value={value}>
{user ? <User /> : <Login />}
</UserContext.Provider>
);
};
export default App;
User.js Component
import React, { useContext } from "react";
import { UserContext } from "../Contexts/UserContext";
import db, { auth } from "../firebase";
export const User = () => {
const { user } = useContext(UserContext);
return (
<>
<img src={user.photoURL} alt={user.displayName} />
<div>{user.displayName}</div>
<div>{user.email}</div>
<button onClick={() => auth.signOut()}>Log Out</button>
</>
);
};
Login.js
import React, { useContext, useEffect } from "react";
import { UserContext } from "../Contexts/UserContext";
import { auth, signInWithGoogle } from "../firebase";
const Login = () => {
const { setUser } = useContext(UserContext);
useEffect(() => {
auth.onAuthStateChanged((user) => {
console.log(user);
setUser(user);
});
});
return (
<>
<div style={{ textAlign: "center" }}>
<button onClick={signInWithGoogle}>
<img
src="https://img.icons8.com/ios-filled/20/000000/google-logo.png"
alt="google icon"
/>
<span> Continue with Google</span>
</button>
</div>
</>
);
};
export default Login;
signInWithGoogle
export const signInWithGoogle = () => {
auth.signInWithPopup(provider).catch((err) => alert(err.message));
};
You should use a then() block in your signInWithGoogle() function, as follows:
export const signInWithGoogle = () => {
auth.signInWithPopup(provider)
.then((result) => {
const userId = result.user.uid;
// Create a doc in a users collection
// It's up to you to build theJavaScript objec to pass to the set() methood
firestore.collection("users").doc(userId).set( {foo: bar, bar: foo} );
})
.catch((err) => alert(err.message));
};
More details in the doc.
I am making a simple app for lending phones with this api but I am unable to access the phone items as the request requires auth token. So I am trying to output this
import React from 'react'
import { MobileContext } from './MobileContext';
import { useContext } from 'react';
import Mobile from './Mobile';
import Navbar from './Navbar';
function MobileList() {
const { mobiles } = useContext(MobileContext);
return (
<div>
<Navbar/>
{mobiles.map((item) => (
<Mobile
vendor={item.vendor}
/>
))}
</div>
)
}
export default MobileList
but after correct login getting this without the phones
this is how my context api is set up but apparently I am unable to access the phones
import React, { useState, useEffect, createContext
} from 'react';
import axios from 'axios';
export const MobileContext = createContext({
mobiles: [],
setMobiles: () => {},
updateMobiles: () => {},
});
export default function MobileProvider(props) {
const [mobiles, setMobiles] = useState([]);
const updateMobiles = (id) => {
axios
.get('https://js-test-api.etnetera.cz/api/v1/phones')
.then((res) => setMobiles(res.data));
};
useEffect(() => {
axios
.get('https://js-test-api.etnetera.cz/api/v1/phones')
.then((res) => setMobiles(res.data));
}, [] );
return (
<MobileContext.Provider value={{ mobiles, setMobiles, updateMobiles}}>
{props.children}
</MobileContext.Provider>
);
}
then there is the login page you have to get through if you want to get to the phones page
import React from 'react'
import axios from 'axios';
import { useState } from 'react';
import { useHistory } from "react-router-dom";
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
let history = useHistory()
const onSubmit = (e) => {
e.preventDefault();
const getIn = {
"login":email,
"password":password,
};
axios
.post('https://js-test-api.etnetera.cz/api/v1/login', getIn,
{
headers: {
'content-type': 'application/json',
}
}).then((res) => {
console.log(res.data);
history.push("/phones");
})
.catch((error) => console.log(error));
};
return (
<div>
<form >
<label>email</label> <input value={email}
onChange={(e) => setEmail(e.target.value)} type="text"/>
<label>password</label> <input type="text" value={password}
onChange={(e) => setPassword(e.target.value)}/>
<button onClick={onSubmit}>login</button>
</form>
</div>
)
}
export default Login
apreciate any advice of how to pass the auth tokens as I have never done this here is the full code
The idea of tokens is that once a user successfully logs in (the POST request), he receives a token from the server (the login's response).
Once a user has his token (stored preferably in a browser's localStorage, to keep it regardless the browser's refresh), he passes this token along with every request to the server that needs authentication.
I.e., for JWT tokens that header is:
Authorization: Bearer [token]
I am working on an authentication system using react at front. I am storing token which comes from my backend server to localStorage and i want user to redirect to dashboard page when there is a token present in localStorage. Every time i login using correct credentials i get token but not redirecting to dashboard page. But when i change route in url it works. I am using react context api.
AuthContext.js
import { createContext } from "react";
const AuthContext = createContext();
export default AuthContext;
AuthState.js
import React, { useReducer, useState } from "react";
import AuthContext from "./AuthContext";
import { SUCCESS_LOGIN } from "../types";
import AuthReducers from "./AuthReducers";
import Axios from "axios";
const AuthState = ({ children }) => {
//setting up initial state for authcontext
const initialState = {
userAuth: null,
userLoading: false,
token: localStorage.getItem("token"),
errors: null,
};
const [state, dispatch] = useReducer(AuthReducers, initialState);
//logging user in
const loginUser = async (userData) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
//posting to api
const res = await Axios.post("/api/user/login", userData, config);
console.log(res.data);
dispatch({
type: SUCCESS_LOGIN,
payload: res.data,
});
} catch (error) {
console.log(error.response);
}
};
return (
<AuthContext.Provider
value={{
userAuth: state.userAuth,
errors: state.errors,
token: state.token,
loginUser,
}}
>
{children}
</AuthContext.Provider>
);
};
export default AuthState;
AuthReducers.js
import { SUCCESS_LOGIN } from "../types";
export default (state, action) => {
switch (action.type) {
case SUCCESS_LOGIN:
const token = action.payload.token;
localStorage.setItem("token", token);
return {
...state,
userAuth: true,
userLoading: true,
errors: null,
token: localStorage.getItem("token"),
};
default:
return state;
}
};
Login.js
import React, { useState, useContext } from "react";
import { useHistory } from "react-router-dom";
import { Button, Form, FormGroup, Label, Input, FormText } from "reactstrap";
import styles from "./login.module.css";
import AuthContext from "../../context/AuthContext/AuthContext";
const Login = (props) => {
//grabbing states from authContext
const { loginUser, userAuth } = useContext(AuthContext);
let history = useHistory();
const [credentials, setCredentials] = useState({
email: "",
password: "",
});
//pulling email and password from state
const { email, password } = credentials;
//method to handle changes on input fields
const handleChange = (e) => {
const { name, value } = e.target;
setCredentials({
...credentials,
[name]: value,
});
};
//method to handle login when user submits the form
const handleLogin = (e) => {
e.preventDefault();
loginUser({ email, password });
console.log(userAuth);
if (userAuth) {
history.push("/dashboard");
}
};
return (
<Form onSubmit={handleLogin}>
<FormGroup>
<Label for="email">Email</Label>
<Input
type="email"
name="email"
value={email}
placeholder="Enter your email"
onChange={handleChange}
/>
</FormGroup>
<FormGroup>
<Label for="password">Password</Label>
<Input
type="password"
name="password"
value={password}
placeholder="Enter password"
onChange={handleChange}
/>
</FormGroup>
<Button className={styles.loginBtn}>Submit</Button>
</Form>
);
};
export default Login;
PrivateRoute.js
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import AuthContext from "../../context/AuthContext/AuthContext";
const PrivateRoute = ({ component: Component, ...rest }) => {
const { token, userAuth } = useContext(AuthContext);
return (
<div>
<Route
{...rest}
render={(props) =>
token ? <Component {...props} /> : <Redirect to="/" />
}
/>
</div>
);
};
export default PrivateRoute;
You need to do this in Login.js.
useEffect(() => {
if (userAuth) {
history.push("/dashboard");
}
},[userAuth,history])
Its happening because when you do handleLogin click functionality you dont have userAuth at that time as true(its taking previous value). Because context update change is not available in handleLogin function . Instead track userAuth in useEffect
If you are trying to redirect the user after successful login via your handleLogin() function, it won't work because of this:
if (userAuth) {
history.push("/dashboard");
}
The above will not run, because userAuth won't change until the component re-renders, after which, the function will have finished executing. You should either return something from your loginUser() action, and redirect based on its return of a successful "login", or implement conditional rendering inside of the Login component, like so:
return userAuth
? <Redirect to="/dashboard" /> // redirect if userAuth == true
: (
// your Login JSX // if userAuth == false, render Login form
)