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.
Related
I am trying to set my state to the data I'm getting from my API with a GETTER in the store.
during the mounted() lifecyclehook trigger the GETTER getProducts() which looks like this:
export const getters = {
async getProducts() {
axios.get('/api/products')
.then(res => {
var data = res.data
commit('setProducts', data)
})
.catch(err => console.log(err));
}
}
In the GETTER I try to trigger a MUTATION called setProducts() which looks like this:
export const mutations = {
setProducts(state, data) {
state.products = data
}
}
But when I run this I get the error ReferenceError: commit is not defined in my console.
So obviously what goes wrong is triggering the MUTATION but after looking for 2 days straight on the internet I still couldn't find anything.
I also tried replacing commit('setProducts', data) with:
this.setProducts(data)
setProducts(data)
Which all ended with the error "TypeError: Cannot read properties of undefined (reading 'setProducts')"
If your function getProduct is defined in a Vue component, you have to access the store like this :
this.$store.commit('setProducts', data)
If your function is not defined in a Vue component but in an external javascript file, you must first import your store
import store from './fileWhereIsYourStore.js'
store.commit('setProducts', data)
If your getters export is literally the definition of your store's getters, you can use the solution of importing the store first, but you should know that it is clearly not a good practice to make commits in getters. There must be a better solution to your problem.
EDIT : To answer your comment, here's how you could do it:
// Your store module
export default {
state: {
products: []
},
mutations: {
SET_PRODUCTS(state, data) {
state.products = data
}
},
actions: {
async fetchProducts(store) {
await axios.get('/api/products')
.then(res => {
var data = res.data
store.commit('SET_PRODUCTS', data)
})
.catch(err => console.log(err));
}
}
}
Now, you can fetch products and populate your store in each of your components like this :
// A random Vue Component
<template>
</template>
<script>
export default {
async mounted() {
await this.$store.dispatch('fetchProducts')
// now you can access your products like this
console.log(this.$store.state.products)
}
}
</script>
I didn't tested this code but it should be ok.
Only actions do have commit in their context as you can see here.
Getters don't have commit.
Otherwise, you could also use mapActions (aka import { mapActions } from 'vuex'), rather than this.$store.dispatch (just a matter of style, no real difference at the end).
Refactoring your code to have an action as Julien suggested is a good solution because this is how you should be using Vuex.
Getters are usually used to have some state having a specific structure, like sorted alphabetically or alike. For common state access, use the regular state or the mapState helper.
I'm new in TypeScript and in this simple example I've created a component to fetch data from a fake API and show them in a map iteration:
import React, { FC, useState } from 'react';
const GetData = (): Promise<[]> => {
return fetch('https://randomuser.me/api/?results=5')
.then((response: any) => response.json())
.then((data: any) => data.results);
}
const App: FC<{}> = () => {
const [users, setUsers] = useState<[]>([]);
const getUsers = (): void => {
GetData().then((data: []) => setUsers(data));
}
return (
<div>
{
<ul>
{
users.map((item: any, index: number) => (
<li key={`user-${index}`}>{`${item.name.first} {item.name.last}`}</li>
))
}
</ul>
}
<button onClick={getUsers}>Load</button>
</div>
)
}
export default App;
This code works well ! but I'm not sure about true of my codes... In this example I've :any type for functions input (in promise chain eg.) so, is it correct to this example? am I able to use many of any type in arguments when I'll have a nested array in output?
and the second one, I didn't add type with : for GetData, but that is a const and I should declare them something like this:
const age: number = 40;
but I didn't get any error?
Thank you
When you are using Promise generic to define one, you shouldn't add type for callback functions into chain. for example write your chain like this:
const GetData = (): Promise<User[]> => {
return fetch('https://randomuser.me/api/?results=5')
.then((response) => response.json())
.then((data) => data.results);
}
And for second one question about const age: number = 40;
when you define a const and assign function to that, means you are create a function and TypeScript considers it will be a function and not a primitive value, therefore, you shouldn't add type for const after that name.
but when you are set something like this: const age: number = 40;
the above code means, your are defining a primitive value and should set type after name.
When you use any you miss out on most of typescript's error-finding abilities. You are basically saying, "trust me, this is fine" since any is assignable to any type.
There are a few places that we can improve your types and also a few types that are inaccurate. Promise<[]> actually means that this is a promise of the empty array []. You want it to return an array of users: Promise<User[]>.
You might have this User object typed somewhere else in your app already. Here I am just defining the properties that we need for this component.
interface User {
name: {
first: string;
last: string;
}
}
Calling fetch returns Promise<Response> where Response is a built-in object type that supports the .json() method. So instead of (response: any) you are better off just doing (response) and using the inferred type. That .json() method returns Promise<any>, so the inferred type for data is any. You can choose to assert at this point that data is an object with a property results containing an array of User objects, but you can also just stick with the inferred any and rely on your function's return type to assert that.
const GetData = (): Promise<User[]> => {
return fetch('https://randomuser.me/api/?results=5')
.then((response) => response.json())
.then((data) => data.results);
}
Now to the component. Our state is an array of User, not an empty array.
const [users, setUsers] = useState<User[]>([]);
Once you change that, you can remove the types from (data: []) and (item: any, index: number). You generally don't need to assert types in a callback when you have proper types higher up in the chain. When you call users.map, the item is already known to have type User because users is typed as User[].
Typescript Playground Link
Using any is a brute force approach that discards all the benefits of using TypeScript on the first place. It works, but isn’t recommended.
Given const age = 40, TypeScript knows that you are assigning a number. The expression on the right-hand side cannot be anything other than a number. It can infer the type from that so you don’t need to be explicit.
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 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
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;