How to save result of /api/page to state - react-hooks

I'm trying to authenticate to Xero's API. I get a 'code' which is then exchanged for an access_token. I'm still new to NextJS and React so I'm likely not thinking about this correctly.
The code I have results in the right data being returned, however I don't know how to use it effectively in the rest of the app. I couldn't figure out how to use NextAuth in a custom provider so tried to roll my own.
The user clicks the button 'Connect to Xero' - this is a href to initiate the process and takes the user to Xero to login in the browser. User authenticates. Xero calls the callback
the callback at /api/callback responds
I extract the 'code' and then make the subsequent request to Xero to swap it for an access token.
This is where I get stuck - because the initial action is a href redirect, I'm not sure how to get the end API result back into my code as state/something usable. In effect Xero is calling the api/callback page and that's where the user is left.
I've tried to put useState hooks into the api/callback however that breaks the rule of hooks.
Any pointers greatly appreciated.
Code;
pages/index.js
import React from 'react'
import Layout from '../components/Layout'
import TopNav from '../components/TopNav'
import Link from 'next/link';
export default function Main(props) {
const test = props.URL
return (
<>
<Layout>
<TopNav name="Main page"/>
<p>this is the main page</p>
<Link href={test} passHref={true}>
<button className=' w-40 border rounded-md py-3 px-3 flex items-center justify-center text-sm font-medium sm:flex-1'>
Connect to Xero
</button>
</Link>
</Layout>
</>
)
}
export async function getStaticProps() {
const XeroAuthURL = "https://login.xero.com/identity/connect/authorize?response_type=code&client_id="
const client_ID = process.env.XERO_CLIENT_ID
const redirect_uri = process.env.XERO_REDIRECT_URI
const scope = "offline_access openid profile email accounting.settings"
const URL = `${XeroAuthURL}${client_ID}&redirect_uri=${redirect_uri}&scope=${scope}`
return {
props: {
URL: URL
},
};
}
/api/callback.js
import axios from "axios"
const qs = require('qs');
export default async function callback(req, res) {
try {
//callback from Xero will deliver the code, scope + state (if given)
//https://developer.xero.com/documentation/guides/oauth2/auth-flow/#2-users-are-redirected-back-to-you-with-a-code
console.log(`REQ = ${JSON.stringify(req.query)}`)
//exchange code for tokens - https://developer.xero.com/documentation/guides/oauth2/auth-flow/#3-exchange-the-code
var data = qs.stringify({
'code': req.query.code,
'grant_type': 'authorization_code',
'redirect_uri': 'http://localhost:3000/api/callback'
});
var config = {
method: 'post',
url: 'https://identity.xero.com/connect/token',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic **put your authorisation result here**'
},
data : data
};
try {
const response = await axios(config)
//response has the data I want to put into State
console.log(JSON.stringify(response.data));
//save data off here somehow???
//tried redirecting but unsure if can pass the result
res.redirect(307, '/')
} catch (error) {
console.error(error)
res.status(error.status || 500).end(error.message)
}
} catch (error) {
console.error(error)
res.status(error.status || 500).end(error.message)
}
}

Added a not-secure cookie that I can use while testing. Do not use this in production as the cookie is not httpOnly and not secure.
import axios from "axios"
import Cookies from 'cookies'
const qs = require('qs');
export default async function callback(req, res) {
const cookies = new Cookies(req,res)
try {
var data = qs.stringify({
'code': req.query.code,
'grant_type': 'authorization_code',
'redirect_uri': 'http://localhost:3000/api/callback'
});
var config = {
method: 'post',
url: 'https://identity.xero.com/connect/token',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic **YOUR AUTH CODE HERE**'
},
data : data
};
try {
var response = await axios(config)
response.data.expires_at = Date.now() + response.data.expires_in*1000
//save data off
//TO DO - THIS IS REALLY BAD - ONLY USE THIS TEMPORARILY UNTIL HAVE GOT PERMSTORAGE SETUP
cookies.set('myCookieName', JSON.stringify(response.data), {
secure: false,
httpOnly: false
}
)
res.redirect(307, '/')
//return ({ data: response.data })
} catch (error) {
console.error(error)
res.status(error.status || 500).end(error.message)
}
} catch (error) {
console.error(error)
res.status(error.status || 500).end(error.message)
}
}
Then in the index;
import React from 'react'
import Layout from '../components/Layout'
import TopNav from '../components/TopNav'
import Link from 'next/link';
import { getCookie } from 'cookies-next';
export default function Main(props) {
//check for cookie
//TO DO THIS IS REALLY BAD; CHANGE WHEN GET PERM STORAGE ORGANISED
const cookie = getCookie('myCookieName');
const URL = props.URL
return (
<>
<Layout>
<TopNav name="Main page"/>
<p>this is the main page</p>
<Link href={URL} passHref={true}>
<button className=' w-40 border rounded-md py-3 px-3 flex items-center justify-center text-sm font-medium sm:flex-1'>
Connect to Xero
</button>
</Link>
<p>{cookie ? cookie : 'waiting for cookie...'}</p>
</Layout>
</>
)
}
export async function getStaticProps() {
const XeroAuthURL = "https://login.xero.com/identity/connect/authorize?response_type=code&client_id="
const client_ID = process.env.XERO_CLIENT_ID
const redirect_uri = process.env.XERO_REDIRECT_URI
const scope = "offline_access openid profile email accounting.settings"
//console.log(`URL - ${XeroAuthURL}${client_ID}&redirect_uri=${redirect_uri}&scope=${scope}`)
const URL = `${XeroAuthURL}${client_ID}&redirect_uri=${redirect_uri}&scope=${scope}`
return {
props: {
URL: URL,
},
};
}

Related

Images are not fetchig from next js api and returning 500 error

So basicaly I want to fetch the data from Laravel server to next js api server and this seems to be working fine for the other atributes like title but the image is not fetching and return error 500 and 'ECONNREFUSED'
Here is the code for courses inside the api colder pages/api/courses.ts
import { NextApiRequest, NextApiResponse } from "next";
import axios from "axios";
import Course from "../../models/course";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const courses = await Course.fetchAll();
res.status(200).json(courses);
}
Code for the api route on laravel
Route::get('/courses', function () {
$courses = Courses::all();
// allow to remote access
header('Access-Control-Allow-Origin: *');
return response()->json(['courses' => $courses], 200);
});
Code for the API call
const fetchCourses = async () => {
const courses = await axios.get("http://localhost:3000/api/courses", {
headers: {
"Content-Type": "application/json",
// allow cors
"Access-Control-Allow-Origin": "*",
},
});
setCourses(courses.data);
};
The image appears like that
The error that appears on image url
I fixed it by replacing the NextImage tag with normal image tag and it worked

useState set function doesn't work after fetch

import React, {useState, useEffect, useContext} from 'react'
import AuthContext from '../context/AuthContext'
const HomePage = () => {
const [note,setNote] = useState([])
let {authTokens} = useContext(AuthContext)
useEffect(()=>{
getNotes()
},[])
let getNotes = async () => {
let response = await fetch("http://127.0.0.1:8000/api/notes",{
method:"GET",
headers:{
"Content-type":"application/json",
"Authorization":"Bearer "+String(authTokens.access)
}
})
let data =await response.json();
console.log("data i have been wait",data)
console.log("Data in it",note)
}
return (
<div>
<p>You are logged in to home paage</p>
{/* {notes.map(note =>{
<li >{note.body}</li>
})} */}
</div>
)
}
export default HomePage
I got my data from django backend and try to show in react using map but it doesnt map.
I can get the data but when I am trying to setNote(data) it doesn't not work.
Sometimes it works but still cant map it. I cant refresh map func and put data on it
and I don't get any error.[console.log like that][1]
console.log("data i have been wait",data)
setNote(data)
console.log("Data in it",note)```
[1]: https://i.stack.imgur.com/h17QF.png
In the case the setState works correctly but you can't map the data, I suggest that the problem comes from the fact that you put brackets inside your map but no return.
Try this instead:
{notes.map(note =>
{
return(<li >{note.body}</li>);
}
)}
or with arrow function:
{notes.map(note => <li>{note.body}</li>)}

How to get auth token automatically in laravel and vue?

I am using laravel passport for authentication in my laravel and vue.js ecommerce project.
After successful login, I want to redirect user to his/her dashboard.
Here is the vue dashboard page:
<template>
<div class="content-center">
<h1>Dashboard my account</h1>
<p>
{{userData.name}}
</p>
</div>
</template>
<script>
import Axios from "axios";
export default {
data() {
return {
userData: "",
authToken: ""
}
},
async beforeMount() {
let res = await Axios.get("http://localhost:8000/api/user-details", {
headers: {
Authorization: "Bearer " + this.authToken,
Accept: 'application/json'
},
});
this.userData = res.data;
// let token = await Axios.get("http://localhost:8000/api/user-login")
// this.authToken = res.data.data.auth_token
//let res = await Axios.get("http://localhost:8000/api/user-details");
},
};
</script>
Everytime I login to different user accounts, I have to set the value of authToken manually copy and pasting from Postman. I want to set this token automatically when a user logs in. How can I do this ?
Here is my api controller:
class AuthApiController extends Controller
{
public function userDetails(){
return auth()->user();
}
public function login(Request $request){
$user = User::where('email',$request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response()->json([
'success'=>false,
'data'=>[],
'message'=>'Login failed',
'errors'=>[]
]);
}else{
return response()->json([
'success'=>true,
'data'=>['user'=> $user, 'auth_token' => $user->createToken('AuthToken')->accessToken],
'message'=>'Login success',
'errors'=>[]
]);
}
}
Updates:
dashboard.vue
<template>
<div class="content-center">
<h1>Dashboard my account</h1>
<p>
{{userData.name}}
</p>
</div>
</template>
<script>
import Axios from "axios";
export default {
data() {
return {
userData: "",
authToken: ""
}
},
async beforeMount() {
let res = await Axios.get("http://localhost:8000/api/user-details", {
headers: {
Authorization: "Bearer " + this.authToken,
Accept: 'application/json'
},
});
this.userData = res.data;
let token = await $api.get("http://localhost:8000/api/user-login")
this.authToken = res.data.data.auth_token
},
};
</script>
Picture:
enter image description here
What should I write to import api.js ?
import $api from ./../api.js or anything else ?
Well, you can store your token in LocalStorage. And whenever you request just get it from the local storage and pass it to the request header.
If you are using Axios then you can use interceptors and just intercept your every request and pass token in the header.
Step 1.
Create a file called api.js or you can call it whatever you want.
Step 2.
Create an Axios instance in the api.js file.
import axios from 'axios';
// Put your backend url here
export const API_URL = `http://localhost:5000/api`
const $api = axios.create({
withCredentials: true,
baseURL: API_URL
})
$api.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`
return config;
})
export default $api;
Step 3: Where ever you are using Axios use this exported instance so in your component you would do like this:
const userdata = await $api.get("http://localhost:8000/api/user-details");
Here you can see, we are using the $api Axios instance which we created in the api.js file instead of Axios direct.
Add also don't forget to store your token in your local storage when you getting that.
localStorage.setItem('token', "Your token goes here...");
I hope this will give you an idea.
This way, Token will be sent with every request automatically, if it exists in the localStorage.
UPDATE:
<template>
<div class="content-center">
<h1>Dashboard my account</h1>
<p>
{{userData.name}}
</p>
</div>
</template>
<script>
// import Axios from "axios";
import $api from 'put relative path of your api.js file'
export default {
data() {
return {
userData: "",
authToken: ""
}
},
async beforeMount() {
let res = await $api.get("/user-details");
this.userData = res.data;
let res = await $api.get("/user-login")
localStorage.setItem('token', res.data.data.auth_token);
},
};
</script>

How to forward data to next page with Apollo and NextJS

I'm working on a web app with NextJS, Apollo and React (hooks).
I have a form that asks the name of the visitor as the first step in a registration process.
When submitting the form the name will be saved in the Apollo cache and the visitor gets redirected to the next page.
import React, { useState } from 'react';
import Router , {useRouter} from 'next/router';
import { useApolloClient } from '#apollo/react-hooks';
const NameForm = props => {
const [name, setName] = useState("");
const client = useApolloClient();
const router = useRouter();
const handleSubmit = e => {
e.preventDefault();
if(!name) return;
client.writeData({ data: { name } });
router.push('/user/register');
}
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Naam</label>
<div>
<input type="text" id="name" name="name" value={name} onChange={e => setName(e.target.value)} />
<button type="submit" onClick={handleSubmit}>Get started</button>
</div>
</div>
</form>
)
}
export default NameForm;
The next page contains a more extensive form. When visitors come from the homepage, the name is already known and I want to get it back from the cache. I thought
import { gql } from 'apollo-boost';
import { useApolloClient } from '#apollo/react-hooks';
import AddUserForm from '../../components/forms/AddUserForm';
const GET_NAME = gql`
query GetName {
name #client
}`;
const AddUser = ({ name }) => (
<React.Fragment>
<AddUserForm name={name} />
</React.Fragment>
)
AddUser.getInitialProps = async ctx => {
const client = useApolloClient();
const name = await client.cache.readQuery({ query: GET_NAME });
return { name: name || '' };
}
export default AddUser;
I thought I could do this in the getInititialProps hooks are only allowed in the body of a functional component.
Because of the continuous development of next, react hooks and apollo I'm missing a tutorial/course about this and I find it difficult to find a right way to do this.
I hope someone here can help me further.
use apollo-client cache can lead you to some questions that really depends on the apollo-client's implementation and nextjs implementation.
If you open your app by entering the url to the browser address bar, Next.js will make requests (assuming the view need to fetch data) from server-side, then send to the client the rendered HTML.
Because apollo-client fetch then cache the data from server side, then the question is "Does Next.js send the apollo-client with its cache to client side for next request?"
You cannot sure about this unless you understand clearly about Next.js and apollo-client cache (about its implementation or how it works inside, if apollo cache data in-memory on server-side, you will fail if you go this way)
The answer is unsure because it depends on two stuffs at the same time. And maybe changed on the future!
So to deal with this problem, just use the Next.js way, it has designed a tunnel for data, it is the query on the url.
const NameForm = props => {
const [name, setName] = useState("");
const client = useApolloClient();
const router = useRouter();
const handleSubmit = e => {
e.preventDefault();
if(!name) return;
router.push(`/user/register?name=${name}`);
}
//render ...
}
import { useRouter } from 'next/router';
import AddUserForm from '../../components/forms/AddUserForm';
const AddUser = () => {
const router = useRouter();
return (
<React.Fragment>
<AddUserForm name={router.query.name} />
</React.Fragment>
)
}
export default AddUser;
If you want to send an object instead of a string?
const data = { name: "FoxeyeRinx", email: "foxeye.rinx#gmail.com" };
const base64 = btoa(JSON.stringify(data));
router.push(`/user/register?data=${base64}`);
const AddUser = () => {
const router = useRouter();
const base64 = router.query.data;
//decode base64 then parse it to js object
const data = JSON.parse(atob(base64));
return (
<React.Fragment>
<AddUserForm data={data}/>
</React.Fragment>
)
}
If you think the query is ugly and want to hide the query, use this guide: https://nextjs.org/learn/basics/clean-urls-with-dynamic-routing

react axios GET not working. Log out 'name.toUppercase is not a function'

I've created the most basic react app possible and am trying to do a simple GET request. It throws a TypeError and states, 'name.toUppercase is not a function'. I only have the one function. Any ideas what is causing this or how to debug?
import React, { Component } from 'react';
import axios from 'axios';
import logo from './logo.svg';
import './App.css';
class App extends Component {
state = {
order_id: Number,
order_date: '',
size: '',
crust: '',
toppings: [],
};
componentDidMount() {
return axios
.get('https://59b6v76zci.execute-api.us-west-
2.amazonaws.com/nr/example', {
method: 'GET',
mode: 'cors',
headers: 'Access-Control-Allow-Origin',
})
.then(response => this.setState({ order_id: response.order_id }))
.catch(err => console.log('err', err));
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
This is the what is returned in the console
err TypeError: name.toUpperCase is not a function
at processHeader (normalizeHeaderName.js:7)
at Object.forEach (utils.js:218)
at normalizeHeaderName (normalizeHeaderName.js:6)
at transformRequest (defaults.js:32)
at transform (transformData.js:16)
at Object.forEach (utils.js:224)
at transformData (transformData.js:15)
at dispatchRequest (dispatchRequest.js:37)
at <anonymous>
Try changing your axios request to this:
axios
.get('https://59b6v76zci.execute-api.us-west-2.amazonaws.com/nr/example', {
method: 'GET',
mode: 'cors',
headers: { 'Access-Control-Allow-Origin': true },
})
.then(response => this.setState({ order_id: response.data.order_id }))
.catch(err => console.log('err', err));
You got two things wrong - headers should be an object, and the data in the response should be under response.data.
Also, on a side-note, Access-Control-Allow-Origin header usually comes as a response header and not on the request.

Resources