Sanity.io Fetching Through References - reactjs

I'm trying to figure out how to access a full object reference through Sanity.io. Example code:
const fetchExperience = async () => {
const data = await sanityClient.fetch(`*[_type == 'experience'] {
company,
title,
'techStack': stack[]->technology,
'imageUrl': image.asset->url,
}`);
return data;
};
I have another schema declared called Skill that contains two fields: technology, and icon. This Experience schema that I am working with above and references the Skill schema. Using this post, I was able to figure out how to grab a singular field through the reference and populate a field called techStack. However, I want to take this one step further: how can I write a GROQ that returns me an array of objects that might look like this?:
[
{ technology: 'React.js', icon: 'pretend-this-is-a-link-to-an-asset' },
...
]
Thanks in advance!

You can do this to get the specific fields:
"techStack": stack[]-> { technology, icon }

Related

Updating multiple queries in useMutation Apollo-client without refetching them

I'm executing this mutation in my NewBook component:
const [addBook] = useMutation(ADD_BOOK, {
update: (cache, response) => {
cache.updateQuery({ query: ALL_BOOKS }, ({ allBooks }) => {
return { allBooks: allBooks.concat(response.data.addBook) };
});
},
refetchQueries: [{ query: ALL_AUTHORS }, { query: ALL_GENRES }],
options: {
awaitRefetchQueries: true,}});
Instead of having to refetch those two queries, I'd like to update them like ALL_BOOKS - but could not find any example in the docs. Does anyone know a way to accomplish that?
Thank you.
What you need to do is make multiple cache updates based on response data.
Once you add your new book to the query, the next step is to fetch all authors.
cache.updateQuery({ query: ALL_BOOKS }, ({ allBooks }) => {
return { allBooks: allBooks.concat(response.data.addBook) };
});
//Get all authors
const existingAuthors = cache.readQuery({
query: ALL_AUTHORS,
//variables: {}
});
//If we never called authors, do nothing, as the next fetch will fetch updated authors. This might be a problem in the future is some cases, depending on how you fetch data. If it is a problem, just rework this to add newAuthor to the array, like allAuthors: [newAuthor]
if(!existingAuthors.?length) {
return null
}
The next thing is that we need to compare the new book's author with existing authors to see if a new author was added.
//continued
const hasAuthor = existingAuthors.find(author => author.id === response.data.createBook.id)
//Double check response.data.createBook.id. I don't know what is returned from response
//If author already exists, do nothing
if(hasAuthor) {
return null
}
//Get a new author. Make sure that this is the same as the one you fetch with ALL_AUTHORS.
const newAuthor = {
...response.data.createBook.author //Check this
}
cache.writeQuery({
query: ALL_AUTHORS,
//variables: {}
data: {
allAuthors: [newAuthor, ...existingAuthors.allAuthors]
},
});
Then continue the same with ALL_GENRES
Note:
If you called ALL_GENERES or ALL_BOOKS with variables, you MUST put the SAME variables in the write query and read query. Otherwise Apollo wont know what to update
Double check if you are comparing numbers or strings for authors and genres
Double check all of the variables I added, they might be named different at your end.
Use console.log to check incoming variables
You can probably make this in less lines. There are multiple ways to update cache
If it doesn't work, console.log cache after the update and see what exactly did apollo do with the update (It could be missing data, or wrong variables.)
Add more checks to ensure some cases like: response.data returned null, authors already fetched but there are none, etc...

Firestore - How to retrieve all subcollections of a collection

I have a question regarding Firestore,
I created a "category" collection in which I have several documents, I created a "construction sites" sub-collection!
I want to retrieve all my sub collections from each category, but when I try to do like this:
useEffect(() => {
const listConstruction = [];
db.collection('category').get().then((collectionCategory) => {
collectionCategory.forEach((doc) => {
doc.ref.collection('construction').get().then((collectionConstruction) => {
collectionConstruction.forEach((doc1) => {
listConstruction.push({
idCategory: doc.id,
libelleCategory: doc.data().libelle,
title: doc1.data().title,
description: doc1.data().description,
});
});
});
});
}).finally(() => {
setConstruction(listConstruction);
});
}, []);
The problem is that it hasn't finished pushing the constructs it passes into my finally!
Having no experience with NoSQL and Firestore yet, I would have liked if I'm not completely wrong about its use and if so how to fix it?
If I correctly understand your problem, you need to use a Collection Group query.
As explained in the doc:
A collection group consists of all collections with the same ID. By default, queries retrieve results from a single collection in your
database. Use a collection group query to retrieve documents from a
collection group instead of from a single collection.
Side note: If you want to call asynchronous methods in a forEach loop, you should use Promise.all().

How to sort how you want the the data from firestore

I have a firestore with lessons, see the picture below, I get from the firestore the title property from each object and display it in the browser, but everytime I refresh the website the lessons are sorted by how they want, why is that happening? I want to sort them how I want, I want to start with 'Introduction' and so on, how can I do that? I think the orderBy() is not working here.
As you see in the image above, the order in the firestore is alphabetical, but in my page is sorted by its own, see the picture below.
I want the result to be by in a specific order, for example we have the following titles, these titles are from the firestore: "Display", "Introduction", "Alignment", my problem is that these 3 titles are in a new order every time I refresh the website, I want them to be: "Introduction", "Alignment", "Display". In my case I have more titles but this is what's happening, I don't know how to align them how I want or even alphabetical if is possible.
Below is the code that I used to get the data from firestore:
useEffect(() => {
db.collection("users")
.doc(`${user.uid}`)
.get()
.then((doc) => {
const allData = { ...doc.data(), id: doc.id };
const intoArray = Object.entries(allData);
intoArray.sort(); // I used sort here because I had the same problem
// (every time a new order) with the
// data when I converted it to an array
const getCSSLessons = intoArray[0][1];
const cssData = Object.values(getCSSLessons);
setCss(cssData);
const getHTMLLessons = intoArray[1][1];
const htmlData = Object.values(getHTMLLessons);
setHtml(htmlData);
const getResLessons = intoArray[3][1];
const resData = Object.values(getResLessons);
setRes(resData);
})
.catch((error) => console.log(error));
}, [user]);
I tried using sort(), for a variable (htmlData) but its not working.
Also, I use map() to display them, if this helps you to answer to my question.
If you use sort without any argument, it will sort array elements alphabetically. It looks like your array elements are arrays, which will end with unexpected behaviors. Use sort argument to ensure it uses your own sorting rules. For example:
const intoArray = Object
.entries(allData)
// I don't know what should be the sorting algorithm
// As an example, I consider each element (`a` and `b`) to
// be arrays and compare both first element as a Number
.sort( (a, b) => a[0] - b[0])
Edit
A more secure way to find elements in an array is to use find:
const getCSSLessons = intoArray[0]
.find( element => element.name === 'CSS Lessons');
I was doing something unnecessary as you see in the first picture I had a main object css and in that object I had sub-objects like alignment and in the sub object I had the properties that I want to display, that sub object was unncessary, istead of sub objects with a pre defined name, I let the firebase to count the sub objects and add as a name a number and the order is the same as I wanted to be.
The code that I used to add data to firebase:
fire
.auth()
.createUserWithEmailAndPassword(email, password)
.then((cred) => {
return db
.collection("users")
.doc(cred.user.uid)
.set({
css: [
{
title: "Introduction",
path: "/css3/introduction",
},
{
title: "Priority",
path: "/css3/priority",
},
],
});

Async array without using anything async?

It drives me crazy. I have a really simple "problem" and it took me hours and still have no idea whats going on.
I have a child service which inherits from a parent service (I'm using ES6). The constructor takes an 1 argument called options. options will be assigned to this._defaults.
Now before I pass the options into my object (new Service(options)) I populate options with some data. To keep it simple, my current options object looks like this:
const options = {
types: []
}
Now I add some stuff into the types array, like this:
const Types = {
standard: {
some: 'data'
},
freeroll: {
some: 'data'
},
mainevent: {
some: 'data'
},
qualifier: {
some: 'data'
}
};
angular.forEach(Types, (val, key) => {
options.types[key] = true;
});
I assign my service to the scope like this:
$scope.service = new Service(options)
and output the service using console. The console now says the value of _defaults.types is Array(0). When I click on the array the correct values will be shown but the scope is not aware of that.
How is that? Doesn't Array(0) mean that at the time of the console.log() the array wasn't filled with any values but has been later? Like an async function would do?
Here is a plunk of my problem.
The problem is that types is an array and you're treating it like a plain Object. You can solve this one of two ways.
First just change types to an Object:
const options = {
types: {}
};
Or, if you need an Array, change how you're adding items to the array:
angular.forEach(Types, (val, key) => {
options.types.push({
type: key,
value: val
});
});
Note that this is just one way of turning the object into an array, the data structure you end up with is up to you.

Displaying data from Firebase in React without arrays

I am new to both React and Firebase. I struggled a bit to get data from the database, even though the instructions on the Firebase website were pretty straightforward.
I managed to print data in the view by using this code:
Get data from DB and save it in state:
INSTRUMENTS_DB.once('value').then(function(snapshot) {
this.state.instruments.push(snapshot.val());
this.setState({
instruments: this.state.instruments
});
From Firebase, I receive and Object containing several objects, which correspond to the differen instruments, like shown in the following snippet:
Object {
Object {
name: "Electric guitar",
image: "img/guitar.svg"
}
Object {
name: "Bass guitar",
image: "img/bass.svg"
}
// and so on..
}
Currently, I print data by populating an array like this:
var rows = [];
for (var obj in this.state.instruments[0]) {
rows.push(<Instrument name={this.state.instruments[0][obj].name}
image={this.state.instruments[0][obj].image}/>);
}
I feel like there's a better way to do it, can somedody give a hint? Thanks
I user firebase a lot and mu solution is little ES6 helper function
const toArray = function (firebaseObj) {
return Object.keys(firebaseObj).map((key)=> {
return Object.assign(firebaseObj[key], {key});
})
};
I also assign the firebase key to object key property, so later I can work with the keys.
The native map function only works for arrays, so using directly it on this object won't work.
What you can do instead is:
Call the map function on the keys of your object using Object.keys():
getInstrumentRows() {
const instruments = this.state.instruments;
Object.keys(instruments).map((key, index) => {
let instrument = instruments[key];
// You can now use instrument.name and instrument.image
return <Instrument name={instrument.name} image={instrument.image}/>
});
}
Alternatively, you can also import the lodash library and use its map method which would allow you to refactor the above code into:
getInstrumentRowsUsingLodash() {
const instruments = this.state.instruments;
_.map(instruments, (key, index) => {
let instrument = instruments[key];
// You can now use instrument.name and instrument.image
return <Instrument name={instrument.name} image={instrument.image}/>
});
}
Side note:
When you retrieve you data from Firebase you attempt to update the state directly with a call on this.state.instruments. The state in React should be treated as Immutable and should not be mutated with direct calls to it like push.
I would use map function:
_getInstrumentRows() {
const instruments = this.state.instruments[0];
if (instruments) {
return instruments.map((instrument) =>
<Instrument name={instrument.name}
image={instrument.image}/>);
}
}
In your render() method you just use {_getInstrumentRows()} wherever you need it.

Resources