LitElement: javascript function runs on repeat - javascript

I have made a LitElement that is supposed to console log all users in a database. I do this with nodejs and mysql. I am able to get the users from the database and console.log them, but the script that fetches the data runs on repeat which it should not do. This is my LitElement.
export class AllUsers extends LitElement{
static get properties(){
return {
users : {type : String},
numberOfUsers : {type : Number},
location : {type: Object} // Needed for vaadin-router with litElements
}
}
constructor(){
super();
this.location = router.location
}
static styles = css`
:host {
display: block;
}
`;
render(){
return html `
<h1>ALL USERS</h1>
${this.getUsers()}
`;
}
getUsers(){
fetch('http://localhost:8081/getUsers', {
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
}
}).then(res =>{ res.json().then(data =>{
this.users = data
console.log(this.users)
this.numberOfUsers = this.users.length
})
}).catch((e) =>{
throw Error(e)
})
}
}
customElements.define('all-users', AllUsers)
nodejs app
app.get('/getUsers', function (req, res) {
db.query('SELECT * FROM users', function (err, result) {
if (err) {
res.status(400).send('Error in database operation.');
} else {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(result));
}
});
});
What would have to be changed to fix this?

This template:
return html `
<h1>ALL USERS</h1>
${this.getUsers()}`;
Renders the output of this.getUsers() into the template, but that's a void, so it basically just fires the function again each time it's rendered.
There are a couple of different approaches here - I'd recommend using guard and until to only get the users when some property changes, or adding a ReactiveController that fetches and caches users, but simplest is probably to just render the users and then kick off the script to get them.
Firstly change the template to render the users:
return html `
<h1>ALL USERS</h1>
${this.users?.map(u => html`<span>User: ${u.name}</span>`)}`;
Then simplify your getUsers to just populate it (note this is also a lot simpler with async/await):
async getUsers() {
if(this.users)
return; // We already have users, don't get again
const res = await fetch('http://localhost:8081/getUsers', {
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
}
};
this.users = await res.json();
console.log(this.users);
this.numberOfUsers = this.users?.length;
}
Then call this.getUsers() in either firstUpdated or connectedCallback.

Related

Put Not Working to Update with Axios Mongoose

I am trying to set up an edit feature to edit a post. Right now I am trying to update a specific post by ID and then I'll make it dynamic.
I can get axios to send the PUT request but I don't receive any indication that it is received on the router. Also the ID I have set it showing up correctly in the URL.
I'm not sure how to send the data over to the router so it can find the ID.
Edit component
function handleSubmit(event){
event.preventDefault()
axios ( {
url: `/api/${props.data[0]._id}`,
method: 'PUT',
headers: { "Content-Type": "multipart/form-data" },
id: props.data[0]._id
})
.then(() => {
console.log(`data has been sent to the server from axios: ${props.data[0]._id}`)
})
.catch(() => {
console.log('Data could not be sent from axios')
})
}
Router
router.put('/:id', async (req, res) => {
try {
const updatedGratitude = await PostGratitude.findByIdAndUpdate(req.params.id)
res.status(200).json(updatedGratitude)
} catch (err){
next(err)
}
})
if you are editing a post then you should send the data in the request as well
like a title: "" and description: "" or something and in the in the router, you could write something like this :
function handleSubmit(event) {
event.preventDefault()
axios({
url: `/api/${props.data[0]._id}`,
method: 'PUT',
headers: { "Content-Type": "application/json" },
data: {
title: '',
description: ''
}
})
.then((response) => {
console.log(response)
})
.catch((err) => {
console.log(err)
})
}
you need to pass the arguments as to what to update as well, here is an example of a code that I wrote
router.put('/updatentry/:id',fetchuser, async (req, res) => {
var success = false
try {
const { title, description } = req.body
let newentry = { title: title , description: description
}
let old_entry = await Journal.findById(req.params.id);
if (!old_entry) {
return res.status(404).send({ success, error: 'Not Found'})
}
const update_entry = await Journal.findByIdAndUpdate(req.params.id, { $set: newentry }, { new: true })
return res.send(res: update_entry)
} catch (error) {
return res.status(500).send(error: 'Internal Server Error')
}
})
This is because you forgot the update body on method. Try this:
PostGratitude.findByIdAndUpdate(req.params.id, req.body)
instead of :
await PostGratitude.findByIdAndUpdate(req.params.id)
Because mongoose can not know what to update :D

SWR not working properly with async fetch

Recently updated SWR - now for some reason my data is not fetching properly.
const { data: expressionsData, error: expressionsError } = useSWRImmutable(
[`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
apiRequest
);
Using this fetching,
import firebase from "./firebase";
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
console.log("error", response);
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
// Create an Error with custom message and code
export function CustomError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
// Check if a indexDb database exists
export function indexedDbdatabaseExists(dbname, callback) {
const req = window.indexedDB.open(dbname);
let existed = true;
req.onsuccess = function () {
req.result.close();
if (!existed) window.indexedDB.deleteDatabase(dbname);
callback(existed);
};
req.onupgradeneeded = function () {
existed = false;
callback(existed);
};
}
Now I'm looking at this StackOverflow thread,
useSWR doesn't work with async fetcher function
And thinking I'll just remake the fetcher to be without Async. I'm just wondering why this has stopped working though in general, and if I can just keep my existing codebase.
The error is a 400 message, it only happens with this expressions API call which takes longer to load due to the amount of data I think,
xxxx/dashboard/expression/get-expression-analytics?startTime=1648183720488&endTime=1650865720488 400 (Bad Request)
with error log
These calls are working fine, they have substantly less data though.
const { data: overall, error: psychometricError } = useSWRImmutable(
`dashboard/psychometric/get-psychometric-home?starttime=infinite`,
apiRequest
);
const { data: sentimentData, error: sentimentError } = useSWRImmutable(
[`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
fetchSentiment
);
Made an update to the fetch call to be more readable and specifically about the URL pathway.
import firebase from './firebase';
// Create an Error with custom message and code
export function CustomError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
export async function expressionsRequest(path, method = 'GET') {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
return fetch(`/api/${path}`, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => {
if (!response.ok) {
throw `Server error: [${response.status}] [${response.statusText}] [${response.url}]`;
}
return response.json();
})
.then((receivedJson) => {
if (receivedJson.status === 'error') {
// Automatically signout user if accessToken is no longer valid
if (receivedJson.code === 'auth/invalid-user-token') {
firebase.auth().signOut();
}
throw new CustomError(receivedJson.code, receivedJson.message);
} else {
return receivedJson.data;
}
})
.catch((err) => {
console.debug('Error in fetch', err);
throw err;
});
}
Additionally, this is what the lambda function (using next API folder) looks like,
const requireAuth = require('../../_require-auth');
const { db } = require('../../_sql');
export default requireAuth(async (req, res) => {
const { uid: id } = req.user;
const startTime = Math.round(req.query.startTime * 0.001);
const endTime = Math.round(req.query.endTime * 0.001);
const parameters = [id, startTime, endTime];
//sql injection definitely possible here, need to work out better method of dealing with this.
const sqlText = `SELECT a,b,c,d,e,f,g,h,i FROM tablename WHERE a=$1 AND i BETWEEN $2 AND $3;`;
try {
const { rows } = await db.query(sqlText, parameters);
return res.status(200).json({
code: 0,
data: rows,
});
} catch (error) {
return res.status(200).json({
code: 0,
message: 'Error occurred in getting tablename',
error,
});
}
});
using postman with the same query, i.e.,
curl --location --request GET 'http://localhost:3000/api/dashboard/expression/get-expression-analytics?startTime=1648387240382&endTime=1651069240382' \
--header 'Authorization: Bearer xxxx' \
--data-raw ''
Successfully returns a response with data attached.
Based on your first code blocks, the startDate value is getting passed into the fetcher as method, and the endDate value is getting passed into the fetcher as data. This is based on the useSWR docs about passing in an array for the key argument: https://swr.vercel.app/docs/arguments#multiple-arguments
If the code you provided is correct, I'd assume the 400 is coming from trying to pass in a random value for the method option for fetch.
This should be fixed by only passing the API endpoint path into useSWR instead of an array:
const { data: expressionsData, error: expressionsError } = useSWRImmutable(
`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`,
apiRequest
);

Singleton config - how to avoid returning a Promise in typescript

I have my Configuration class:
interface ConfigObject {
apiUrl: string;
identityPoolId: string;
identityPoolRegion: string;
region: string;
userPoolId: string;
userPoolWebClientId: string;
}
class Configuration {
private static _instance: ConfigObject;
public static get Instance() {
return (
this._instance ||
this.getConfig<ConfigObject>().then((config) => {
this._instance = config;
return config;
})
);
}
private static async getConfig<TConfig>(): Promise<TConfig> {
const response = await fetch('env.json', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
return data;
}
}
export default Configuration.Instance;
and I want to access it's values in my service:
export default class APIService {
static baseURL: string = `${Config.apiUrl}/mocked`;
Yet at the time of accessing Config.apiUrl is undefined
How can I make sure that the getConfig fetch gets executed and the actual object is returned instead?
You can't make an asynchronous process synchronous. But you can make your module wait to load until you've read that JSON file, by using top-level await, which is now broadly supported in browsers and by bundlers.
async function getConfigData(): Promise<ConfigObject> {
const response = await fetch('env.json', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data: ConfigObject = await response.json();
return data;
}
const data = await getConfigData(); // *** Module load waits here
class Configuration {
private static _instance: ConfigObject;
public static get Instance() {
if (!this._instance) {
this._instance = data;
}
return this._instance;
}
}
export default Configuration.Instance;
That said, there doesn't seem to be any purpose to the Configuration class, just export the data directly:
async function getConfigData(): Promise<ConfigObject> {
const response = await fetch('env.json', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
return data;
}
const data: ConfigObject = await getConfigData(); // *** Module load waits here
export default data;
Side note: Since objects are mutable by default, any module that imports the configuration data can modify it (for instance, if there's a data.example property, by doing data.example = "some value"). Maybe you want it to be mutable, in which case don't do anything, but if you don't you might use Object.freeze to make everything about the object read-only:
// ...
const data: ConfigObject = await getConfigData();
Object.freeze(data); // *** Freezes the object
export default data;

axio. to get the data in a different file

I hope you can help me.
I'm trying to get a response from an API and use that information in another file.
I have 3 files:
api.jsx
import axios from 'axios';
export const api = (url, data) => {
const { path, method } = url;
let result ={};
axios({
method: method,
url: path,
data: data
})
.then(res => {
result = res.data;
console.log(res.data);
})
.catch(err => console.error(err));
return result;
};
url.jsx
export const URL = {
users:
{
getAllUsers: { path:'/users', method: 'post'},
login: { path:'/login', method: 'post'},
register: { path:'/register', method: 'post'},
version: { path:'/', method: 'get'},
}
}
app.js (within the render)
const data = {
email: 'hello#world.com',
password: '12345',
};
let result = api(URL.users.login, data);
console.log(result);
In the api file i get the proper response but in the react component no. I am aware that It's a problem of sync as i get first the console of app.jsx and later on the console of the api.jsx but i would like to respect the current structure or make something similar.
Any idea how to fix this without many changes?
PS. sorry about the mess. I tried to highlight all the code but for some reason it is not working fine.
You want to return a Promise in api.jsx
api.jsx
export const api = (url, data) => {
const { path, method } = url
return axios({ // the axios call returns a promise because of its .then; you can just return it
method: method,
url: path,
data: data
})
.then(res => {
return res.data;
})
.catch(err => {
console.error(err)
})
}

Fetch POST cannot pass body as JSON

I am trying to call an express endpoint with fetch from my react app and pass the body as a JSON object so that I can include more data within it. The call currently works when I set body directly to token.id, but I want to include more data. For now I'm just constructing the JSON with that one piece of data, but cannot seem to get the server to properly handle it. I have the working lines of code commented out, which I know work as I am seeing the calls make it through to the stripe API. With the new code I get as far as the token creation working, but the create customer call does not show up on the stripe API.
import React, {Component} from 'react';
import {CardElement, injectStripe} from 'react-stripe-elements';
class CheckoutForm extends Component {
constructor(props) {
super(props);
this.state = {complete: false};
this.submit = this.submit.bind(this);
}
async submit(ev) {
let {token} = await this.props.stripe.createToken({name: "Name"});
let data ={
id: token.id
}
let response = await fetch("/charge", {
method: "POST",
//Works with the commented code
//headers: {"Content-Type": "text/plain"},
//body: token.id,
headers: {"Content-Type": "application/json"},
body: JSON.stringify(data)
}).then(response => response.json());
if (response.ok) console.log("Purchase Complete!")
if (response.ok) this.setState({complete: true});
}
render() {
if (this.state.complete) return <h1>Purchase Complete</h1>;
return (
<div className="checkout">
<p>Would you like to complete the purchase?</p>
<CardElement />
<button onClick={this.submit}>Send</button>
</div>
);
}
}
export default injectStripe(CheckoutForm);
This is my server code:
const app = require("express")();
const stripe = require("stripe")("sk_test_oE2EqjsM7mWqgRRwaomptrdX");
app.use(require("body-parser").text());
app.post("/charge", jsonParser, async (req, res) => {
try {
var userdata = JSON.parse(req.body);
let {status} = stripe.customers.create({
description: "Test Person",
//Commented code currently works
//source: req.body
//Below is how I expected this to work when passing the body as JSON
source: userdata.id
});
res.json({status});
} catch (err) {
res.status(500).end();
}
});
app.listen(9000, () => console.log("Listening on port 9000"));

Categories