Is there a standard way to add presentation information to Vue? - javascript

Let's say I have a Vue component that has the following data, which has been retrieved from an API:
data: () => ({
books: [
{name: 'The Voyage of the Beagle', author: 'Charles Darwin'},
{name: 'Metamorphoses', author: 'Ovid'},
{name: 'The Interpretation of Dreams', author: 'Sigmund Freud'},
],
}),
I would like to store presentation variables for each of these books, e.g. an open boolean to determine whether the book is open or not. I don't want the API to return these variables though, as I don't want the API to be cluttered with presentation data.
Is there a standard way of doing this in Vue?

you can add the presentation data after receive the information from the API:
...
data: () => ({ books: [] });
...
methods: {
// API call to get the books
async requestBooks() {
// TODO: add try catch block
const books = await getBooks(); // Your API call
this.books = addPresentationInformation(books);
},
addPresentationInformation(books) {
return books.map(book => {
return {
...book, // default format from API (name, author)
open: false, // add the open variable to the object
reading: false,
currentPage: 0
}
});
}
},
created() {
this.requestBooks(); // Call the api on created hook to initialize the books data prop
}
You can add many presentation variables as you want, I recommend use vuex to store the books and their presentation variables, that way you can save information in the local storage for each book, so after restart the app, you can know if some book is currently being reading or is open.

I would personally maintain another array that contains some state relational to each book rather than trying to mutate the API response data. That's just me though.

Probably another way is to copy object and modify it and keep original response data
data(){
let data = Object.assign({}, this);
// add necessary presentation data
return data;
}

I now use normalizr to process and flatten responses from the backend API, and this library provides a means to add extra data. For example, the following schema adds the hidden data attribute.
const taskSchema = new schema.Entity(
'tasks',
{},
{
// add presentation data
processStrategy: (value) => ({
...value,
hidden: false
}),
}
);

Related

How can I structure my data better in the redux state?

I'm new to this so it might be a very beginner question but I hope there might be a solution to this for the people who are good.
songData() here returns an array of lots of song objects.
That's why the [0] here in currentSong: { currentSongData: songData()[0] },
This is my redux state:
const initState = {
allSongs: songData(),
currentSong: { currentSongData: songData()[0] },
isPlaying: false,
isLibraryOpen: false,
songTimer: [
{
currentTime: 0,
duration: 0,
},
],
};
Whenever I have to access anything in the currentSong, I have to write something like:
currentSong.currentSongData.name
or if I define the currentSong as currentSong: [songData()[0]], then like:
currentSong[0].name
Is there a better way to define the redux state where I don't have to write such long things when I access the data?
I want to access it by writing
currentSong.name
BTW this is the structure of the song object.
{
name: 'Cold Outside',
cover: 'https://chillhop.com/wp-content/uploads/2020/09/09fb436604242df99f84b9f359acb046e40d2e9e-1024x1024.jpg',
artist: 'Nymano',
},
You can use property spread notation
currentSong: { ...songData()[0] }
I would favor to store currentSong's key id which you could extract then its data content from allSongs. otherwise, you would be duplicating data something you better avoid. in this way, at initial state you would have currentSongId: songData()[0].id
so how would you handle that at your functions? you can use useSelector. this function allows you to filter your data so you consume it properly. at your function component you would extract like:
// at your component function
const selectedSong = useSelector(state => state.allSongs.filter(({ id }) => id === state.currentSongId))
if you read further below at useSelector docs, you could go a step further and memoize its value to avoid unnecessary rerenders in combination with another library reselect.

Resolve Custom Types at the root in GraphQL

I feel like I'm missing something obvious. I have IDs stored as [String] that I want to be able to resolve to the full objects they represent.
Background
This is what I want to enable. The missing ingredient is the resolvers:
const bookstore = `
type Author {
id: ID!
books: [Book]
}
type Book {
id: ID!
title: String
}
type Query {
getAuthor(id: ID!): Author
}
`;
const my_query = `
query {
getAuthor(id: 1) {
books { /* <-- should resolve bookIds to actual books I can query */
title
}
}
}
`;
const REAL_AUTHOR_DATA = [
{
id: 1,
books: ['a', 'b'],
},
];
const REAL_BOOK_DATA = [
{
id: 'a',
title: 'First Book',
},
{
id: 'b',
title: 'Second Book',
},
];
Desired result
I want to be able to drop a [Book] in the SCHEMA anywhere a [String] exists in the DATA and have Books load themselves from those Strings. Something like this:
const resolve = {
Book: id => fetchToJson(`/some/external/api/${id}`),
};
What I've Tried
This resolver does nothing, the console.log doesn't even get called
const resolve = {
Book(...args) {
console.log(args);
}
}
HOWEVER, this does get some results...
const resolve = {
Book: {
id(id) {
console.log(id)
return id;
}
}
}
Where the console.log does emit 'a' and 'b'. But I obviously can't scale that up to X number of fields and that'd be ridiculous.
What my team currently does is tackle it from the parent:
const resolve = {
Author: {
books: ({ books }) => books.map(id => fetchBookById(id)),
}
}
This isn't ideal because maybe I have a type Publisher { books: [Book]} or a type User { favoriteBooks: [Book] } or a type Bookstore { newBooks: [Book] }. In each of these cases, the data under the hood is actually [String] and I do not want to have to repeat this code:
const resolve = {
X: {
books: ({ books }) => books.map(id => fetchBookById(id)),
}
};
The fact that defining the Book.id resolver lead to console.log actually firing is making me think this should be possible, but I'm not finding my answer anywhere online and this seems like it'd be a pretty common use case, but I'm not finding implementation details anywhere.
What I've Investigated
Schema Directives seems like overkill to get what I want, and I just want to be able to plug [Books] anywhere a [String] actually exists in the data without having to do [Books] #rest('/external/api') in every single place.
Schema Delegation. In my use case, making Books publicly queryable isn't really appropriate and just clutters my Public schema with unused Queries.
Thanks for reading this far. Hopefully there's a simple solution I'm overlooking. If not, then GQL why are you like this...
If it helps, you can think of this way: types describe the kind of data returned in the response, while fields describe the actual value of the data. With this in mind, only a field can have a resolver (i.e. a function to tell it what kind of value to resolve to). A resolver for a type doesn't make sense in GraphQL.
So, you can either:
1. Deal with the repetition. Even if you have ten different types that all have a books field that needs to be resolved the same way, it doesn't have to be a big deal. Obviously in a production app, you wouldn't be storing your data in a variable and your code would be potentially more complex. However, the common logic can easily be extracted into a function that can be reused across multiple resolvers:
const mapIdsToBooks = ({ books }) => books.map(id => fetchBookById(id))
const resolvers = {
Author: {
books: mapIdsToBooks,
},
Library: {
books: mapIdsToBooks,
}
}
2. Fetch all the data at the root level instead. Rather than writing a separate resolver for the books field, you can return the author along with their books inside the getAuthor resolver:
function resolve(root, args) {
const author = REAL_AUTHOR_DATA.find(row => row.id === args.id)
if (!author) {
return null
}
return {
...author,
books: author.books.map(id => fetchBookById(id)),
}
}
When dealing with databases, this is often the better approach anyway because it reduces the number of requests you make to the database. However, if you're wrapping an existing API (which is what it sounds like you're doing), you won't really gain anything by going this route.

How to merge and observe two collections in Firestore based on reference ID in documents?

I'm creating a StencilJS app (no framework) with a Google Firestore backend, and I want to use the RxFire and RxJS libraries as much as possible to simplify data access code. How can I combine into a single observable stream data coming from two different collections that use a reference ID?
There are several examples online that I've read through and tried, each one using a different combination of operators with a different level of nested complexity. https://www.learnrxjs.io/ seems like a good resource, but it does not provide line-of-business examples that make sense to me. This question is very similar, and maybe the only difference is some translation into using RxFire? Still looking at that. Just for comparison, in SQL this would be a SELECT statement with an INNER JOIN on the reference ID.
Specifically, I have a collection for Games:
{ id: "abc000001", name: "Billiards" },
{ id: "abc000002", name: "Croquet" },
...
and a collection for Game Sessions:
{ id: "xyz000001", userId: "usr000001", gameId: "abc000001", duration: 30 },
{ id: "xyz000002", userId: "usr000001", gameId: "abc000001", duration: 45 },
{ id: "xyz000003", userId: "usr000001", gameId: "abc000002", duration: 55 },
...
And I want to observe a merged collection of Game Sessions where gameId is essentially replace with Game.name.
I current have a game-sessions-service.ts with a function to get sessions for a particular user:
import { collectionData } from 'rxfire/firestore';
import { Observable } from 'rxjs';
import { GameSession } from '../interfaces';
observeUserGameSesssions(userId: string): Observable<GameSession[]> {
let collectionRef = this.db.collection('game-sessions');
let query = collectionRef.where('userId', '==', userId);
return collectionData(query, 'id);
}
And I've tried variations of things with pipe and mergeMap, but I don't understand how to make them all fit together properly. I would like to establish an interface GameSessionView to represent the merged data:
export interface GameSessionView {
id: string,
userId: string,
gameName: string,
duration: number
}
observeUserGameSessionViews(userId: string): Observable<GameSessionView> {
this.observeUserGameSessions(userId)
.pipe(
mergeMap(sessions => {
// What do I do here? Iterate over sessions
// and embed other observables for each document?
}
)
}
Possibly, I'm just stuck in a normalized way of thinking, so I'm open to suggestions on better ways to manage the data. I just don't want too much duplication to keep synchronized.
You can use the following code (also available as Stackblitz):
const games: Game[] = [...];
const gameSessions: GameSession[] = [...];
combineLatest(
of(games),
of(gameSessions)
).pipe(
switchMap(results => {
const [gamesRes, gameSessionsRes] = results;
const gameSessionViews: GameSessionView[] = gameSessionsRes.map(gameSession => ({
id: gameSession.id,
userId: gameSession.userId,
gameName: gamesRes.find(game => game.id === gameSession.gameId).name,
duration: gameSession.duration
}));
return of(gameSessionViews);
})
).subscribe(mergedData => console.log(mergedData));
Explanation:
With combineLatest you can combine the latest values from a number of Obervables. It can be used if you have "multiple (..) observables that rely on eachother for some calculation or determination".
So assuming you lists of Games and GameSessions are Observables, you can combine the values of each list.
Within the switchMap you create new objects of type GameSessionView by iterating over your GameSessions, use the attributes id, userId and duration and find the value for gameName within the second list of Games by gameId. Mind that there is no error handling in this example.
As switchMap expects that you return another Observable, the merged list will be returned with of(gameSessionViews).
Finally, you can subscribe to this process and see the expected result.
For sure this is not the only way you can do it, but I find it the simplest one.

Filtering away javascript objects into an array when using fetch

I have a react application, where I use the axios library, to get some values, and set them into an array of javascript objects in my state
componentDidMount(){
axios.get('http://localhost:8080/zoo/api/animals')
.then(res => this.setState({animals: res.data}))
}
Now I want to check if the objects, contains an Owner object, inside it, and filter out does that does,
First, I tried making a const, and then using the filter, to check if they contain the objects, and then set the state, but I can't save my values in a local variable
componentDidMount(){
const animals= [];
axios.get('http://localhost:8080/zoo/api/animals')
.then(res => animals=res.data)
console.log(animals) // logs empty array
console.log('mounted')
}
how can I make it so, that I can only get the animals that do NOT, have an owner object inside it?
Your animal array is empty in your second example because axios.get is asynchronous, what is in your then will be executed once the data is fetch, but the function will keep on going in the meantime.
To filter out your array, simply use filter right after fetching your data :
componentDidMount(){
axios.get('http://localhost:8080/zoo/api/animals')
.then(res => this.setState({animals: res.data.filter(animal => !animal.owner)}))
}
This function will filter out every animal object that does not have an owner property.
Working example :
const animals = [
{
name: 'Simba',
owner: {
some: 'stuff'
}
},
{
name: 1
}, ,
{
name: 2
}, ,
{
name: 3,
owner: {
some: 'stuff'
}
},
{
name: 'Bambi'
//status: 'dead'
}
]
console.log(animals.filter(animal => animal.owner))
EDIT: the answer was changed so that it only filters animals, that does not have an owner

Chat App Tut: purpose of populate()?

I'm in the process of learning FeathersJS and so far it seems like everything I wish Meteor was. Keep up the great work!
Right now I'm working through the Chat App tutorial but have run into some confusion. I don't quite understand what's going on in this section of the tutorial, specifically the populate hook in messages.hooks.js:
'use strict';
const { authenticate } = require('feathers-authentication').hooks;
const { populate } = require('feathers-hooks-common');
const processMessage = require('../../hooks/process-message');
module.exports = {
before: {
all: [ authenticate('jwt') ],
find: [],
get: [],
create: [ processMessage() ],
update: [ processMessage() ],
patch: [ processMessage() ],
remove: []
},
after: {
all: [
// What's the purpose of this ?
populate({
schema: {
include: [{
service: 'users',
nameAs: 'user',
parentField: 'userId',
childField: '_id'
}]
}
})
],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
error: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
};
Here's process-message.js:
'use strict';
// Use this hook to manipulate incoming or outgoing data.
// For more information on hooks see: http://docs.feathersjs.com/api/hooks.html
module.exports = function() {
return function(hook) {
// The authenticated user
const user = hook.params.user;
// The actual message text
const text = hook.data.text
// Messages can't be longer than 400 characters
.substring(0, 400)
// Do some basic HTML escaping
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
// Override the original data
hook.data = {
text,
// Set the user id
userId: user._id,
// Add the current time via `getTime`
createdAt: new Date().getTime()
};
// Hooks can either return nothing or a promise
// that resolves with the `hook` object for asynchronous operations
return Promise.resolve(hook);
};
};
I understand that before a create, update, or patch is executed on the messages service, the data is sent to processMessage() which sanitizes the data and adds the user ID to it.
Questions:
After processMessage(), is the data immediately written to the database?
After the data is written to the database, the after hooks are executed, correct?
What's the purpose of the populate hook then ?
Thanks :)
TO better understand how hooks and other cool stuff on feathers. It is good to do some basic logs. Anyways here is the flow.
CLIENT -> Before ALL hook -> OTHER BEFORE(create, update, ... ) -> Database -> ERROR hook (note: only if have error on previous steps) -> AFTER ALL hook -> OTHER AFTER(create, update, ...) -> FILTERS -> CLIENT
As for the populate hook, it does the same purpose of populate in your db. Feathers do it for you, instead of you doing the populate query.
Base on your example, you expect that in your schema to have something like this;
{
...,
userId : [{ type: <theType>, ref: 'users' }]
}
And you want to add another field, named user, then populate it with data from users service and match its _id with the userId.
The populate hook is one of the hooks provided by the feathers-hooks-common module. Its function is to provide data after joining the various tables in the database. Since each table is represented by an individual Service you can think of join happening between the service on which the populate hook is being called and another service.
Hence in the following piece of code a schema object is being passed to the populate hook:
populate({
schema: {
include: [{
service: 'users',
nameAs: 'user',
parentField: 'userId',
childField: '_id'
}]
}
})
It is basically telling the hook to include data from the 'users' service.
It is also telling it to name the additional data as 'user'.
It is saying that the parentField for the join (i.e. the field in the service which has the hook (in your case messages service) is 'userId'.
It is saying that the childField for the join (i.e. the field in the 'users' service is '_id'.
When the data is received, it will have all the fields from the messages table and an additional object with key user and key,value pairs from the users table.

Categories

Resources