How to pass auth token that I can access API?(react question) - reactjs

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]

Related

React.js Axios API render error on request

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;

How to save states between route change in React

I have a simple application that shows the list of local hotels. Each list item has a <Link/> that redirects to another component, which displays the location on the map for that specific hotel. When switching routes, it seems that the <ProductList/> component gets destroyed and so do all the states in it. So every time when it makes new API calls and re-renders. I tried to save in local storage on each componentWillUnmount and retrieve it in useEffect() so that I can make API calls conditionally, and it works but sometimes doesn't work.
import React, { useState, useEffect} from "react";
import ProductItem from "../Components/ProductItem";
import axios from "axios";
const ProductList = () => {
const [hotelList, setHotelList] = useState([]);
// Get user location by IP
const getCurrentLocation = () => {
return fetch("https://ipinfo.io/json?token=MyToken").then(
(response) => response.json()
);
};
// Get list of hotels in specific location
const getHotelsInLocation = (destInfo) => {
console.log('destInfo is: ', destInfo)
const options = {
method: "GET",
url: "https://booking-com.p.rapidapi.com/v1/hotels/search",
params: {
checkout_date: "2022-10-01",
units: "metric",
dest_id: destInfo.destId,
dest_type: destInfo.destType,
locale: "en-gb",
adults_number: 2,
order_by: "popularity",
filter_by_currency: "USD",
checkin_date: "2022-09-30",
room_number: 1,
},
headers: {
"X-RapidAPI-Host": "booking-com.p.rapidapi.com",
"X-RapidAPI-Key": "MyApiKey",
},
};
axios
.request(options)
.then(function (response) {
console.log(response.data.result);
setHotelList(response.data.result);
})
.catch(function (error) {
console.error(error);
});
};
useEffect(() => {
getCurrentLocation().then((currentLocation) => {
console.log("Current city ", currentLocation.city);
const options = {
method: "GET",
url: "https://booking-com.p.rapidapi.com/v1/hotels/locations",
params: { locale: "en-gb", name: currentLocation.city },
headers: {
"X-RapidAPI-Host": "booking-com.p.rapidapi.com",
"X-RapidAPI-Key":
"MyApiKey",
},
};
axios
.request(options)
.then(function (response) {
console.log(response.data);
let destId = response.data[0].dest_id;
let destType = response.data[0].dest_type;
const destInfo = { destId, destType };
getHotelsInLocation(destInfo);
})
.catch(function (error) {
console.error(error);
});
});
}, []);
return (
<>
{hotelList.map((hotel) => (
<ProductItem key={hotel.hotel_id} hotel={hotel} />
))}
</>
);
};
export default ProductList;
How could I do so when coming back to <ProductList/> component, it doesn't make new API calls but just display the hotelList from the previous call.
In this case, You need to keep the datas in the centralized store. For this, you have 2 options. One is using context api which is default react feature. Another one is using redux which is seperate package.
My opinion is, you can go with context api.
A simple example of context api is given below,
Filecontext.jsx
import { createContext } from 'react'
export const Filecontext = createContext({});
Formcomponent.jsx
import { Filecontext } from '../Contexts/Filecontext';
import { useContext } from 'react'
export default function Formcomponent() {
const { setName, setEmail, setMobileno, showAlert } = useContext(Filecontext)
return (
<>
<div className="form-group">
<label>Name : </label>
<input type="text" onChange={(e) => { setName(e.target.value) }} />
</div>
<div className="form-group">
<label>Email : </label>
<input type="email" onChange={(e) => { setEmail(e.target.value) }} />
</div>
<div className="form-group">
<label>Mobile No : </label>
<input type="number" onChange={(e) => { setMobileno(e.target.value) }} />
</div>
<div className="form-group">
<input type="submit" value="submit" onClick={() => { showAlert() }} />
</div>
</>
)
}
Listcomponent.jsx
import { Filecontext } from '../Contexts/Filecontext';
import { useContext } from 'react'
export default function Listcomponent() {
const { name, email, mobileno } = useContext(Filecontext)
return (
<>
<p>Name :</p>{name}
<p>Email :</p>{email}
<p>Mobile No :</p>{mobileno}
</>
)
}
App.js
import logo from './logo.svg';
import './App.css';
import Formcomponent from './Components/Formcomponent';
import Listcomponent from './Components/Listcomponent';
import { Filecontext } from './Contexts/Filecontext';
import { useState } from 'react'
function App() {
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [mobileno, setMobileno] = useState("")
return (
<div className="App">
<Filecontext.Provider value={{ name, setName, email, setEmail, mobileno, setMobileno, showAlert }}>
<Formcomponent />
<Listcomponent />
</Filecontext.Provider>
</div>
);
}
export default App;
From the above example, The Formcomponent can be displayed in different route and Listcomponent can be displayed in different route. But even then, the datas can be retained with the help of context.
Although caching with useEffect is possible, My recommendation is to consider using one of the query caching libraries such as:
RTK Query
react-query
Apollo (if you're using GraphQL)
From my experience if you're already using redux toolkit RTK Query will be the fit for you.
The short answer is that
You need the list of hotels in a global state instead of local.
Use context / redux along with a cache policy in API calls and track your state changes.
Skip the API calls based on your application logic and memoize the query result when needed hence in effect memoize the global state.
First set up a global state manager. you can use redux, or context API. I prefer to use zustand.
second config local storage to set visited component data.
third when you navigate to a new component retrieve a list of hotels visited and check if the ID exists or not. if yes no need for an API call and if no call it and save to zustand again.

Why cannot I grab items from api/context?(react question)

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.

How to redirect to protected route after successful login?

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
)

Getting 401 (Unauthorized) hitting ticketmaster api after logging in

I am building an artist search React app that hits the Ticketmaster API and should return the results before logging and after logging in.
I am getting 401(Unauthorized) after logging in.
search.js
import React, {Component} from 'react';
import axios from 'axios';
import {withRouter} from 'react-router-dom';
import {Form, FormControl, Button} from 'react-bootstrap';
import './style.css';
class SearchField extends Component {
state = {
search: ""
};
handleChange = (event) => {
const {name, value} = event.target;
this.setState({[name]: value.toLowerCase()});
};
apiCall = () => {
const ticketmasterURL = "https://app.ticketmaster.com/discovery/v2/events.json?keyword=";
const searchKey = process.env.REACT_APP_TM_KEY;
const term = this.state.search.split(" ").join("+");
axios.get("https://cors-anywhere.herokuapp.com/" + ticketmasterURL + term + "&apikey=" + searchKey)
.then(res => {
this.props.history.push({
pathname: "/events/",
search: `?${this.state.search.split(" ").join("+")}`,
state: {data: JSON.stringify(res.data._embedded.events)}
})
})
.catch(err => console.log(err));
};
handleSubmit = (event) => {
event.preventDefault();
this.apiCall();
//set the redirect state to true
this.setState({redirect: true});
};
render(){
return (
<div className="search-container">
<Form onSubmit={this.handleSubmit}>
<Form.Group>
<FormControl
type="text"
placeholder="Search"
name="search"
value={this.state.search}
onChange={this.handleChange}
/>
<div className="searchbtn-container">
<Button type="submit">Submit</Button>
</div>
</Form.Group>
</Form>
</div>
)
}
}
export default withRouter(SearchField);
app.js
import setAuthToken from './_helpers/setAuthToken';
import { setCurrentUser, logoutUser } from "./actions/authAction";
if (localStorage.jwtToken) {
const token = localStorage.jwtToken;
setAuthToken(token);
const decoded = jwt_decode(token);
Store.dispatch(setCurrentUser(decoded));
const currentTime = Date.now() / 1000;
if (decoded.exp < currentTime) {
Store.dispatch(logoutUser());
window.location.href = "/login";
}
}
setAuthToken.js
import axios from 'axios';
const setAuthToken = (token) => {
if (token) {
axios.defaults.headers.common['Authorization'] = token;
} else {
delete axios.defaults.headers.common["Authorization"];
}
};
export default setAuthToken;
I think I localized the issue to setAuthToken function in app.js because it works without it but am not sure.
You need to add the type of the token, so:
axios.defaults.headers.common['Authorization'] = "Bearer " + token;
https://www.rfc-editor.org/rfc/rfc6749 (Section 7.1)

Resources