Mern Data flow problems in ReactJS and Axios - reactjs

I am an aspiring react developer and I am having some issues with my app.
I am trying to scrape the New York times for an assignment I have, and I can get my data with a search to log server-side, but I cant seem to pass it back. I can get my data by pushing it to a MongoDB then querying it in a separate process from the front end, but I don't want to do that.
I want to pass the object back up the stack to the client side. Does anyone know how I might accomplish that?
here is some of my code.
my dir structure:
here is the client folder structure:
here is my Home.jsx file clientside in /pages:
import React, { Component } from 'react';
import { Container, Row, Column } from '../../components/BootstrapGrid';
import API from '../../utils/API'
import {Input, FormBtn} from '../../components/Form'
class Home extends Component {
state = {
formInput: "",
posts: [],
}
loadArticles = (res) => {
console.log('res')
}
handleInputChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
});
};
handleFormSubmit = event => {
event.preventDefault();
let query = this.state.formInput
// console.log(query)
API.scrapeArticles(query)
// console.log(this.state)
};
render() {
return (
<Container>
<Row>
<Column>
</Column>
<Column>
<Input
value={this.state.formInput}
onChange={this.handleInputChange}
name="formInput"
placeholder="Search Query (required)"
/>
<FormBtn onClick={this.handleFormSubmit}>Scrape NYT API</FormBtn>
</Column>
</Row>
</Container>
);
}
}
export default Home;
here is my code calling the clientside api in client/utils/api/:
import axios from "axios";
export default {
// getPosts: function () {
// return axios.get('/api/posts')
// },
// savePost: function (postData) {
// return axios.post("/api/posts", postData);
// },
scrapeArticles: function (query) {
// console.log(query)
let queryData = {
query: query
}
return axios.post('/api/scraper', queryData)
}
};
here is my code from the backend routes/index.js being hit by axios (i think? Im honestly not sure how but i think this is the flow):
const path = require("path");
const router = require("express").Router();
const postsController = require('../controllers/postsController')
router.route("/")
.get(postsController.getAll)
.post(postsController.create);
router.route('/api/scraper')
.post(postsController.scraper)
.get(postsController.scraper)
// If no API routes are hit, send the React app
router.use(function (req, res) {
res.sendFile(path.join(__dirname, "../client/build/index.html"));
});
module.exports = router;
here is my controller that is referenced in the file above:
const scraper = require('../scraper')
const db = require('../models');
module.exports = {
create: function (req, res) {
db.Post
.create(req.body)
.then(dbmodel => res.json(dbmodel))
.catch(err => res.status(422).json(err))
},
getAll: function (req, res) {
db.Post
.find(req.query)
.sort({date: -1})
.then(dbModel => res.json(dbModel))
.catch(err => res.status(422).json(err))
},
scraper: function (req, res) {
let queryData = req.body.query
scraper(queryData)
},
scraperGet: function (req, res) {
scraper()
console.log(res.body)
}
}
and lastly, here is the scraper file on the backend:
const request = require('request');
const mongoose = require('mongoose');
const db = require('./models');
const scraper = (queryData) => {
console.log(`#scraper ${queryData}`)
let articleData = []
request.get({
url: "https://api.nytimes.com/svc/search/v2/articlesearch.json",
qs: {
'api-key': "-----------------------------",
"q" : queryData
},
}, function (err, response, body) {
body = JSON.parse(body);
let articles = body.response.docs
articles.forEach(element => {
// console.log(element)
let title= element.headline.main
let url = element.web_url
let synopsis = element.abstract
let snippet = element.snippet
let source = element.source
let pubDate = element.pub_date
let article = {
title: title,
url: url,
synopsis: synopsis,
snippet: snippet,
source: source,
pubDate: pubDate,
}
// console.log(article)
articleData.push(article)
db.Post.create({title:article.title}).then(article => {
console.log(article)
}).catch(err => {
console.log(err)
})
});
return articleData
});
}
module.exports = scraper;
So i know right now it is pushing to mongo. This is only because I couldn't figure out how to pass that data back just stored in a variable.
I really don't want to have to push all my results to the db and then make a query for them. I want to have a save article function that you only save the ones you actually want.

You should send articleData to the client and then get it in the client side using .then() method of a promise.
Something like this:
scraper: function (req, res) {
let queryData = req.body.query
const articleData = scraper(queryData)
// return your json to the client
res.json(articleData)
},
Then you should receive this data in the client side, like this:
handleFormSubmit = event => {
event.preventDefault()
let query = this.state.formInput
API.scrapeArticles(query)
.then(resp => {
this.setState({ posts: resp })
})
}

Related

Google OAuth components must be used within GoogleOAuthProvider

I want to build my next js project in which i am using
https://www.npmjs.com/package/#react-oauth/google
but when I build it i get the following :
this is layout.js and in _app.js I have all the components wrapped in GoogleOAuthProvider
import { GoogleLogin } from '#react-oauth/google';
import {FcGoogle} from "react-icons/Fc"
import { useGoogleLogin } from '#react-oauth/google';
export default function Layout({ children }) {
const client_id = ""
const responseGoogle = (response) => {
console.log(response);
}
CUTTED (NOT RELEVANT)
const login = useGoogleLogin({
onSuccess: codeResponse => {
const { code } = codeResponse;
console.log(codeResponse)
axios.post("http://localhost:8080/api/create-tokens", { code }).then(response => {
const { res, tokens } = response.data;
const refresh_token = tokens["refresh_token"];
const db = getFirestore(app)
updateDoc(doc(db, 'links', handle), {
refresh_token : refresh_token
})
updateDoc(doc(db, 'users', useruid), {
refresh_token : refresh_token
}).then(
CUTTED (NOT RELEVANT)
)
}).catch(err => {
console.log(err.message);
})
},
onError: errorResponse => console.log(errorResponse),
flow: "auth-code",
scope: "https://www.googleapis.com/auth/calendar"
});
return (
<>
CUTTED (NOT RELEVANT)
</>
)
}
Everything works perfect in dev mode but it does not want to build
I've faced this issue too. So I use 'GoogleLogin' instead of 'useGoogleLogin', then you can custom POST method on 'onSuccess' property.
import { GoogleLogin, GoogleOAuthenProvider} from '#react-oauth/google';
return(
<GoogleOAuthProvider clientId="YOUR CLIENT ID">
<GoogleLogin
onSuccess={handleLogin}
/>
</GoogleOAuthProvider>
The async function will be like...
const handleLogin = async = (credentialResponse) => {
var obj = jwt_decode(credentialResponse.credential);
var data = JSON.stringify(obj);
console.log(data);
const data = {your data to send to server};
const config = {
method: 'POST',
url: 'your backend server or endpoint',
headers: {},
data: data
}
await axios(config)
}
Spending whole day, this solve me out. Just want to share.
You have to wrap your application within GoogleOAuthProvider component. Please keep in mind that you will need your client ID for this.
import { GoogleOAuthProvider } from '#react-oauth/google';
<GoogleOAuthProvider clientId="<your_client_id>">
<SomeComponent />
...
<GoogleLoginButton onClick={handleGoogleLogin}/>
</GoogleOAuthProvider>;

How do I get the data from an API call, in a different file, in React

I'm trying to update some code, taking into account new sdk versions. I have the new api call in one file:
import { CognitoIdentityProviderClient, ListUsersCommand } from "#aws-sdk/client-cognito-identity-provider";
import awsmobile from "../../aws-exports";
import { Auth } from "aws-amplify";
export default async function ListUsers() {
await Auth.currentCredentials().then((data) => {
const client = new CognitoIdentityProviderClient({
region: awsmobile.aws_project_region,
credentials: data
});
const params = {
UserPoolId: awsmobile.aws_user_pools_id
};
const command = new ListUsersCommand(params);
client.send(command).then(
(data) => {
return data
},
(error) => {
console.log(error)
}
);
});
}
I'm trying to retrive the data in another file:
import ListUsers from "../../../API/cognito/ListUsers";
import ListUsersInGroup from "../../../API/cognito/ListUsersInGroup";
import { useState, useEffect, useRef } from "react";
import PortalUsersTable from "../../../components/tables/PortalUsersTable";
export default function ManageUsers() {
const [userDetails, setUserDetails] = useState("");
const refUsers = useRef();
const refUsersExec = useRef();
const refUsersAdmin = useRef();
const refUsersGroups = useRef();
useEffect(() => {
function getUsers() {
refUsers.current = ListUsers();
refUsersExec.current = ListUsersInGroup("usersAdmin");
refUsersAdmin.current = ListUsersInGroup("usersExec");
//setUsersTloOfficers(apiTloOfficers);
refUsersGroups.current = ListUsersInGroup("usersGroups");
let userData = [];
let arrUsersExec = [];
for (let a in refUsersExec.current.Users) {
arrUsersExec.push(refUsersExec.current.Users[a].Username);
}
let arrUsersAdmin = [];
for (let b in refUsersAdmin.current.Users) {
arrUsersAdmin.push(refUsersAdmin.current.Users[b].Username);
}
let arrUsersGroups = [];
for (let b in refUsersNtigGroups.current.Users) {
arrUsersGroups.push(refUsersGroups.current.Users[b].Username);
}
for (let i in refUsers.current.Users) {
let email = null;
for (let x in refUsers.current.Users[i].Attributes) {
if (refUsers.current.Users[i].Attributes[x].Name === "email") {
email = refUsers.current.Users[i].Attributes[x].Value;
break;
}
}
let memberExec = arrUsersExec.includes(refUsers.current.Users[i].Username);
let memberAdmin = arrUsersAdmin.includes(refUsers.current.Users[i].Username);
let memberGroups = arrUsersGroups.includes(refUsers.current.Users[i].Username);
userData.push({
id: i,
Username: refUsers.current.Users[i].Username,
AccountStatus: refUsers.current.Users[i].UserStatus,
Email: email,
Users: memberGroups,
Exec: memberExec,
Admin: memberAdmin,
});
}
setUserDetails(userData);
}
getUsers();
}, []);
return (
<>
<h2>Manage Portal Users</h2>
<PortalUsersTable userDetails={userDetails} />
</>
);
}
The logic to handle the API data is sound.
This is the old API call:
import AWS from "aws-sdk";
import awsmobile from "../../aws-exports";
import { Auth } from "aws-amplify";
export default async function ListUsers() {
let idToken = "";
await Auth.currentAuthenticatedUser().then((user) => {
idToken = user.signInUserSession.idToken.getJwtToken();
});
AWS.config.region = awsmobile.aws_cognito_region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: awsmobile.aws_cognito_identity_pool_id,
RoleArn: "arn:aws:iam::xxxxxxxxx:role/xxxxxxxxxxxxx",
Logins: { "xxxxxxxxxxxxxxxxxxxx": idToken }
});
let cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
let params = {
UserPoolId: awsmobile.aws_user_pools_id,
AttributesToGet: ["email"]
};
return new Promise((resolve, reject) => {
cognitoidentityserviceprovider.listUsers(params, function (err, result) {
if (err) {
console.log(err);
//onError(err);
reject(err);
return;
}
if (result) {
resolve(result);
}
});
});
}
I can see the new API call is returning the correct data in the console. I think I'm not passing the data between files correctly.
I've tried various ways of changing the API call function, reading the cognito sdk description but it's not the API call that is incorrect.
How can I use the API call data in the separate file?
Even if your API call if correct, it looks like you are not returning anything from your function ListUsers. You are mixing async/await pattern with the then. I assume you have added a console.log right before the return data. Refactoring your function using async/await would look like this :
export default async function ListUsers() {
try {
const data = await Auth.currentCredentials();
const client = new CognitoIdentityProviderClient({
region: awsmobile.aws_project_region,
credentials: data,
});
const params = {
UserPoolId: awsmobile.aws_user_pools_id,
};
const command = new ListUsersCommand(params);
const commandData = await client.send(command);
return commandData;
} catch (error) {
console.log(error);
}
}

Dynamic generated sitemap -there are only declared pages on sitemap

I'd like to generate dynamic url fo each slug, but there is an array only with pages which I declared: const pages = ["/", "/about", "/portfolio", "/blog"];
http://localhost:3000/api/my-sitemap. I've installed npm sitemap from https://www.npmjs.com/package/sitemap
my query in ../../lib/data.js
export const getBlogSlugs = async () => {
const endpoint =
"https://api-eu-central-gsagasgasxasasxsaxasxassaster";
const graphQLClient = new GraphQLClient(endpoint);
const query = gql`
{
posts {
slug
}
}
`;
return await graphQLClient.request(query);
};
pages/api/my-sitemap.js
import { getBlogSlugs } from "../../lib/data";
const { SitemapStream, streamToPromise } = require("sitemap");
const { Readable } = require("stream");
export const getStaticProps = async () => {
const { posts } = await getBlogSlugs();
return {
props: {
posts,
},
};
};
export default async (req, res, posts) => {
try {
const links = [];
posts?.map((slug) => {
links.push({
url: `/blog/${slug}`,
changefreq: "daily",
priority: 0.9,
});
});
// Add other pages
const pages = ["/", "/about", "/portfolio", "/blog"];
pages.map((url) => {
links.push({
url,
changefreq: "daily",
priority: 0.9,
});
});
// Create a stream to write to
const stream = new SitemapStream({
hostname: `https://${req.headers.host}`,
});
res.writeHead(200, {
"Content-Type": "application/xml",
});
const xmlString = await streamToPromise(
Readable.from(links).pipe(stream)
).then((data) => data.toString());
res.end(xmlString);
} catch (e) {
console.log(e);
res.send(JSON.stringify(e));
}
};
I added to my robots.txt in pudblic folder:
User-agent: *
Allow: /
Sitemap: http://localhost:3000/api/my-sitemap
What I got is only declared pages
localhost:3000/api/my-sitemap
I tried like this and doesn't work too:
export const getStaticProps = async () => {
const data = await getBlogSlugs();
return {
props: {
posts: data.posts,
},
};
};
export default async (req, res, posts) => {
try {
const links = [];
posts?.map((post) => {
links.push({
url: `/blog/${post.slug}`,
changefreq: "daily",
priority: 0.9,
});
});
You cannot use getStaticProps from an API route.
https://github.com/vercel/next.js/discussions/16068#discussioncomment-703870
You can fetch the data directly inside the API function.
Edit: In my app, I use the API route code below to fetch data from external server
import fetch from "isomorphic-unfetch";
export default async (req, res) => {
try {
const result = await fetch("YOUR_URL");
const posts = await result.json();
//use posts
});
} catch (e) {}
};
For GraphQl may be you can check the example given in vercel site
https://github.com/vercel/next.js/tree/canary/examples/api-routes-graphql

prettier urls with nextjs routes

I'm building out a new marketing site for my company using next.js, and I'm running into an issues with URLS. Essentially, I've built a custom API route to access data from our internal database, using Prisma:
getAllDealers.ts
import Cors from 'cors';
import { prisma } from 'lib/prisma';
import { NextApiResponse, NextApiRequest, NextApiHandler } from 'next';
const cors = Cors({
methods: ['GET', 'HEAD'],
});
function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: any) {
return new Promise((resolve, reject) => {
fn(req, res, (result: any) => {
if (result instanceof Error) {
return reject(result);
}
return resolve(result);
});
});
}
const getDealers: NextApiHandler = async (req: NextApiRequest, res: NextApiResponse) => {
const { method } = req;
await runMiddleware(req, res, cors);
const dealers = await prisma.crm_dealers.findMany({
where: {
active: {
not: 0,
},
},
});
switch (method) {
case 'GET':
res.status(200).send({ dealers, method: method });
break;
case 'PUT':
res.status(500).json({ message: 'sorry, we only accept GET requests', method: method });
break;
default:
res.setHeader('Allow', ['GET']);
res.status(405).end(`Method ${method} Not Allowed`);
}
};
export default getDealers;
And I've built a route to access individual dealers:
getSingleDealer.ts
import Cors from 'cors';
import { prisma } from 'lib/prisma';
import { NextApiResponse, NextApiRequest, NextApiHandler } from 'next';
const cors = Cors({
methods: ['GET', 'HEAD'],
});
function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: any) {
return new Promise((resolve, reject) => {
fn(req, res, (result: any) => {
if (result instanceof Error) {
return reject(result);
}
return resolve(result);
});
});
}
const getDealerById: NextApiHandler = async (req: NextApiRequest, res: NextApiResponse) => {
await runMiddleware(req, res, cors);
const dealer = await prisma.crm_dealers.findUnique({
where: {
id: Number(req.query.id),
},
});
res.status(200).send({ dealer, method: req.method });
};
export default getDealerById;
I can use my getSingleDealer function in getServerSideProps like so:
export const getServerSideProps = async ({ params }: Params) => {
const { uid } = params;
const { dealer } = await getSingleDealer('api/dealer', uid);
return {
props: { dealer },
};
};
And this works just fine. What I need to do though is prettify my URLS. Right now, the way to access a singular dealer's page is dealers/1 with 1 being whatever the ID of the dealer is. I want to have that URL be a string, like dealers/sacramento-ca (that location is also served up in the API) while still accessing the API on the basis of the id so it's searching for an integer, rather than a string. Is that possible within next?
You'd handle the routing in your client with getServerSideProps similarly to what you are doing now. To do so, you need to configure your dynamic routing file or folder name to match your desired format.
Example folder structures are:
pages > dealers > [dealer].tsx = /dealers/sacramento-ca
pages > dealers > [location] > index.tsx = /dealers/sacramento-ca
export const getServerSideProps = async ({ params }: Params) => {
const { uid } = params;
const { dealer } = await getSingleDealer('api/dealer', uid);
if (!dealer ) {
return { notFound: true }
}
return {
props: {
...dealer,
location: 'sacramento-ca', // object key must match your dynamic [folder or file's name]
},
};
};
All dynamic URL parts must be included as a key in the return.
pages > dealers > [state] > index.tsx [city].tsx = /dealers/ca/sacramento
return {
props: {
...dealer,
city: 'sacramento',
state: 'ca',
},
};
Here is a article detailing what you will need to do. It's important to note that sometimes it's desirable to use a catch all route to simplify searching and deeply nested dynamic routes.

How to fetch API data from Axios inside the getServerSideProps function in NextJS?

I'm building an App with Next.js, and I need to connect to specific API routes (set up with API Platform) and populate pages with the route's responses.
The API is working fine, but no matter how I try to implement my Axios call inside the getServerSideProps, I always get the same error, ECONNREFUSED, from my Node stack.
I tried to get the data from useEffect() and it's working fine, but I would like to know if there's a way to call it directly in getServerSideProps.
I'm using a Node container for Docker, and the routes are authenticated through a JWT Token (stored in the session and the client cookies for the server-side connection)
Here are is my code:
pages/accounts.js:
export async function getServerSideProps(context) {
const cookies = new Cookies(context.req.headers.cookie)
const adminToken = cookies.get('jwtToken')
const res = await getAllAccounts(adminToken)
return {
props: {
testdata: ''
},
}
}
lib/accounts.js:
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`).then((response) => {
}).catch((error) => {
console.dir(error)
})
}
HTTP wrapper:
import axios from 'axios';
import jwt_decode from "jwt-decode";
import mockAdapter from 'axios-mock-adapter';
const service = ({ jwtToken = null, store = null, mockURL = null, mockResponse = null, multipart = false } = {}) => {
const options = {};
options.baseURL = process.env.NEXT_PUBLIC_API_URL + '/api';
if(multipart === true) {
options.headers = {
'Content-Type': 'multipart/form-data'
}
} else {
options.headers = {
'Content-Type': 'application/ld+json',
accept: 'application/ld+json'
}
}
const instance = axios.create(options);
instance.interceptors.response.use(response => {
return response;
}, error => {
return Promise.reject(error);
})
if (mockURL !== null && mockResponse !== null) {
let mock = new mockAdapter(instance);
mock.onAny(mockURL).reply(200, mockResponse)
}
return instance;
};
export default service;
Through the error dump in the node stack, I managed to see that the request headers are correct, and the JWT correctly passed through.
Do not use Axios. Just use fetch().
Next.js polyfills fetch() by default on both the client and server, so you can just use it:
In addition to fetch() on the client-side, Next.js polyfills fetch() in the Node.js environment. You can use fetch() in your server code (such as getStaticProps/getServerSideProps) without using polyfills such as isomorphic-unfetch or node-fetch.
Source.
getServerSideProps works well with axios if we return response.data
export const getServerSideProps: GetStaticProps = async ({ params }) => {
const { brandName } = params as IParams;
const brandData = await $host.get(`api/brand/${brandName}`).then(response => response.data);
return {
props: {
brand: brandData,
},
};
};
Your problem is that your async method does not return a promise.
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`);
return res;
}
In my NextJS begining I followed this tutorial , and I changed fetch to axios in this way:
export const getStaticPaths = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await res.json();
const paths = data.map((ninja) => {
return {
params: { id: ninja.id.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context) => {
const id = context.params.id;
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const data = await res.json();
return {
props: { ninja: data },
};
};
I applied the change using useEffect()
useEffect(() => {
// const data = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
// const res = await data.json();
// setninja(res);
const fetchData = async () => {
const result = await axios(`https://jsonplaceholder.typicode.com/users/${id}`);
setninja(result.data);
};
fetchData();
console.log(data);
}, []);
I hope this info will be useful for you.
I Used Axios in getServerSideProps without any problems.
export const getServerSideProps: GetServerSideProps = async({
params,
res
}) => {
try {
const response = await axios.get(`/api/test`);
return {
props: {
data: response.data
},
}
} catch {
res.statusCode = 404;
return {
props: {}
};
}
};

Resources