I have a simple page in NEXT.js for example like this:
function Page({ stars }) {
return <div>Next stars: {stars}</div>;
}
Page.getInitialProps = async ({ req }) => {
const res = await fetch('https://api.github.com/repos/zeit/next.js');
//console.log(res) shows data at-it-is, -raw
const json = await res.json();
return { stars: json.stargazers_count };
};
export default Page;
and a data-set from API https://directmarketaccess.ru/api/curves/curves/3
in such format which is needed for Google React Chart in exactly the same view at they are like:
[
[header1,..headerX]
...
[valueY,...valueY]
]
It's not an object with {} it's an array of arrays
According to the dataset from Google Charts Demo line chart - array of arrays is fine
I spent almost two days for understanding this case and such tutorials like:
https://nextjs.org/learn/excel/lazy-loading-modules/lazy-loading
https://nextjs.org/learn/basics/fetching-data-for-pages
and searching for answers from google/SO/etc. Probably I don't understand something or missing a trivial thing, but in some cases after importing data with getInititalProps even after console.log(res) stage, I receive error every time when I'm trying to import everything that isn't an object with properties. The funny thing is, that after all the experiments console.log(res) show data correctly, even if server refure to render page for me. So what if necessary data are a string, number, array, but not an object (let skip part where typeof array === object). How to import data in necessary format with or without getInitialProps?
Is there any way to import data on page as-they-are (for example from async function in other file, that connecting to DB (mongo) and receiving data from it) or I always should use fetch from API which should sent me object with {prop: value} model?
You can put any type of data as the property of an object, including an array of arrays.
Just fetch the data, and in getInitialProps, return it as:
function Page({ data }) {
// render your chart here with `data`
}
Page.getInitialProps = async ({ req }) => {
const res = await fetch(GOOGLE_CHARTS_API);
const json = await res.json(); // `json` is your array of arrays of data
return { data: json };
};
export default Page;
Related
I have the following hook / query in my frontend, which fetches an some articles and has a filter so it can fetch only the articles that have been proofread or all the articles that have not been:
export default function useArticles(isProofread) {
return useQuery("articles", async () => {
const { articles } = await request(
endpoint,
gql`
query {
articles(isProofread: ${isProofread}) {
id
title
}
}
`
)
return articles
})
}
I do not understand how to fetch all articles though? I am always passing isProofread which will either be true or false. But I struggle to understand how I would just not pass this into my query at all? I have tried not passing the variable into the function, but then undefined will still be passed into the query template, giving me an error.
How can I make this work?
I have the same problem in the backend...
It looks like you're responsible for API, too.
Your API should:
use meaningful names:
an isProofread should be a prop [of entry];
definitely handle undefined - lack of value is a value/state, too:
e.g. if (undefined != args.proofread) filters['proofread'] = args.proofread;
support where variable/arg/condition:
where:{ proofread: { eq: false } };
where:{ proofread: { in: [false, true] } } - equal to undefined;
I am solving this issue:
The application flow:
I have to call the first API endpoint (let's call it EP-A for simplicity) which takes Blob as body and fileType as a request parameter. Its performed via calling automatically generated class
uploadFile$Response(params?: {
fileType?: 'USER_AVATAR' | 'UNKNOWN' | 'DELIVERY_LOGO' | 'PAYMENT_LOGO' | 'ITEM_PICTURE';
body?: { 'file'?: Blob }
}): Observable<StrictHttpResponse<FileUploadResponse>> {
const rb = new RequestBuilder(this.rootUrl, FileControllerService.UploadFilePath, 'post');
if (params) {
rb.query('fileType', params.fileType, {});
rb.body(params.body, 'application/json');
}
return this.http.request(rb.build({
responseType: 'blob',
accept: '*/*'
})).pipe(
filter((r: any) => r instanceof HttpResponse),
map((r: HttpResponse<any>) => {
return r as StrictHttpResponse<FileUploadResponse>;
})
);
}
The StrictHttpResponse<T> is simply an interface holding a "generic" body (so you can retrieve data that will have a structure defined by swagger from which this method is generated).
Then the result FileUploadResponse which is an object like
{
uuid: string,
time: Timestamp
...
Other members omitted for simplicity
...
}
is sent to another EP (let's call it EP-B) right after EP-A call returns a value, EP-B takes an object below as a body and currently logged person as a path variable.
{
uuid: string
}
So before calling EP-B the result from EP-A should be parsed (in this case, the uuid field should be taken and put into a new object for EP-B calling)
Again via the generated method with a similar signature as the one above (and I will omit it for simplicity).
If everything performed well, I´d like to let the caller know about that. If anything failed (any of these 2 EP calls), I´d like to let it know to call of this method to react somehow (show alert, change page somehow, ...)
The method I have is now incomplete, I do not know how to "connect" these 2 Observables, I´ve read about mergeMap, flatMap, etc. but I am not sure how to use it in my case.
updateUserAvatar(avatar: Blob): Observable<boolean> {
return new Observable<boolean>((observer) => {
// Calling EP-A
this.avatarFormChangeRequestSubscription = this.fileControllerService.uploadFile$Response({
fileType: 'USER_AVATAR',
body: {
file: avatar
}
})
.subscribe((response: StrictHttpResponse<FileUploadResponse>) => {
// Handle returned UUID and somehow pass it into an observable belog
console.log(response);
},
(error: any) => {
observer.error(error);
});
// Creating object for EP-B calling
const avatarUpdateParams = {
id: 1, // Just dummy ID for now, will be dynamically changed
body: {
avatarUUID: '' // the UUID from observable response above should be placed here
}
};
// Calling EP-B
this.avatarFormChangeRequestSubscription = this.userControllerService.updateUserAvatar$Response(avatarUpdateParams)
.subscribe((response: StrictHttpResponse<string>) => {
// Handle successfull avatar upload (change the "Logged user" object avatar to change it everywhere etc
console.log(response);
observer.next(true);
},
(error: any) => {
observer.error(error);
});
});
}
At the end I would like to add "use case" flow too to understand what I am trying to achieve from user view:
User uploads his photo which is firstly uploaded into a file system (and linked with database record) on BE side, then this file is linked to his profile as his profile picture.
You could do it using rxjs. Something like that might works :
this.fileControllerService.uploadFile$Response({
fileType: 'USER_AVATAR',
body: {
file: avatar,
},
})
.pipe(
tap((responseOfFirstApiCall: StrictHttpResponse<FileUploadResponse>) => {
// Do whatever you want here, but you might not need that since you get the response below as well (in the flatMap)
// Handle returned UUID and somehow pass it into an observable belog
console.log(response);
}),
flatMap(
(responseOfFirstApiCall: StrictHttpResponse<FileUploadResponse>) => {
// Creating object for EP-B calling
const avatarUpdateParams = {
id: 1, // Just dummy ID for now, will be dynamically changed
body: {
avatarUUID: '', // the UUID from observable response above should be placed here
},
};
return this.userControllerService.updateUserAvatar$Response(avatarUpdateParams);
}
),
tap((responseOfTheSecondApiCall: StrictHttpResponse<string>) => {
// Handle successfull avatar upload (change the "Logged user" object avatar to change it everywhere etc
console.log(response);
observer.next(true);
}),
catchError((err: any) => of(err))
)
.subscribe(); // Empty subscribe() call to trigger the http request. Not needed if you get the result somewhere else (eg if your method return an observable that you want to handle the result somewhere else)
flatMap() is the same as mergeMap. Change it as you wish, there's a lot of option like map or switchMap that you should learn about since they are useful.
Basically, the pipe allow you to chain functions, and if there is an error, then the catchError is triggered.
Tip: Note that what is in the pipe is executed BEFORE the result of your api call. So if you want to do something with your result before to get it, then think about rxjs:
service
getUser(id: string) {
return this._http.get<any>(url).pipe(
map(result => result.email), // Return only the email
);
}
component:
ngUnsubscribe = new Subject();
ngOnInit() {
this._userService.getUser(1)
.pipe(takeUntil(this.ngUnsubscribe)) // Don't forget to unsubscribe !
.subscribe(email => console.log('email = ', email))
}
ngOnDestroy() {
this.ngUnsubscribe.unsubscribe();
// or
// this.ngUnsubscribe.next();
// this.ngUnsubscribe.complete();
}
need help with retrieving all the fields in a Firestore document.
I have a react app that gets documents from Firestore.
I am using react-redux-firebase for this. Below is part of my code on the individual component
const mapStateToProps = (state, ownProps) => {
console.log(state);
const collectionName = state.firestore.data.collectionName;
return {
data: data
}
}
export default compose(
connect(mapStateToProps),
firestoreConnect([
{ collection: 'collectionName' }
])
)(ComponentName);
To get a specific field in the document, I do
data.fieldName
This works well if you know all the field names. My problem is, the fields are not the same for all documents.
It would therefore help if I could get all the fields of each document and return a map or any other key:value pair that i can display to a list on the page.
In Dart, I could do something like this:
Map<String, dynamic> data() => dartify(jsObject.data());
I dont know how to do it in React. I am new to the library so any assistance will be appreciated.
If you are trying to iterate/map over the key/value pairs in an object, you can use Object.entries(), among many other options available in Object:
const formatted = Object.entries(data).map(pair => {
// do something with key and value here
const key = pair[0]
const value = pair[1]
return `${key}: ${value}`
})
I'm a bit new at frontend, so I have a question about code practices with with props importing.
I'm using next.js (which is based on React) and I'm need to insert props from API endpoint right to my page.
According to the example, it should looks like this:
export async function getServerSideProps({query}) {
const res = await fetch(encodeURI(`url_here+${query}`));
const json = await res.json();
The problem is with «what happens» next:
If I export my props (result of the function above) to page like this:
return { props: {
_id: json._id,
ilvl: json.ilvl,
...
checksum: json.checksum,
And import it like with using of destructuring assignment as an argument function:
function CharacterPage({ _id, id, ... }) {
...
}
THE PROBLEM
That there are almost 16+ key:values in response json object from API endpoint.
So if I will follow to the codestyle from above that will be.. em.. guess you already understand.
So I could export result from API endpoint like:
export async function getServerSideProps({query}) {
const res = await fetch(encodeURI(`url_here`));
const json = await res.json();
return {props: {json}
}
And import it, as one argument to the page like:
function CharacterPage({json})
But if I'll use json.name object keys on page (for conditional rendering) my IDE (WebStrom) shows me unresolved variable warning.
So where can I read about correct import practice and find react-import props example with lots of keys from JSON?
Should I use:
let {id, name, ...etc} = json
right after:
function CharacterPage({json})
for every key that I want to access or there is a better way/code practice for importing props?
My First idea is you can modify the JSON object in return of getServerSideProps. It would be more clear to identify which kind of object with attributes used here.
return { props: {
name: json.name,
id: json.id
...
}
}
If you cannot do that, it would be better to destructure initially.
let {id, name, ...etc} = json
But only destructure the elements you need. There is no need for destructuring all the elements.
I am looking for a way to modify the response object of a graphql query or mutation before it gets sent out.
Basically in addition the the data object, I want to have extra fields like code and message.
At the moment I am solving this by adding the fields directly into my GQL schemas take this type definition for example:
type Query {
myItems: myItemResponse
}
type myItemResponse {
myItem: Item
code: String!
success: Boolean!
message: String!
}
The response itself would be look like that:
{
data: {
myItems: {
myItem: [ ... fancy Items ... ],
message: 'successfully retrieved fancy Items',
code: <CODE_FOR_SUCCESSFUL_QUERY>
}
}
}
I find that solution not nice because it overcomplicates things in my FrontEnd.
I would prefer a solution where message code and other Metadata are seperated from the actual data, so something like this:
{
data: {
myItems: [ ... fancy Items ... ],
},
message: 'successfully retrieved fancy Items',
code: <CODE_FOR_SUCCESSFUL_QUERY>
}
With apollo-server I already tried the formatResponse object in the constructor:
const server = new ApolloServer({
...
formatResponse({ data }) {
return {
data,
test: 'Property to test if shown in the FrontEnd',
}
}
...
}
unfortunately that doesn't have the desired effect. Before I use express middlewares I want to ask if there is a possibility to do this via apollo-server out of the box or if I am maybe just missing something in the formatResponse function.
from graphql.org:
A response to a GraphQL operation must be a map.
If the operation encountered any errors, the response map must contain an entry with key errors. The value of this entry is described in the “Errors” section. If the operation completed without encountering any errors, this entry must not be present.
If the operation included execution, the response map must contain an entry with key data. The value of this entry is described in the “Data” section. If the operation failed before execution, due to a syntax error, missing information, or validation error, this entry must not be present.
The response map may also contain an entry with key extensions. This entry, if set, must have a map as its value. This entry is reserved for implementors to extend the protocol however they see fit, and hence there are no additional restrictions on its contents.
To ensure future changes to the protocol do not break existing servers and clients, the top level response map must not contain any entries other than the three described above.
After doing a lot of research I found out that the only allowed top level properties in a graphql responses are data, errors, extensions. Here you can find the regarding Issue in GitHub
GitHub Issue
for my purpose I will probably use the extensions field.
Example data modifier
This function will concat ":OK" suffix on each string in the output object
// Data/output modifier - concat ":OK" after each string
function outputModifier(input: any): any {
const inputType = typeof input;
if (inputType === 'string') {
return input + ':OK';
} else if (Array.isArray(input)) {
const inputLength = input.length;
for (let i = 0; i < inputLength; i += 1) {
input[i] = outputModifier(input[i]);
}
} else if (inputType === 'object') {
for (const key in input) {
if (input.hasOwnProperty(key)) {
input[key] = outputModifier(input[key]);
}
}
}
return input;
}
Solution 1 - Override GraphQL Resolvers
Long story short: you have 3 main types (Query, Mutation, and Subscription).
Each main type has fields with resolvers.
The resolvers are returning the output data.
So if you override the resolvers you will be able to modify the outputs.
Example transformer
import { GraphQLSchema } from 'graphql';
export const exampleTransformer = (schema: GraphQLSchema): GraphQLSchema => {
// Collect all main types & override the resolvers
[
schema?.getQueryType()?.getFields(),
schema?.getMutationType()?.getFields(),
schema?.getSubscriptionType()?.getFields()
].forEach(fields => {
// Resolvers override
Object.values(fields ?? {}).forEach(field => {
// Check is there any resolver at all
if (typeof field.resolve !== 'function') {
return;
}
// Save the original resolver
const originalResolve = field.resolve;
// Override the current resolver
field.resolve = async (source, inputData, context, info) => {
// Get the original output
const outputData: any = await originalResolve.apply(originalResolve.prototype, [source, inputData, context, info]);
// Modify and return the output
return outputModifier(outputData);
};
});
});
return schema;
};
How to use it:
// Attach it to the GraphQLSchema > https://graphql.org/graphql-js/type/
let schema = makeExecutableSchema({...});
schema = exampleTransformer(schema);
const server = new ApolloServer({schema});
server.listen(serverConfig.port);
This solution will work on any GraphQL-JS service (apollo, express-graphql, graphql-tools, etc.).
Keep in min with this solution you will be able to manipulate the inputData too.
Solution 2 - Modify the response
This solution is more elegant, but is implemented after the implementation of the directives and scalar types and can not manipulate the input data.
The specific for the output object is that the data is null-prototype object (no instance methods like .hasOwnProperty(), .toString(), ...) and the errors are locked objects (readonly).
In the example I'm unlocking the error object... be careful with this and do not change the structure of the objects.
Example transformer
import { Translator } from '#helpers/translations';
import type { GraphQLResponse, GraphQLRequestContext } from 'apollo-server-types';
import type { GraphQLFormattedError } from 'graphql';
export const exampleResponseFormatter = () => (response: GraphQLResponse, requestContext: GraphQLRequestContext) => {
// Parse locked error fields
response?.errors?.forEach(error => {
(error['message'] as GraphQLFormattedError['message']) = exampleTransformer(error['message']);
(error['extensions'] as GraphQLFormattedError['extensions']) = exampleTransformer(error['extensions']);
});
// Parse response data
response.data = exampleTransformer(response.data);
// Response
return response;
};
How to use it:
// Provide the schema to the ApolloServer constructor
const server = new ApolloServer({
schema,
formatResponse: exampleResponseFormatter()
});
Conclusion
I'm using both solutions in my projects. With the first you can control the input and the output based on specific access directives in the code or to validate the whole data flow (on any graphql type) .
And second to translate all the strings based on the context headers provided by the user without messing resolvers and the code with language variables.
Those examples are tested on TS 4+ and GraphQL 15 and 16