Displaying multiple users from http data instead of a single user - javascript

I'm trying to modify the following code so that I receive an array of users from http and display all of them. This code only receives and displays a single user.
import Cycle from '#cycle/core';
import {div, button, h1, h4, a, makeDOMDriver} from '#cycle/dom';
import {makeHTTPDriver} from '#cycle/http';
function main(sources) {
const USERS_URL = 'http://jsonplaceholder.typicode.com/users/';
const getAllUsers$ = sources.DOM.select('.get-all').events('click')
.map(() => {
return {
url: USERS_URL + 1,
method: 'GET'
};
});
const user$ = sources.HTTP
.filter(res$ => res$.request.url.indexOf(USERS_URL) === 0)
.mergeAll()
.map(res => res.body)
.startWith(null);
const vtree$ = user$.map(user =>
div('.users', [
button('.get-all', 'Get all users'),
user === null ? null : div('.user-details', [
h1('.user-name', user.name),
h4('.user-email', user.email),
a('.user-website', {href: user.website}, user.website)
])
])
);
return {
DOM: vtree$,
HTTP: getAllUsers$
};
}
const drivers = {
DOM: makeDOMDriver('#app-main'),
HTTP: makeHTTPDriver(),
};
Cycle.run(main, drivers);

You should do one request for multiple users, then iterate over them:
Get all users:
// rather than getting just user 1
url: USERS_URL,
Update this stream:
const users$ = sources.HTTP // users (plural)
.filter(res$ => res$.request.url.indexOf(USERS_URL) === 0)
.mergeAll()
.map(res => res.body)
.startWith([]); // start with an empty array
Generate markup for all users:
users.map(user =>
div('.user-details', [
h1('.user-name', user.name),
h4('.user-email', user.email),
a('.user-website', {href: user.website}, user.website)
])
)
Here's the complete, working code:
function main(sources) {
const USERS_URL = 'http://jsonplaceholder.typicode.com/users/';
const getAllUsers$ = sources.DOM.select('.get-all').events('click')
.map(() => {
return {
url: USERS_URL,
method: 'GET'
};
});
const users$ = sources.HTTP
.filter(res$ => res$.request.url.indexOf(USERS_URL) === 0)
.mergeAll()
.map(res => res.body)
.startWith([])
const vtree$ = users$.map(users => {
return div('.users', [
button('.get-all', 'Get all users'),
...users.map(user =>
div('.user-details', [
h1('.user-name', user.name),
h4('.user-email', user.email),
a('.user-website', {href: user.website}, user.website)
])
)
])
});
return {
DOM: vtree$,
HTTP: getAllUsers$
};
}

Related

When I log Array there's an object inside, but when I'm trying to access that object it returns me undefined

This is my cache "component":
// imports
const useCache = (cacheName: string, url: string) => {
const cacheArray: Array<Object> = []
const getAllCaches = async () => {
const cacheNames = await caches.keys();
for (const cname of cacheNames) {
const cacheStorage = await caches.open(cname);
const cachedResponse = await cacheStorage.match(url);
const cdata = await cachedResponse?.json()
cacheArray.push({name: cname, data: cdata})
}
}
useEffect(() => {
getAllCaches()
.catch(err => console.log(err))
}, [])
const addCache = (response: any) => {
const data = new Response(JSON.stringify(response));
if ('caches' in window) {
caches.open(cacheName).then((cache) => {
cache.put(url, data);
});
}
const finalData = {name: cacheName, data: response}
cacheArray.push(finalData)
return data
}
const getCache = (cacheName?: string) => {
if (cacheName) {
return cacheArray.filter((i: any) => i.name === cacheName)[0]
}
else {
return cacheArray
}
}
const removeCache = (cacheName: string) => {
caches.delete(cacheName).then(function (res) {
return res;
});
}
return [
getCache as (cacheName?: any) => any,
addCache as (response: any) => any,
removeCache as (cacheName: any) => any
]
};
export default useCache;
Now here's code in my home component:
const [getCache, addCache, removeCache] = useCache("user", "http://localhost:3000")
useEffect(() => {
console.log(getCache())
console.log(getCache()[0])
console.log(getCache().length)
// the rest of code, not matter
and when I run home component (with vite and preact) it logging me Array, then unfedinfed, then 0 (but second should return object, and third should return 1) also I attached a screen from console.
Why it's returning me undefined and 0 length when it should return object and 1?
I'm using preact, vite, newest nodejs, typescript

It is not possible to make a loop through the array

There is a request to Firebase, using then I add new elements to the array. If you output console.log, then there are elements, but lenght = 0 and loops do not work.
export const useLastMessageDialogs = (messagesStatus: StatusDialogType): UserMessageType[] => {
const messages: string[] = [];
useEffect(() => {
const querySideDialogs = query(
collection(firestoreDb, 'questions'),
where('status', '==', messagesStatus)
);
onSnapshot(querySideDialogs, (dialogs) => {
dialogs.docChanges().forEach((dialog) => {
getDocs(
query(
collection(firestoreDb, 'questions', dialog.doc.id, 'messages'),
orderBy('timestamp', 'desc'),
limit(1)
)
).then((response) => {
response.forEach((message) => {
messages.push('bebebe');
});
});
});
});
}, [messagesStatus]);
console.log(messages); // [0: "bebebe", 1: "bebebe" length: 2]
console.log(messages.length); // 0
return [];
};
I took it to a separate service and connected it via redux-saga

react-query 3 setQueryData doesn't update the cache

I have this query:
export function useCardList() {
return useQuery(['cardList'], fetchCardList());
}
I'm using it in my component:
const { data, isLoading } = useCardList();
// delete
const handleOnDelete = (id: string) => {
const newData = {
...data,
data: data?.data.filter((card) => card.id !== id)
};
queryClient.setQueryData(['cardList'], newData);
};
but setQueryData doesn't work. what do you think? (edited)

Gatsby createPages lifecycle method running before onCreateNode method finishes adding needed node fields

I am working on a Gatsby site that uses gatsby-source-wordpress. In my gatbsy-node.js file, I use the onCreateNote lifecycle method to determine if the node is a certain WordPress custom post type, then I reach out to a separate API to get related information for the post type, use createNodeField to add it as a field, and sometimes also use createRemoteFileNode to add images sourced from the API to a field on the new node.
Now this works great most of the time, but occasionally the createPages lifecycle method runs while the image/node code is still happening (I believe). This means that the image fields don't exist yet, and the page creation fails. Then after it fails, I see a console message in the log that I set up where it notifies me that the new field has successfully been added to the node.
How can I make sure that all of those nodes are finished and the data is complete, BEFORE the createPages lifecycle runs? It seems when the client uploads a larger image, this is more likely to fail... which makes sense if I'm understanding this correctly. Here is the code from my gatsby-node.js file:
const path = require(`path`);
const slash = require(`slash`);
const fetch = require('node-fetch');
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, actions, store, cache,createNodeId, }) => {
const { createNode, createNodeField } = actions;
function getData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => response.json())
.then((data) => {
resolve(data);
});
})
}
if( node.internal.type === "wordpress__wp_location"){
const yextID = node.acf.yext_entity_id;
const yextOrthos = node.acf.location_orthodontists;
try {
const getLocation = async () => {
const data = await fetch("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareFacility")
.then(response => response.json());
// Transform the data into json
if( data && data.response && data.response.count === 1 ){
createNodeField({
node,
name: `yextLocation`,
value: data.response.entities[0]
});
} else {
console.log("NO LOCATIONS FOUND");
}
};
function getOrthos(){
let orthodontists = [];
yextOrthos.forEach( (ortho, i) => {
orthodontists.push(getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + ortho.acf.yext_entity_ortho_id + "%22%7D&entityTypes=healthcareProfessional"));
});
Promise.all(orthodontists).then( (orthoData) => {
if( orthoData.length ){
let finalOrthos = [];
orthoData.forEach( (finalOrtho, x) => {
finalOrthos.push(finalOrtho.response.entities[0]);
});
createNodeField({
node,
name: `yextOrthos`,
value: finalOrthos
});
} else {
console.log("NO DOCTORS FOUND");
}
});
}
getLocation();
getOrthos();
} catch (error) {
console.log(error);
}
}
if( node.internal.type === "wordpress__wp_orthodontist"){
const yextID = node.acf.yext_entity_ortho_id;
const wpID = node.wordpress_id;
try {
const getTextOrtho = async () => {
const data = await fetch("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareProfessional")
.then(response => response.json());
// Transform the data into json
if( data && data.response && data.response.count === 1 ){
googleProfilePhoto = data.response.entities[0].googleProfilePhoto.url;
createNodeField({
node,
name: `yextOrthodontist`,
value: data.response.entities[0]
});
if( data.response.entities[0].googleProfilePhoto && data.response.entities[0].googleProfilePhoto.url){
createNodeField({
node,
name: `yextProfilePicture`,
value: data.response.entities[0].googleProfilePhoto.url
});
let fileNode = await createRemoteFileNode({
url: data.response.entities[0].googleProfilePhoto.url, // string that points to the URL of the image
parentNodeId: node.id, // id of the parent node of the fileNode you are going to create
createNode, // helper function in gatsby-node to generate the node
createNodeId, // helper function in gatsby-node to generate the node id
cache, // Gatsby's cache
store, // Gatsby's redux store
})
// if the file was created, attach the new node to the parent node
if (fileNode) {
console.log("GOOGLE PROFILE NODE CREATED!")
node.featuredImg___NODE = fileNode.id
} else {
console.log("ERROR! fileNode not Created!");
}
} else {
console.log("NO GOOGLE PROFILE PHOTO FOUND");
}
} else {
console.log("NO ORTHODONTISTS FOUND");
}
}
const getWpLocations = async () => {
const data = await fetch(process.env.GATSBY_WP_BASEURL+ "/wp-json/custom_endpoint/v1/locations_by_orthodontist?orthodontist_id=" + wpID).then(response => response.json());
if( data ){
createNodeField({
node,
name: `wpLocations`,
value: data
});
} else {
console.log("NO ORTHODONTISTS FOUND");
}
}
getTextOrtho();
getWpLocations();
} catch (error) {
console.log(error);
}
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
{
locations: allWordpressWpLocation(filter: {status: {eq: "publish"}}) {
nodes {
id
path
acf {
location_orthodontists {
acf {
yext_entity_ortho_id
}
}
yext_entity_id
}
}
}
pages: allWordpressPage(
filter: {
wordpress_id: {nin: [177, 183, 8, 42, 44, 185, 46]}
status: {eq: "publish"}
}) {
nodes {
id
wordpress_id
path
}
}
orthodontists: allWordpressWpOrthodontist(filter: {status: {eq: "publish"}}) {
nodes {
id
path
}
}
posts: allWordpressPost(filter: {status: {eq: "publish"}}) {
nodes {
slug
id
}
}
}
`);
// Check for any errors
if (result.errors) {
throw new Error(result.errors);
}
const { locations, pages, orthodontists, posts } = result.data;
const locationTemplate = path.resolve(`./src/templates/location.js`);
const pageTemplate = path.resolve(`./src/templates/page.js`);
const orthoTemplate = path.resolve(`./src/templates/orthodontist.js`);
const postTemplate = path.resolve(`./src/templates/post.js`);
const blogTemplate = path.resolve(`./src/templates/blog.js`);
locations.nodes.forEach(node => {
let orthodontists = [];
node.acf.location_orthodontists.forEach(ortho => {
orthodontists.push(ortho.acf.yext_entity_ortho_id);
});
let orthodontistList = orthodontists.join();
createPage({
path: `${node.path}`,
component: slash(locationTemplate),
context: {
id: node.id,
yextId: node.acf.yext_entity_id,
yextOrthoIds: orthodontists
},
});
});
pages.nodes.forEach(node => {
createPage({
path: `${node.path}`,
component: slash(pageTemplate),
context: {
id: node.id,
},
});
});
orthodontists.nodes.forEach(node => {
createPage({
path: `${node.path}`,
component: slash(orthoTemplate),
context: {
id: node.id,
},
});
});
posts.nodes.forEach(node => {
createPage({
path: `${node.slug}`,
component: slash(postTemplate),
context: {
id: node.id,
},
});
});
const postsPerPage = 12;
const numPages = Math.ceil(posts.nodes.length / postsPerPage);
Array.from({ length: numPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/blog` : `/blog/page/${i + 1}`,
component: slash(blogTemplate),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
numPages,
currentPage: i + 1,
},
})
})
};
Thanks for any information you can provide! I imagine this is probably due to me still learning to use asynchronous behavior in JS, but I just can't seem to find information on how to make this happen.
Please let me know if I can explain the situation any better!
After a rewrite, this seems to have solved the issue I was having. I'll be honest, I'm still working on completely understanding the ins and outs on async/await/promises functionality in JS, but hopefully if someone encounters a similar problem, viewing this rewrite may help:
const path = require(`path`);
const slash = require(`slash`);
const fetch = require('node-fetch');
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)
exports.onCreateNode = async ({ node, actions, store, cache,createNodeId, }) => {
const { createNode, createNodeField } = actions;
const getData = async (url) => {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => response.json())
.then((data) => {
resolve(data);
});
})
}
const getLocation = async (yextID) => {
const data = await getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareFacility");
// Transform the data into json
if( data && data.response && data.response.count === 1 ){
createNodeField({
node,
name: `yextLocation`,
value: data.response.entities[0]
});
} else {
console.log("NO LOCATIONS FOUND");
}
};
const getOrthos = async (yextOrthos) => {
let orthodontists = [];
yextOrthos.forEach( (ortho, i) => {
orthodontists.push(getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + ortho.acf.yext_entity_ortho_id + "%22%7D&entityTypes=healthcareProfessional"));
});
Promise.all(orthodontists).then( (orthoData) => {
if( orthoData.length ){
let finalOrthos = [];
orthoData.forEach( (finalOrtho, x) => {
finalOrthos.push(finalOrtho.response.entities[0]);
});
createNodeField({
node,
name: `yextOrthos`,
value: finalOrthos
});
} else {
console.log("NO DOCTORS FOUND");
}
});
};
const getTextOrtho = async (yextID) => {
const data = await getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareProfessional");
if( data && data.response && data.response.count === 1 ){
createNodeField({
node,
name: `yextOrthodontist`,
value: data.response.entities[0]
});
if( data.response.entities[0].googleProfilePhoto && data.response.entities[0].googleProfilePhoto.url){
createNodeField({
node,
name: `yextProfilePicture`,
value: data.response.entities[0].googleProfilePhoto.url
});
let fileNode = await createRemoteFileNode({
url: data.response.entities[0].googleProfilePhoto.url, // string that points to the URL of the image
parentNodeId: node.id, // id of the parent node of the fileNode you are going to create
createNode, // helper function in gatsby-node to generate the node
createNodeId, // helper function in gatsby-node to generate the node id
cache, // Gatsby's cache
store, // Gatsby's redux store
});
// if the file was created, attach the new node to the parent node
if (fileNode) {
node.featuredImg___NODE = fileNode.id;
console.log("GOOGLE PROFILE NODE CREATED!")
} else {
console.log("ERROR! fileNode not Created!");
}
} else {
console.log("NO GOOGLE PROFILE PHOTO FOUND");
}
} else {
console.log("NO ORTHODONTISTS FOUND");
}
};
const getWpLocations = async (wpID) => {
const data = await getData(process.env.GATSBY_WP_BASEURL+ "/wp-json/perch_endpoint/v1/locations_by_orthodontist?orthodontist_id=" + wpID);
if( data ){
createNodeField({
node,
name: `wpLocations`,
value: data
});
} else {
console.log("NO ORTHODONTISTS FOUND");
}
}
if( node.internal.type === "wordpress__wp_location"){
const yextID = node.acf.yext_entity_id;
const yextOrthos = node.acf.location_orthodontists;
try {
await getLocation(yextID);
await getOrthos(yextOrthos);
} catch (error) {
console.log(error);
}
}
if( node.internal.type === "wordpress__wp_orthodontist"){
const yextID = node.acf.yext_entity_ortho_id;
const wpID = node.wordpress_id;
try {
await getTextOrtho(yextID);
await getWpLocations(wpID);
} catch (error) {
console.log(error);
}
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
{
locations: allWordpressWpLocation(filter: {status: {eq: "publish"}}) {
nodes {
id
path
acf {
location_orthodontists {
acf {
yext_entity_ortho_id
}
}
yext_entity_id
}
}
}
pages: allWordpressPage(
filter: {
wordpress_id: {nin: [177, 183, 8, 42, 44, 185, 46]}
status: {eq: "publish"}
}) {
nodes {
id
wordpress_id
path
}
}
orthodontists: allWordpressWpOrthodontist(filter: {status: {eq: "publish"}}) {
nodes {
id
path
}
}
posts: allWordpressPost(filter: {status: {eq: "publish"}}) {
nodes {
slug
id
}
}
}
`);
// Check for any errors
if (result.errors) {
throw new Error(result.errors);
}
const { locations, pages, orthodontists, posts } = result.data;
const locationTemplate = path.resolve(`./src/templates/location.js`);
const pageTemplate = path.resolve(`./src/templates/page.js`);
const orthoTemplate = path.resolve(`./src/templates/orthodontist.js`);
const postTemplate = path.resolve(`./src/templates/post.js`);
const blogTemplate = path.resolve(`./src/templates/blog.js`);
locations.nodes.forEach(node => {
let orthodontists = [];
node.acf.location_orthodontists.forEach(ortho => {
orthodontists.push(ortho.acf.yext_entity_ortho_id);
});
let orthodontistList = orthodontists.join();
createPage({
path: `${node.path}`,
component: slash(locationTemplate),
context: {
id: node.id,
yextId: node.acf.yext_entity_id,
yextOrthoIds: orthodontists
},
});
});
pages.nodes.forEach(node => {
createPage({
path: `${node.path}`,
component: slash(pageTemplate),
context: {
id: node.id,
},
});
});
orthodontists.nodes.forEach(node => {
createPage({
path: `${node.path}`,
component: slash(orthoTemplate),
context: {
id: node.id,
},
});
});
posts.nodes.forEach(node => {
createPage({
path: `${node.slug}`,
component: slash(postTemplate),
context: {
id: node.id,
},
});
});
const postsPerPage = 12;
const numPages = Math.ceil(posts.nodes.length / postsPerPage);
Array.from({ length: numPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/blog` : `/blog/page/${i + 1}`,
component: slash(blogTemplate),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
numPages,
currentPage: i + 1,
},
})
})
};

How to request data sequentially in Cycle.js?

I’m new to reactive programming and toying around with cycle.js, trying to implement who to follow box from this tutorial. But I understood that for proper implementation (and learning purposes) I don’t have one piece of data: full user name. I can get it by sequentially getting users and then full user data from server. In imperative style I would do something like this:
fetch(`https://api.github.com/users`)
.then(data => data.json())
.then(users => fetch(users[0].url))
.then(data => data.json())
.then(/* ... work with data ... */)
But how do I do it in cycle?
I’m using fetch driver and trying something like this:
function main({ DOM, HTTP }) {
const users = `https://api.github.com/users`;
const refresh$ = DOM.select(`.refresh`).events(`click`)
const response$ = getJSON({ key: `users` }, HTTP)
const userUrl$ = response$
.map(users => ({
url: R.prop(`url`, R.head(users)),
key: `user`,
}))
.startWith(null)
const request$ = refresh$
.startWith(`initial`)
.map(_ => ({
url: `${users}?since=${random(500)}`,
key: `users`,
}))
.merge(userUrl$)
const dom$ = ...
return {
DOM: dom$,
HTTP: request$,
};
}
where getJSON is
function getJSON(by, requests$) {
const type = capitalize(firstKey(by));
return requests$
[`by${type}`](firstVal(by))
.mergeAll()
.flatMap(res => res.json());
And I’m always getting some cryptic (for me) error like: TypeError: Already read. What does it mean and how do I handle it properly?
You were quite close. You just need to remove startWith(null) as a request, and grabbing the second response (you were missing the getJSON for that one).
function main({ DOM, HTTP }) {
const usersAPIPath = `https://api.github.com/users`;
const refresh$ = DOM.select(`.refresh`).events(`click`);
const userResponse$ = getJSON({ key: `user` }, HTTP);
const listResponse$ = getJSON({ key: `users` }, HTTP);
const userRequest$ = listResponse$
.map(users => ({
url: R.prop(`url`, R.head(users)),
key: `user`,
}));
const listRequest$ = refresh$
.startWith(`initial`)
.map(_ => ({
url: `${usersAPIPath}?since=${Math.round(Math.random()*500)}`,
key: `users`,
}));
const dom$ = userResponse$.map(res => h('div', JSON.stringify(res)));
return {
DOM: dom$,
HTTP: listRequest$.merge(userRequest$),
};
}
Because inquiring minds want to know...here's a complete working example:
import Cycle from '#cycle/rx-run';
import {div, button, makeDOMDriver} from '#cycle/dom';
import {makeFetchDriver} from '#cycle/fetch';
import R from 'ramda'
function main({DOM, HTTP}) {
const usersAPIPath = 'https://api.github.com/users';
const refresh$ = DOM.select('button').events('click');
const userResponse$ = getJSON({ key: 'user' }, HTTP);
const listResponse$ = getJSON({ key: 'users' }, HTTP);
const userRequest$ = listResponse$
.map(users => ({
url: R.prop('url', R.head(users)),
key: 'user',
}));
const listRequest$ = refresh$
.startWith('initial')
.map(_ => ({
url: `${usersAPIPath}?since=${Math.round(Math.random()*500)}`,
key: 'users',
}));
const dom$ = userResponse$.map(res => div([
button('Refresh'),
div(JSON.stringify(res))
]));
return {
DOM: dom$,
HTTP: listRequest$.merge(userRequest$)
};
function getJSON(by, requests$) {
return requests$.byKey(by.key)
.mergeAll()
.flatMap(res => res.json());
}
}
Cycle.run(main, {
DOM: makeDOMDriver('#main-container'),
HTTP: makeFetchDriver()
});
It took me a while to figure out HTTP was the #cycle/fetch driver, and NOT the #cycle/http driver. Next, a bit of searching turned the ramda npm library providing prop and head methods.

Categories

Resources