I am trying to use a Svelte datastore and a Restapi backend to store, display and update a simple list of objects.
The front end has a table of items, a text entry, and a button to add to the list.
When I add to the list the datastore seems to update okay, as does the backend JSON.
The table in the frontend however only display "undefined" until the page is refreshed manually in the browser.
The logic I have tried to use is as follows:
The App function handleAddItem() triggers the function addData() on the custom store and then it
does an update on the items = [...items, new_data].
Table shows undefined until the page is refreshed
Any clues what I might be doing wrong?
api.js backend server
export async function fetchData(){
console.log("fetching data");
const response = await fetch("http://localhost:3000/data")
const data = await response.json();
return data
}
export async function addItem(item_label){
console.log("Backend api is posting item: ", item_label);
const newlyAddedItem = {item_label};
const settings = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(newlyAddedItem)
};
try {
const url = `http://localhost:3000/data`;
const response = await fetch(url, settings);
const newlyAddedItem = await response.json();
console.log("returned data after POST", newlyAddedItem)
} catch (e) {
return e;
}
}
datastore.ts
import { writable } from "svelte/store";
import { fetchData, addItem } from "../backend/api.js";
import type item from "../../src/App.svelte";
function alistStore() {
const { subscribe, update, set } = writable([])
return {
subscribe,
update,
loadData: async () => {
let data = await fetchData();
set(data);
return data
},
addData: async (item_label) => {
console.log("Datastore handling add item: ", item_label)
let new_data: item = await addItem(item_label);
return new_data
}
};
}
export let alist_store = alistStore();
App.svelte
<script lang="ts">
import { alist_store } from "./stores/datastore";
import { onMount } from "svelte";
interface item {
"id": number,
"item_label": string
};
export let items: item[];
export let new_data;
export let item_label = "";
alist_store.subscribe((data) => items = data)
onMount(() => {
alist_store.loadData();
})
async function handleAddItem(item_label) {
console.log("App is handling add item: ", item_label);
new_data = alist_store.addData(item_label);
console.log("updating items")
items = [...items, new_data];
console.log("updated items")
};
</script>
<main>
<table>
<th>Id</th>
<th>Name</th>
{#each items as item (item.id)}
<tr>
<td>{item.id}</td>
<td>{item.item_label}</td>
</tr>
{/each}
</table>
<div>
<textarea bind:value={item_label}></textarea>
<button class="button" on:click={() => handleAddItem(item_label)}></button>
</div>
</main>
I have tried a few things:
1: reloading the data from the store
2: trying to add reactive syntax to the => $items
3: I tried defining the object type as "item" before adding it to the list.
I think it might be to do with how things are being handled by the async and await, but this is all new to me - so I am left a bit confused.....
It looks like a problem with the order in things are happening??
There's the log console.log("returned data after POST", newlyAddedItem) at the end of addItem inside api.js but newlyAddedItem isn't returned
alist_store.addData is async -> new_data = await alist_store.addData(item_label);
(if the variable new_data is only needed inside this function it wouldn't have to be declared on top level)
Change the function on the addAlbum in the datastore.
It needed to handle the logic for the append to the current array of data, this means it is guaranteed to always return an array. No promises broken and the components all refresh instantly after the add album is executed.
Debugging the code to print to the console showed that when I expected it should be an array, it was an album.
addAlbum: async () => {
let data = await fetchData();
let album = await addAlbum({
"id": "99",
"title": "Blue Train",
"artist": "John Coltrane",
"price": 99.99
});
data = [...data, album]
set(data);
}
Related
Im mostly using SWR to get data, however I have a situation that I need to update data. The problem is, I need an indicator that this request is ongoing, something like isLoading flag. In the docs there's a suggestion to use
const isLoading = !data && !error;
But of course when updating (mutating) the data still exists so this flag is always false. The same with isValidating flag:
const { isValidating } = useSWR(...);
This flag does NOT change when mutation is ongoing but only when its done and GET request has started.
Question
Is there a way to know if my PUT is loading? Note: I dont want to use any fields in state because it won't be shared just like SWR data is. Maybe Im doing something wrong with my SWR code?
const fetcher = (url, payload) => axios.post(url, payload).then((res) => res);
// ^^^^^ its POST but it only fetches data
const updater = (url, payload) => axios.put(url, payload).then((res) => res);
// ^^^^^ this one UPDATES the data
const useHook = () => {
const { data, error, mutate, isValidating } = useSWR([getURL, payload], fetcher);
const { mutate: update } = useSWRConfig();
const updateData = () => {
update(getURL, updater(putURL, payload)); // update data
mutate(); // refetch data after update
};
return {
data,
updateData,
isValidating, // true only when fetching data
isLoading: !data && !error, // true only when fetching data
}
Edit: for any other who reading this and facing the same issue... didnt find any solution for it so switched to react-query. Bye SWR
const { mutate: update } = useSWRConfig();
const updateData = () => {
// this will return promise
update(getURL, updater(putURL, payload)); // update data
mutate(); // refetch data after update
};
By using react-toastify npm module to show the user status.
// first wrap your app with: import { ToastContainer } from "react-toastify";
import { toast } from "react-toastify";
const promise=update(getURL, updater(putURL, payload))
await toast.promise(promise, {
pending: "Mutating data",
success: "muttation is successfull",
error: "Mutation failed",
});
const markSourceMiddleware = (useSWRNext) => (key, fetcher, config) => {
const nextFetcher = (...params) =>
fetcher(...params).then((response) => ({
source: "query",
response,
}));
const swr = useSWRNext(key, nextFetcher, config);
return swr;
};
const useHook = () => {
const {
data: { source, response },
mutate,
} = useSWR(key, fetcher, { use: [markSourceMiddleware] });
const update = mutate(
updateRequest().then((res) => ({
source: "update",
response,
})),
{
optimisticData: {
source: "update",
response,
},
}
);
return {
update,
updating: source === "update",
};
};
Hmm based on that:
https://swr.vercel.app/docs/conditional-fetching
It should work that the "is loading" state is when your updater is evaluates to "falsy" value.
REMAINDER! I don't know react swr just looked into docs - to much time at the end of the weekend :D
At least I hope I'll start discussion :D
I'm working on a projet using Vue.js + typescript + axios, and i'm actually trying to create a complex ApiRequest. In other words, i don't want to do that :
data (){
return {
users:[]
}
},
methods: {
getUsers : function() {
axios
.get("http://localhost:9090/Users")
.then(response => {
this.users = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
}
because is not enough maintenable in a big project.
Instead of that, i created an "ApiService.ts" file where i declared the GET function :
import axios from "axios";
import { ApiRequest } from "../models/api/ApiRequest";
import { HttpMethod } from "../enums/HttpMethod";
const httpClient = axios.create({
baseURL: "http://localhost:9090"
});
class ApiService {
async get<T=any>(url: string): Promise<T>{
return await this.perform<T>({
method: HttpMethod.GET,
url,
});
}
private async perform<T = any>(request: ApiRequest): Promise<T> {
try {
const response = await httpClient({
method: request.method,
url: request.url,
});
console.log(response.data)
return response.data
} catch (error) {
const message = error.response
? error.response.data.message
: error.message;
throw Error (message);
}
}
}
export const apiService = new ApiService();
And i imported it in my file 1:
data (){
return {
users:[]
}
},
methods: {
getUsers : function() {
this.users = apiService.get("/Users");
}
}
Finally, i called the function and my users Array in the same file 1 like that :
<button #click= "getUsers"> Click here to get users </button>
<div> {{users}} </div>
BUT : when i click...
that what happened
To explain you a litte, my users Array contain a non-understable "Object Promise", but my function works bcs as you can see in the console.log the Arrays appears.
So, someone can explain me how can i do to display my differents users in my users Array instead of "Object Promise" ? and what is this "object Promise" ?
Thanks a lot !
The return type of get function is Promise because the function is async function. That's why you're getting Promise object.
So you should use async/await when you call apiService.get() function.
methods: {
getUsers : async function() {
this.users = await apiService.get("/Users");
}
}
Also if you want to show the users, you can do sth like this:
<button #click= "getUsers"> Click here to get users </button>
<div
v-for="(item, index) in users"
:key="index"
>
{{ `Name: ${item.name}, Address: ${item.address}` }}
</div>
I'm learning axios and async calls, please sorry if this is too basic. I have this axios call:
trackComponent.jsx
getTrack(event) {
const {select, userId} = this.props
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/track/${select}/${userId}/${this.props.spotifyToken}`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
};
return axios(options)
.then((res) => {
this.setState({
playlist: res.data.data[0].playlist,
artists: res.data.data[0].artists,
previews: res.data.data[0].previews,
youtube_urls: res.data.data[0].youtube_urls,
})
})
.catch((error) => { console.log(error); });
};
Now, I'm refactoring my code, and I've implemented an apiService in front of all my component calls in order to deal with authorization, like so:
trackComponent.jsx
import {apiService} from '../ApiService'
async track(event) {
if (this.props.isAuthenticated) {
const {userId, spotifyToken} = this.props;
const {artist} = this.state
const tracks = await apiService.getTrack(userId, spotifyToken, artist) ;
this.setState({tracks});
} else {
this.setState({tracks: []});
}
}
an in ApiService.js I have:
async getTrack(userId, spotifyToken, select) {
return this.axios.get(
`${process.env.REACT_APP_WEB_SERVICE_URL}/track/${artist}/${userId}/${spotifyToken}`
);
}
Now how do I tweak this new async track(event) in the component in order to keep my 'response' and set the following states,
playlist: res.data.data[0].playlist,
artists: res.data.data[0].artists,
previews: res.data.data[0].previews,
youtube_urls: res.data.data[0].youtube_urls,
which were being passed as response inside then() of the first getTrack(event)?
Provided the API call executes successfully, tracks (the value you're returning from getTrack) will contain the responses you're looking for.
If you console.log it, you'll see the various fields. At that point, it's just a matter of accessing the values and setting state with them:
const tracks = await apiService.getTrack(userId, spotifyToken, artist);
const firstEntry = tracks.data.data[0];
this.setState({
playlist: firstEntry.playlist,
artists: firstEntry.artists,
...
});
I'm new to Next Js and functional comoponents. I'm trying to retrieve data from /api/retrieve2
//this is retrieve page
export default function Retrieve() {
const onSubmit = async data => {
const { user } = await axios.post("/api/retrieve2", data);
console.log(user) // user here is undefined
};
return (...);
}
//this is retrieve2, inside the API folder
export default async (req, res) => {
try {
const { data } = await axios.post(myBackendUrl, req.body);
console.log(data) //this is printing the right data - { email: 'casas#gmail.com', code: '123123' }
res.json(data);
} catch (e) {
res.json({ err: e.message || e });
}
};
What am I missing, is this something about Next? About functional components?
You should read about ES6 destructuring
You try to destructure user but the axios respons witch is a object doesnt contain the key user
For data it works because there is a data property in the response
Here are all properties that you can destructure:
{ data, status, statusText, headers, config, request }
You need to get the full URL to make http request to using getInitialProps, here Home is the name of your component
const Home = ({ENDPOINT}) => {
const onSubmit = async data => {
const { data } = await axios.post(`${ENDPOINT}/api/retrieve2`, data);
// consider changing `user` here to `data` since Axios stores response in data object
console.log(data) // should be defined
};
return (...);
}
Home.getInitialProps = ctx => {
const ENDPOINT = getEndpoint(ctx.req);
return { ENDPOINT };
};
// You should store this somewhere you can reuse it
export function getEndpoint(req) {
return !!req
? `${req.headers['x-forwarded-proto']}://${req.headers['x-forwarded-host']}`
: window.location.origin;
}
I'm trying to interact with the API of processmaker.
I have made a simple form to authenticate and get the authorization token, which is needed to interact with the rest of the API.
I am able to use the token to output a json response of created projects after login. The response is an array of objects.
I need to get the prj_uid for an api request so I want to extract these, but I've had little luck using map.
How can I iterate over the response and get prj_name and prj_uid for each of the objects in the array?
import React, { useState, useEffect } from "react";
//import ResponsiveEmbed from "react-responsive-embed";
const Tasks = ({ loggedIn }) => {
const [hasError, setErrors] = useState(false);
const [projects, setProjects] = useState([]);
const url = "http://127.0.0.1:8080/api/1.0/workflow/project";
useEffect(() => {
let access_token = sessionStorage.getItem("access_token");
async function fetchData() {
const response = await fetch(url, {
method: "GET",
withCredentials: true,
timeout: 1000,
headers: {
Authorization: "Bearer " + access_token
}
});
response
.json()
.then(response => setProjects(response))
.catch(err => setErrors(err));
}
fetchData();
}, [loggedIn]);
console.log(JSON.stringify(loggedIn) + " logged in, displaying projects");
console.log(projects + " projects");
if (!loggedIn) {
return <h1>Error</h1>;
} else {
return (
<>
<p>Login success!</p>
<h2>Projects:</h2>
<span>{JSON.stringify(projects)}</span>
<div>Has error: {JSON.stringify(hasError)}</div>
</>
);
}
};
export default Tasks;
Stringified Response:
[
{
"prj_uid":"1755373775d5279d1a10f40013775485",
"prj_name":"BPMN Process",
"prj_description":"This is a processmaker BPMN Project",
"prj_category":"8084532045d5161470c0de9018488984",
"prj_type":"bpmn",
"prj_create_date":"2019-08-13 08:50:25",
"prj_update_date":"2019-08-13 09:04:16",
"prj_status":"ACTIVE"
},
{
"prj_uid":"7459038845d529f685d84d5067570882",
"prj_name":"Purchase Request",
"prj_description":"",
"prj_category":"2284311685392d2e70f52e7010691725",
"prj_type":"bpmn",
"prj_create_date":"2019-08-13 11:30:48",
"prj_update_date":"2019-08-13 12:20:05",
"prj_status":"ACTIVE"
}
]
Array.map() is your answer- you had it right.
its as simple as:
let mappedObject = result.map( el => ({ prj_name, prj_uid }) );
el is every element in the array, and we construct the new array with an object containing only prj_name and prj_uid. Because el alraeady has those properties with those names, we do not need to write { prj_name: el.prj_name } when we construct the new object, it is implied and will do the trick with only the property names there.
mappedObject will now hold an array of objects consists only of the asked properties.
You might wanna read more about map to understand it better- Array.map()
If loggedIn is the json object, then you can do this:
const uidNameArr = loggedIn.map((item) => { // returns an array of arrays with the values you want.
return [item.prj_uid, item.prj_name]
})
uidNameArr.forEach(([uid,name]) => {
console.log(`${name} has a uid of ${uid}`)
})