best way to authenticate with SWR (firebase auth) - reactjs

I'm doing project with React , firebase auth social signin(google, github provider) and backend(spring boot)
I'm wondering how can i use useSWR for global state for google userData
Here's my Code This is Login page simply i coded
In this page, I fetch userData(email, nickname ,, etc) with header's idToken(received from firebase auth) and backend validates idToken and send me a response about userData
This is not problem I guess.. But
// import GithubLogin from '#src/components/GithubLogin';
import GoogleLogin from '#src/components/GoogleLogin';
import { auth, signOut } from '#src/service/firebase';
import { fetcherWithToken } from '#src/utils/fetcher';
import React, { useEffect, useState } from 'react';
import useSWR from 'swr';
const Login = () => {
const [token, setToken] = useState<string | undefined>('');
const { data: userData, error } = useSWR(['/api/user/me', token], fetcherWithToken);
useEffect(() => {
auth.onAuthStateChanged(async (firebaseUser) => {
const token = await firebaseUser?.getIdToken();
sessionStorage.setItem('user', token!);
setToken(token);
});
}, []);
return (
<div>
<button onClick={signOut}>Logout</button>
<h2>Login Page</h2>
<GoogleLogin />
</div>
);
};
export default Login;
Here's Code about fetcher using in useSWR parameter
export const fetcherWithToken = async (url: string, token: string) => {
await axios
.get(url, {
headers: {
Authorization: `Bearer ${token}`,
Content-Type: 'application/json',
},
withCredentials: true,
})
.then((res) => res.data)
.catch((err) => {
if (err) {
throw new Error('There is error on your site');
}
});
};
problem
I want to use userData from useSWR("/api/user/me", fetcherWithToken) in other page! (ex : Profile Page, header's Logout button visibility)
But for doing this, I have to pass idToken (Bearer ${token}) every single time i use useSWR for userData. const { data: userData, error } = useSWR(['/api/user/me', token], fetcherWithToken);
Like this.
What is the best way to use useSWR with header's token to use data in other pages too?
seriously, I'm considering using recoil, context api too.
but I don't want to.

You can make SWR calls reusable by wrapping them with a custom hook. See the SWR docs page below.
Make It Reusable
When building a web app, you might need to reuse the data in many
places of the UI. It is incredibly easy to create reusable data hooks
on top of SWR:
function useUser (id) {
const { data, error } = useSWR(`/api/user/${id}`, fetcher)
return {
user: data,
isLoading: !error && !data,
isError: error
}
}
And use it in your components:
function Avatar ({ id }) {
const { user, isLoading, isError } = useUser(id)
if (isLoading) return <Spinner />
if (isError) return <Error />
return <img src={user.avatar} />
}

Related

Twitter user search to display name, followers, following, among others using React.js

I am fairly new to react.js and I'm just trying my hands on a few random projects i can think of and one of them is to make a search engine in react.js that looks up users on twitter by simply entering their name in a search bar and the result will display their details using the Twitter API. However, when doing this i am hit with the follwoing errors in console:
Error ocuring
App.js:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const App = ({ username }) => {
const [user, setUser] = useState({});
const [tweets, setTweets] = useState({});
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const { data: user } = await axios.get(`https://api.twitter.com/1.1/users/show.json?screen_name=${username}`, {
method : "GET",
headers: {
Authorization: `Bearer <YOUR_TOKEN>`
}
});
const { data: tweets } = await axios.get(`https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=${username}&count=200`, {
method : "GET",
headers: {
Authorization: `Bearer <YOUR_TOKEN>`
}
});
setUser(user);
setTweets(tweets);
} catch (error) {
setError(error);
}
};
fetchData();
}, [username]);
if (error) {
return <div>An error occurred: {error.message}</div>;
}
return (
<div>
<h1>{user.name}</h1>
<p>Username: {user.screen_name}</p>
<p>Followers: {user.followers_count}</p>
<p>Following: {user.friends_count}</p>
<p>Bio: {user.description}</p>
<p>Date Joined: {user.created_at}</p>
<p>Pinned Tweet: {user.status ? user.status.text : 'No Pinned Tweet'}</p>
<p>Total Tweets: {user.statuses_count}</p>
</div>
);
};
export default App;
UPDATE
I have added the search box feature to the code but I'm still getting the same errors
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const TWITTER_API_URL = 'https://api.twitter.com/1.1/users/search.json';
function App() {
const [username, setUsername] = useState('');
const [userData, setUserData] = useState({});
const [searchValue, setSearchValue] = useState('');
useEffect(() => {
if (searchValue) {
axios
.get(TWITTER_API_URL, {
params: {
q: searchValue,
count: 1
},
headers: {
'Authorization': 'Bearer YOUR_BEARER_TOKEN'
}
})
.then(response => {
setUsername(response.data[0].screen_name);
})
.catch(error => {
console.log(error);
});
}
}, [searchValue]);
useEffect(() => {
if (username) {
axios
.get(`https://api.twitter.com/1.1/users/show.json?screen_name=${username}`, {
headers: {
'Authorization': 'Bearer YOUR_BEARER_TOKEN'
}
})
.then(response => {
setUserData(response.data);
})
.catch(error => {
console.log(error);
});
}
}, [username]);
return (
<div>
<input
type="text"
placeholder="Search by name"
value={searchValue}
onChange={e => setSearchValue(e.target.value)}
/>
{username && (
<div>
<p>Username: {username}</p>
<p>Name: {userData.name}</p>
<p>Following: {userData.friends_count}</p>
<p>Followers: {userData.followers_count}</p>
<p>Bio: {userData.description}</p>
<p>Date Joined: {userData.created_at}</p>
<p>Pinned Tweet: {userData.status.text}</p>
<p>Total Tweets: {userData.statuses_count}</p>
</div>
)}
</div>
);
}
export default App;
I would appreiciate any help given to resolve this issue. Thank you.
I would advise you to move the const fetchData = async () => { ... outside the useEffect() and may sound silly, but for the Authorization: Bearer <YOUR_TOKEN> have you changed the <YOUR_TOKEN> with your actual token? Lastly, you don't need method : "GET" because you are doing axios.get( ...
Please try this code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const App = ({ username }) => {
const [user, setUser] = useState({});
const [tweets, setTweets] = useState({});
const [error, setError] = useState(null);
const fetchData = async () => {
try {
const { data: user } = await axios.get(`https://api.twitter.com/1.1/users/show.json?screen_name=${username}`, {
headers: {
Authorization: `Bearer <YOUR_TOKEN>`
}
});
const { data: tweets } = await axios.get(`https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=${username}&count=200`, {
headers: {
Authorization: `Bearer <YOUR_TOKEN>`
}
});
setUser(user);
setTweets(tweets);
} catch (error) {
setError(error);
}
};
useEffect(() => {
fetchData();
}, [username]);
if (error) {
return <div>An error occurred: {error.message}</div>;
}
return (
<div>
<h1>{user.name}</h1>
<p>Username: {user.screen_name}</p>
<p>Followers: {user.followers_count}</p>
<p>Following: {user.friends_count}</p>
<p>Bio: {user.description}</p>
<p>Date Joined: {user.created_at}</p>
<p>Pinned Tweet: {user.status ? user.status.text : 'No Pinned Tweet'}</p>
<p>Total Tweets: {user.statuses_count}</p>
</div>
);
};
export default App;
The error message you are seeing is related to CORS (Cross-Origin Resource Sharing) and it is preventing your JavaScript code running on "http://localhost:3000" from making a request to "https://api.twitter.com".
CORS is a security feature implemented by web browsers that prevents a web page from making requests to a different domain than the one that served the web page.
To fix this issue, you will need to set up CORS headers on the server side. The "Access-Control-Allow-Origin" header is used to specify which domains are allowed to make requests to the server. You can set this header to "*" to allow any domain to make requests, or you can set it to the specific domain that your application is running on, "http://localhost:3000" in your case.
You can also use a proxy server in order to avoid CORS issue when trying to access twitter's API. This means that your react application will send the request to your server which will then forward it to twitter's API. It will then receive the response, and forward it back to your react application. This way your application will never be blocked by the CORS policy, as the request is coming from your server and not directly from your application.

How to make App run useQuery without having to refresh the page

I am making a React Application with a GraphQL backend. My code is as follows:
App.js
import { UserContext } from "./UserContext"
function App() {
const userQuery = useQuery(USER)
...
return {
<UserContext.Provider value={userQuery.data}>
LoginForm.js
import { UserContext } from "./UserContext"
function LoginForm() {
...
useEffect(() => {
if (result.data) {
const token = result.data.login.value
localStorage.setItem("user-token", token)
navigate("/")
}
}, [result.data])
index.js
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem("mouldy-peppers-user-token")
return {
headers: {
...headers,
authorization: token ? `bearer ${token}` : null,
},
}
})
But when the LoginForm navigates back to "/", the app component re-renders and user is null. Until I refresh the page, then it gets the logged-in user. The authorisation data sent with every graphQL query uses the local storage token. I would like to know how to get the query to use the new auth data without having to refresh the page.

Avoiding multiple API calls due to rerenders in React with Firebase auth

My web app uses Firebase Auth to handle user authentication along with a backend API, these are provided to the React app as a provider. The idea is that the backend API will verify the user's token when they sign in and deal with any custom claims / data that needs to be sent to the client.
The problem I'm having is that the provider is rerendering multiple times during the login flow, and each rerender is making an API call. I've managed to get the amount of rerenders down to two, but if I add other 'features' to the provider (e.g update the user's state if their access should change) then this adds to the amount of rerenders, sometimes exponentially, which leads me to suspect that the provider is rerendering as a result of setUserState being called, perhaps unnecessarily. Either way, it is clearly indicative of a problem somewhere in my code, which I've included below:
import {useState, useContext, createContext, useEffect} from 'react'
import {auth, provider} from './firebase'
import {getAuth, onAuthStateChanged, signInWithPopup, signOut} from 'firebase/auth'
import {api} from './axios'
export const UserContext = createContext(null)
export const useAuth = () => useContext(UserContext)
const verifyToken = token => {
return api({
method: 'post',
url: '/verifyToken',
headers: {token}
})
}
const UserProvider = props => {
const [userState, setUserState] = useState(null)
const [loading, setLoading] = useState(true)
const userSignedOut = async () => {
setLoading(true)
return await signOut(auth).then(() => {
setUserState(null)
}).catch(e => {
console.error(e)
}).finally(() => {
setLoading(false)
})
}
const userSignIn = async () => {
console.log('userSignIn')
setLoading(true)
try {
return await signInWithPopup(auth, provider)
} catch (e) {
console.error(e)
} finally {
if (!userState) {
setLoading(false)
}
}
}
const handleUserSignIn = async user => {
console.log('handleUserSignIn', user)
if (user && getAuth().currentUser) {
setLoading(true)
const idToken = await getAuth().currentUser.getIdToken(true)
const firebaseJWT = await getAuth().currentUser.getIdTokenResult()
if (!firebaseJWT) {throw(new Error('no jwt'))}
verifyToken(idToken).then(res => {
if (res.data.role !== firebaseJWT.claims.role) {
throw(new Error('role level claims mismatch'))
} else {
user.verifiedToken = res.data
console.log(`user ${user.uid} valid and token verified`, user)
setUserState(user)
setLoading(false)
}
}).catch(e => {
userSignedOut()
console.error('handleUserSignIn', e)
}).finally(() => {
setLoading(false)
})
} else {
console.log('no user')
userSignedOut()
}
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async user => {
console.log('onAuthStateChanged')
if (user?.uid && user.accessToken) {
await handleUserSignIn(user)
} else {
setUserState(null)
setLoading(false)
}
})
return () => unsubscribe()
}, [])
const value = {
signOut: userSignedOut, // for sign out button
signIn: userSignIn, // for sign in button
user: userState
}
return (
<UserContext.Provider value={value}>
{props.children}
</UserContext.Provider>
)
}
export default UserProvider
I tried to create a codesandbox for this, but unfortunately I was unable to simulate the Firebase auth functions.
The login flow is supposed to look like this:
The user signs in using their Google account.
The app is now loading, and the user cannot interact with it yet (they just get a spinning wheel).
The user's data and accessToken are sent to the backend API server. (function verifyToken)
The API server sets any custom claims and returns the verified token in its response, as well as the access that the user is supposed to have.
If the user's role / custom claims do not match what the API says they should be, the user is signed out.
The user's data is set using setUserState()
The app has finished loading, and the user is signed in.
I would like to avoid unnecessary rerenders and API calls and I suspect that some refactoring may be in order, but I'm not really sure what is best to do here.

React Chat Engine Loading

I create small chat application using react chat engine library. This is the UI image
But chats are not loading even I provide correct credentials. It's gives GET https://api.chatengine.io/chats/latest/25/ 403.There is no any error of code side. I used Firebase authentication for get logged user details. Using Auth context set user details. Those process are work correctly. I have no idea about this issue.
import React,{useRef,useEffect,useState} from "react";
import { useHistory } from "react-router-dom";
import {ChatEngine} from 'react-chat-engine';
import { auth } from "../firebase";
import {useAuth} from '../contexts/AuthContext';
import axios from "axios";
const Chats = () => {
const history = useHistory();
const {user}= useAuth();
const[loading,setLoading]=useState(true);
const getFile =async (url) =>{
const response = await fetch(url);
const data =await response.blob();
return new File([data],"userPhoto.jpg",{type:"image/jpeg"})
}
useEffect(()=>{
if(!user){
history.push('/')
return;
}
axios.get('https://api.chatengine.io/users/me',{
headers:{
"project-id":"8dc9fa0e-7ed4-40ec-a003-a7c76a11e7f7",
"user-name":user.email,
"user-secret":user.uid
}
})
.then(()=>{
setLoading(false);
})
.catch(()=>{
let formdata=new FormData();
formdata.append('email',user.email);
formdata.append('username',user.email);
formdata.append('secret',user.uid);
getFile(user.photoURL)
.then((avatar)=>{
formdata.append('avatar',avatar,avatar.name)
axios.post('https://api.chatengine.io/users/',
formdata,
{headers:{"private-key":"1445fb04-f7c9-42d2-b63b-3019a881d3a3"}}
).then(()=>setLoading(false))
.catch(error => console.log(error))
})
})
},[user,history])
const LogoutHandler =async()=>{
await auth.signOut();
history.push('/');
}
if(!user || loading) return 'Loading ...';
return (
<div className="chat-page">
<div className="nav-bar">
<div className="logo-tab">UEassyMessage</div>
<div className="logout-tab" onClick={LogoutHandler}>Logout</div>
</div>
<ChatEngine
height="calc(100vh-66px)"
projectID= '8dc9fa0e-7ed4-40ec-a003-a7c76a11e7f7'
userName={user.email}
userScret={user.uid}
/>
</div>
);
};
export default Chats;
Can anyone have idea about this issue?
Not sure if you still have this problem, but it should be userSecret instead of userScret. I had the same problem cause I write projectID with a lower case d.
I think you have a space in your projectID={} prop which is likely problematic. Make sure you take that away and try again.
Remove the space in your project ID and capitalize the ID
axios.get("https://api.chatengine.io/users/me", {
headers: {
projectID: "8dc9fa0e-7ed4-40ec-a003-a7c76a11e7f7",
"user-name": user.email,
"user-secret": user.uid,
},
});
if that doesn't work, Add the private key to your 'axios.get'
axios.get("https://api.chatengine.io/users/me", {
headers: {
projectID: "8dc9fa0e-7ed4-40ec-a003-a7c76a11e7f7",
"user-name": user.email,
"user-secret": user.uid,
"private-key": "*******",
},
});

How to access React Context outside of component?

Im trying to implement a function which gets called from within a functional React Component by a button.
It is supposed to delete a user from my own DB. But I need the access Token from Firebase to make this protected API call to my backend.
Now I'm serving the firebase instance from the Context API but I don't seem to be able to find a way to access this instance outside from a React Component.
I'm getting this error:
Line 10: Expected an assignment or function call and instead saw an expression
Am I aproaching this the wrong way?
import React from 'react';
import axios from 'axios';
import { PasswordForgetForm } from '../PasswordForgetForm/PasswordForgetForm';
import PasswordChangeForm from '../PasswordChangeForm/PasswordChangeForm';
import { AuthUserContext, withAuthorization } from '../../services/Session';
import { FirebaseContext } from '../../services/Firebase';
const deletUser = (authUser) => {
{
firebase => {
const token = firebase.doGetIdToken();
console.log(token);
axios.delete('/api/users/' + authUser.uid, {
headers: {
authorization: `Bearer ${token}`
}
})
.then(res => {
//this.props.history.push('/dashboard');
console.log(res);
})
}
}
}
const AccountPage = () => (
<AuthUserContext.Consumer>
{authUser => (
<div>
<h1>Account: {authUser.email}</h1>
<PasswordForgetForm />
<PasswordChangeForm />
<button type="button" onClick={() => deletUser(authUser)}>Delete Account</button>
</div>
)}
</AuthUserContext.Consumer>
);
const condition = authUser => !!authUser;
export default withAuthorization(condition)(AccountPage);
Thanks for any help!
The code is declaring an anonymous object, the inner syntax is incorrect
const deletUser = (authUser) => {
{//anonymous object
firebase => {//There is no key for the member of the object
const token = firebase.doGetIdToken();
console.log(token);
axios.delete('/api/users/' + authUser.uid, {
headers: {
authorization: `Bearer ${token}`
}
})
.then(res => {
//this.props.history.push('/dashboard');
console.log(res);
})
}
}//You never call or return anything of your object
}

Resources