Mapping components to data with nesting - reactjs

I am looking for a way to handle object data from the server, given data I'd like to pass all the data into a thing, and specify one thing and return that thing, for instance if I have a organization object and I wanted the createdAt date formatted. I would just be able to pass <data.Organization.createdAt {...organization}>. Is there anything that does this?
Here's the data
const contact = {
name: 'Thomas',
uuid: 1234
}
const orgnization = {
contact,
}
const scopedOrgniazation = {
orgnization
}
Here's the Components
import {RF, SalmonLink} from '../index'
const Contact = {
Name: ({ name }) => (name) ? <RF>{name}</RF> : <NA/>,
NameLink: ({ name, uuid }) => (uuid && name) ? <SalmonLink to={`/admin/organizations/${uuid}`}>{name}<SalmonLink> : <NA/>
}
const Organization = {
Contact
}
const ScopedOrganization = {
Organization
}
Desired Components API
I was thinking this may be possible by using something like lodash's flow or flowRight.
<Contact.Name {...contact}>
<Organization.Contact.Name {...organization}>
<ScopedOrganization.Organization.Contact.Name {...scopedOrgniazation}>

Related

Passing ID in GraphQL query not returning data

so i'm trying to use Apollo GraphQL with React to get specific product data by its ID, but it seems to be returning undefined. I read the Apollo docs and researched, so I'm not sure what I'm doing wrong. Also, I'm able to return data from other queries that don't require an ID (like all products, for instance). Would greatly appreciate some help!
Query
export const PRODUCT = gql`
query GetProduct($itemID: String!) {
product(id: $itemID) {
id
name
inStock
gallery
description
category
attributes {
id
name
type
items {
displayValue
value
}
}
prices {
currency {
label
symbol
}
}
brand
}
}
`;
This is where I try to return data using the ID, but to no avail:
let myID = "ps-5";
const { productLoading, productError, productData } = useQuery(PRODUCT, {
variables: { itemID: myID },
});
useEffect(() => {
if (productData) {
console.log("data: " + productData) // logs nothing. "Undefined" when if statement is removed
}
}, [])
It looks like the React client for Apollo uses the same API for useQuery as for Vue (with which I'm more familiar), in which case it should be used like this:
useQuery(PRODUCT, { itemID: myID })
(not { variables : { itemID : myID }})
I would have expected the backend to return an error though, because $itemID is declared as non-nullable.
It seems that you are destructing the object that useQuery() returns with the wrong object keys.
// instead of
const { productLoading, productError, productData } = '...'
// you can either use the regular keys as variables
const { loading, error, data } = '...'
// or assign aliases (useful when you use more queries on the same page)
// this way you can use the same variables as in your example
const { loading:productLoading, error:productError, data:productData } = '...'

GraphQL on component load, Query will sometimes return populated properties as null

I am using graphQl with react with apollo client and mongoose. Sometimes when I click on a component. rand om data will return from this useQuery as null.
// must define novel as a state to use useEffect correctly
const [novel, setNovel] = useState({});
const { loading, data } = useQuery(GET_NOVEL, {
variables: { _id: novelId }
});
// use effect ensures that all novel data is completely loaded
// before rendering the SingleNovel page
useEffect(() => {
console.log(data?.novel);
// if there's data to be stored
if (data) {
setNovel(data.novel)
}
}, [data, loading, novel]);
export const GET_NOVEL = gql`
query getNovel($_id: ID!) {
novel(_id: $_id) {
_id
title
description
penName
user {
_id
username
email
}
favorites{
_id
}
createdAt
reviews {
_id
reviewText
rating
createdAt
user{
_id
username
}
}
chapterCount
reviewCount
}
}
`
Specifically, the novel.user.username and reviews.rating property come back as null. On reload of the page however, the data seems to populate the fields normally.
How can I fix this?
Heres the resolver
novel: async (parent, { _id }) => {
// returns single novel from the novel id given
const novel = await Novel.findOne({ _id })
.populate('user')
// populate the reviews for the novel but also populate
// the info within the reviews of the user who made each review.
.populate({
path: 'reviews',
populate: {path: 'user'}
})
.exec();
console.log(novel)
return novel;
},

Removing an object from an array in Firestore

I'm just getting into Firebase, and really digging it so far, but I'm having a bit of an issue using a Cloud Function to remove an item from an array in a collection. In the app I'm using, users have a document in a users collection that contains user-specific data that doesn't fit within the authentication model. The data looks like this:
Screenshot of user collection structure.
(It's a fitness-related app, hence the array being called 'workouts')
The user has the ability to create a workout, and when they do, after it's created, a cloud function watching for new workouts adds the ID of the newly created workout to the user document, appending it to the workouts array using arrayUnion:
exports.addWorkoutToAuthorsListOfWorkouts = functions.firestore
.document('workouts/{workoutId}')
.onCreate((snap, context) => {
const id = context.params.workoutId
const name = snap.data().title
const uid = snap.data().author
const workout_reference = { id: id, name: name, uid: uid }
const workoutsRef = admin.firestore().collection("users").doc(uid)
return workoutsRef.update({
workouts: admin.firestore.FieldValue.arrayUnion(workout_reference)
})
})
This works fine, but I want the user to be able to delete workouts, and I didn't add a string to the workouts array, but instead an object like this:
{ id: "1214", name: "My workout", uid: "123asd" }
Now, for this exact use-case I could delete the array in my ReactJS app and then do an update, but I'm going to be adding the ability for users to "like" the workouts of other users, which will result in a reference to the workout being added to "workouts" in their personal user document. So if I, as a creator of a workout, delete my workout, I need to be able to delete it from the workouts array of any user who has it. Using arrayRemove didn't work because (I assume) I couldn't pass the object to be removed.
What's the best-practices way to do this? I made this attempt:
exports.removeWorkoutFromAuthorsListOfWorkouts = functions.firestore
.document('workouts/{workoutId}')
.onDelete((snap, context) => {
const id = context.params.workoutId
const uid = snap.data().author
const name = snap.data().name
let newWorkouts = new Array()
admin.firestore().collection("users").doc(uid).collection("workouts").get().then((querySnapshot) => {
querySnapshot.forEach((doc: any) => {
if (doc.id !== id) {
newWorkouts.push(doc.data())
}
});
});
const workoutsRef = admin.firestore().collection("users").doc(uid)
return workoutsRef.update({
workouts: newWorkouts
})
})
But Firebase didn't like it at all, and I'm new enough to the platform that I realize this is most likely due to the result of a knowledge gap, rather than any problem with Firestore or Cloud Functions.
Any help you could provide would be very much appreciated. Cheers!
UPDATE: Got it working, with the following code:
exports.removeWorkoutFromSubscribersListOfWorkouts = functions.firestore
.document('workouts/{workoutId}')
.onDelete((snap, context) => {
const workoutId = context.params.workoutId
const subscribers = snap.data().subscribers
let newWorkouts = new Array()
subscribers.forEach(subscriber => {
let userRef = admin.firestore().collection("users").doc(subscriber)
return userRef.get().then(doc => {
return userRef.update({
workouts: doc.data().workouts.filter(workout => workout.id !== workoutId)
})
})
})
.catch(() => {
console.log("error")
})
})

GatsbyJS getting data from Restful API

I am new in both React and GatsbyJS. I am confused and could not make figuring out in a simple way to load data from third-party Restful API.
For example, I would like to fetch data from randomuser.me/API and then be able to use the data in pages.
Let’s say something like this :
import React from 'react'
import Link from 'gatsby-link'
class User extends React.Component {
constructor(){
super();
this.state = {
pictures:[],
};
}
componentDidMount(){
fetch('https://randomuser.me/api/?results=500')
.then(results=>{
return results.json();
})
.then(data=>{
let pictures = data.results.map((pic,i)=>{
return(
<div key={i} >
<img key={i} src={pic.picture.medium}/>
</div>
)
})
this.setState({pictures:pictures})
})
}
render() {
return (<div>{this.state.pictures}</div>)
}
}
export default User;
But I would like to get the help of GraphQL in order to filter & sort users and etc…..
Could you please help me to find the sample to how I can fetch data and insert them into GraphQL on gatsby-node.js?
If you want to use GraphQL to fetch your data, you have to create a sourceNode. The doc about creating a source plugin could help you.
Follow these steps to be able to query randomuser data with GraphQL in your Gatsby project.
1) Create nodes in gatsby-node.js
In your root project folder, add this code to gatsby-node.js:
const axios = require('axios');
const crypto = require('crypto');
exports.sourceNodes = async ({ actions }) => {
const { createNode } = actions;
// fetch raw data from the randomuser api
const fetchRandomUser = () => axios.get(`https://randomuser.me/api/?results=500`);
// await for results
const res = await fetchRandomUser();
// map into these results and create nodes
res.data.results.map((user, i) => {
// Create your node object
const userNode = {
// Required fields
id: `${i}`,
parent: `__SOURCE__`,
internal: {
type: `RandomUser`, // name of the graphQL query --> allRandomUser {}
// contentDigest will be added just after
// but it is required
},
children: [],
// Other fields that you want to query with graphQl
gender: user.gender,
name: {
title: user.name.title,
first: user.name.first,
last: user.name.last,
},
picture: {
large: user.picture.large,
medium: user.picture.medium,
thumbnail: user.picture.thumbnail,
}
// etc...
}
// Get content digest of node. (Required field)
const contentDigest = crypto
.createHash(`md5`)
.update(JSON.stringify(userNode))
.digest(`hex`);
// add it to userNode
userNode.internal.contentDigest = contentDigest;
// Create node with the gatsby createNode() API
createNode(userNode);
});
return;
}
I used axios to fetch data so you will need to install it: npm install --save axios
Explanation:
The goal is to create each node for each piece of data you want to use.
According to the createNode documentation, you have to provide an object with few required fields (id, parent, internal, children).
Once you get the results data from the randomuser API, you just need to create this node object and pass it to the createNode() function.
Here we map to the results as you wanted to get 500 random users https://randomuser.me/api/?results=500.
Create the userNode object with the required and wanted fields.
You can add more fields depending on what data you will want to use in your app.
Just create the node with the createNode() function of the Gatsby API.
2) Query your data with GraphQL
Once you did that, run gatsby develop and go to http://localhost:8000/___graphql.
You can play with GraphQL to create your perfect query. As we named the internal.type of our node object 'RandomUser', we can query allRandomUser to get our data.
{
allRandomUser {
edges {
node {
gender
name {
title
first
last
}
picture {
large
medium
thumbnail
}
}
}
}
}
3) Use this query in your Gatsby page
In your page, for instance src/pages/index.js, use the query and display your data:
import React from 'react'
import Link from 'gatsby-link'
const IndexPage = (props) => {
const users = props.data.allRandomUser.edges;
return (
<div>
{users.map((user, i) => {
const userData = user.node;
return (
<div key={i}>
<p>Name: {userData.name.first}</p>
<img src={userData.picture.medium} />
</div>
)
})}
</div>
);
};
export default IndexPage
export const query = graphql`
query RandomUserQuery {
allRandomUser {
edges {
node {
gender
name {
title
first
last
}
picture {
large
medium
thumbnail
}
}
}
}
}
`;
That is it!
Many thanks, this is working fine for me, I only change small parts of the gastbyjs-node.js because it makes an error when use sync & await, I think I need change some section of a build process to use babel to allow me to use sync or await.
Here is the code which works for me.
const axios = require('axios');
const crypto = require('crypto');
// exports.sourceNodes = async ({ boundActionCreators }) => {
exports.sourceNodes = ({boundActionCreators}) => {
const {createNode} = boundActionCreators;
return new Promise((resolve, reject) => {
// fetch raw data from the randomuser api
// const fetchRandomUser = () => axios.get(`https://randomuser.me/api/?results=500`);
// await for results
// const res = await fetchRandomUser();
axios.get(`https://randomuser.me/api/?results=500`).then(res => {
// map into these results and create nodes
res.data.results.map((user, i) => {
// Create your node object
const userNode = {
// Required fields
id: `${i}`,
parent: `__SOURCE__`,
internal: {
type: `RandomUser`, // name of the graphQL query --> allRandomUser {}
// contentDigest will be added just after
// but it is required
},
children: [],
// Other fields that you want to query with graphQl
gender: user.gender,
name: {
title: user.name.title,
first: user.name.first,
last: user.name.last
},
picture: {
large: user.picture.large,
medium: user.picture.medium,
thumbnail: user.picture.thumbnail
}
// etc...
}
// Get content digest of node. (Required field)
const contentDigest = crypto.createHash(`md5`).update(JSON.stringify(userNode)).digest(`hex`);
// add it to userNode
userNode.internal.contentDigest = contentDigest;
// Create node with the gatsby createNode() API
createNode(userNode);
});
resolve();
});
});
}
The accepted answer for this works great, just to note that there's a deprecation warning if you use boundActionCreators. This has to be renamed to actions to avoid this warning.
You can get data at the frontend from APIs using react useEffect. It works perfectly and you will no longer see any error at builtime
const [starsCount, setStarsCount] = useState(0)
useEffect(() => {
// get data from GitHub api
fetch(`https://api.github.com/repos/gatsbyjs/gatsby`)
.then(response => response.json()) // parse JSON from request
.then(resultData => {
setStarsCount(resultData.stargazers_count)
}) // set data for the number of stars
}, [])
The answers given above work, except the query in step 2 seems to only return one node for me. I can return all nodes by adding totalCount as a sibling of edges. I.e.
{
allRandomUser {
totalCount
edges {
node {
id
gender
name {
first
last
}
}
}
}
}

Look-ups in Redux Reducers

Let's say I have the following state:
state = {
products: {
50: {
sku: "000",
name: "A Product",
category: 123,
...
}
},
categories: {
123: {
name: "Some Category",
parentCategory: 100,
department: "Electronics"
}
},
filteredProducts: [50]
}
I want to be able to filter products based on categories. However, I need to filter based on multiple properties of categories. i.e. I might want to get all categories within the Electronics department or I might want to get a category with id 123 and all it's sub-categories.
This is a bit of a contrived example that closely matches what I'm trying to achieve but it's a bit easier to understand, so please bear with me. I'm aware that in this specific instance, I could probably use something like reselect, but assuming that I needed to do a category lookup for a products reducer, what would my options be?
You can use reselect as you mentioned, and make some selectors with parameter the re-use these selectors from categories in products to be as follow:
Make your category/selectors file as follow:
import { createSelector } from 'reselect';
const categoriesSelector = state => state.categories;
const selectCategoryById = id => {
return createSelector(
categoriesSelector,
categories => categories[id]
);
}
const selectCategoryByName = name => {
return createSelector(
categoriesSelector,
categories => categories.filter(c => c.name === name)
);
}
export default {
categoriesSelector,
selectCategoryById,
selectCategoryByName,
}
Meanwhile, in product/selector you can import both category and product selector files as follow:
import { createSelector } from 'reselect';
import { selectCategoryById } from './category/selectors';
const productsSelector = state => state.products;
const selectProductByCategoryId = id => {
return createSelector(
productsSelector,
selectCategoryById,
(products, categories) => products.filter(p.category.indexOf(id) > -1)
);
}
export default {
productsSelector,
selectProductByCategoryId,
}
And in product/reducer, you can import both selectors and return the new changed state based on category logic.

Resources