Why does React Flow reject this approach? - react-hooks

I'm building a React app with the Context API and React Hooks. I'm trying to follow best practices. To that end I'm using Flow and am trying to adhere to its warnings.
I have a situation where once the user logs in, I want to store some data about this user in a SessionContext I've built. Given that there are only 3 pieces of data right now and they're all primitive, I thought it made sense to have just one Reducer Action:
export const sessionReducer = (state: SessionState, action: SessionAction) => {
switch (action.type) {
case UPDATE_SESSION_PROP: {
return {
...state,
[action.propName]: action.payload
};
}
default: {
return state;
}
}
}
Here are the Action types I created to account for the 3 types of data:
export type UserEmailAction = {type: 'UPDATE_SESSION_PROP',
propName: 'currentUserEmail',
payload: string};
export type UserAccessLevelAction = {type: 'UPDATE_SESSION_PROP',
propName: 'currentUserAccessLevel',
payload: number};
export type CurrentCompanyNameAction = {type: 'UPDATE_SESSION_PROP',
propName: 'currentCompanyName',
payload: string};
In my SessionContext I combine these 3 Action types as follows, and then define a Dispatch type from that:
export type SessionAction =
| UserEmailAction
| UserAccessLevelAction
| CurrentCompanyNameAction;
type Dispatch = (action: SessionAction) => void;
There are 4 warning messages over top of sessionReducer in this code:
const [state: SessionState, dispatch: Dispatch] = useReducer(sessionReducer, defaultState);
The messages are akin to this: "Cannot call useReducer with sessionReducer bound to reducer because number [1] is incompatible with string [2] in property currentCompanyName of the return value."
Did I define the Action types incorrectly or is Flow just not intelligent enough differentiate the 3 patterns? By the way, this approach seems to work fine when run.

Flow isn't intelligent enough. But you can use different approach that flow will understand:
type SessionState = {
currentUserEmail: string,
currentUserAccessLevel: number,
currentCompanyName: string,
};
type SessionAction = {
type: 'UPDATE_SESSION_PROP',
payload: $Shape<SessionState>,
};
const sessionReducer = (state: SessionState, action: SessionAction) => {
switch (action.type) {
case UPDATE_SESSION_PROP: {
return {
...state,
...action.payload
};
}
default: {
return state;
}
}
}
https://flow.org/try/#0JYWwDg9gTgLgBAbzgVwM4FMBK6AmyDG6UcAvnAGZQQhwDkU6AhvjLQNwBQH+EAdqvACqABQAiAQQAqAUQD6AZWnz5ASQDyAOVnDMa4XAC8dERJkKlqzdt3D2XGAE8w6OPPSpUwPvJiMYLowQOODh8ZCgGXhhBDChpEEZgABsALjgBKGBeAHMAGmDQ8Mjo2PF8Qg8AGXQAN3RUuF5kEAAjInyQsIj0KIBhajBGXgcNRhB0NIysvI4STg50AA9IWDhHZ1d3Tz4ymC9eQ0QC9YnjMSk5RWV1LR09Wg64QYckiEYcNIASeQALRmcADxuDz7Hx+dAAPnyc3sThcomAqEGMHwP0OAApmHs+GlgdteLt9gBKQwQuA1CDAHDzHDofBJRgMcmMuC08iMZBJGBg-y4rag3z+eY8fjwDAgvjYPCEYhGdECcF8iW8HnoXJwLH7JX4wl8EkGMlBEKoADuwBRaMxLH2ADoTiSjSFQowMHATBdzNcrHdhGlHU64AwYOEDv6A3AbZGFf5HuGI5HNXwbc9Xu8CgGYRmuAG2RyuX7006gyH0oL0JwsyESLMuCKBHAANrR054gXg9U4RHI1FpBFIvyogC6hzQWFwBCI8v5kvHMo76HZnO5ZaJbCAA
It will reduce overall amount of code also so I don't think it's too bad.

Related

Apollo Rover CLI returns non-wrapped schema when using NestJS driver

I have two subgraphs hosting their own respective schemas. Each of these schemas have input types that are named the same. For example, both schemas have an entity called Product and therefore both have inputs relevant to this entity called ProductCreateInput. Due to the way Apollo Federation works, because Input types are merged using the intersection strategy, I have to rename the inputs to different names to avoid composition errors when composing a supergraph.
So I rename the ProductCreateInput to something like Product_ProductCreateInput and Review_ProductCreateInput. I do this for every input type by using a regex and wrapSchema from #graphql-tools/wrap to rename the input types to precede with their subgraph name.
The driver code :
#Injectable()
export class GqlConfigService implements GqlOptionsFactory {
constructor(private readonly config: ConfigService, private readonly prisma: PrismaService) {}
createGqlOptions(): ApolloFederationDriverConfig {
const plugins: PluginDefinition[] = [];
if (this.config.graphql.sandbox) plugins.push(ApolloServerPluginLandingPageLocalDefault);
if (!this.config.graphql.trace) plugins.push(ApolloServerPluginInlineTraceDisabled());
return {
typeDefs: print(ALL_TYPE_DEFS),
resolvers: { Upload: GraphQLUpload },
transformSchema: async (schema: GraphQLSchema) => {
return renamedInputTypesSchema(schema);
},
debug: !this.config.production,
playground: false,
plugins,
introspection: this.config.graphql.introspection,
cors: this.config.cors,
csrfPrevention: this.config.graphql.csrfPrevention,
cache: 'bounded',
installSubscriptionHandlers: this.config.graphql.subscriptions,
subscriptions: this.config.graphql.subscriptions
? {
'graphql-ws': {
onConnect: (context: Context<any>) => {
const { connectionParams, extra } = context;
extra.token = connectionParams.token;
},
},
}
: undefined,
context: async (ctx): Promise<IContext> => {
// Subscriptions pass through JWT token for authentication
if (ctx.extra) return { req: ctx.extra, prisma: this.prisma };
// Queries, Mutations
else return { ...ctx, prisma: this.prisma };
},
};
}
}
The schemaWrapper code :
import { RenameTypes, wrapSchema } from '#graphql-tools/wrap';
import { GraphQLSchema } from 'graphql';
export const modelNames = ['User', 'Product'];
//This subgraph's name is set to "Product".
//Input type args is modified to be preceded by "Product_" because inputs are merged using the intersection strategy in the current version of Apollo Federation and directives are not supported with input types.
export const renamedInputTypesSchema = async (schema: GraphQLSchema) => {
const typeMap = schema.getTypeMap();
const models: string = modelNames.join('|');
const inputTypes = Object.keys(typeMap).filter(type => {
const inputTypesRegex = new RegExp(
`(${models})(WhereInput|OrderByWithRelationInput|WhereUniqueInput|OrderByWithAggregationInput|ScalarWhereWithAggregatesInput|CreateInput|UncheckedCreateInput|UpdateInput|UncheckedUpdateInput|CreateManyInput|UpdateManyMutationInput|UncheckedUpdateManyInput|CountOrderByAggregateInput|AvgOrderByAggregateInput|MaxOrderByAggregateInput|MinOrderByAggregateInput|SumOrderByAggregateInput|Create.*?Input|Update.*?Input)`
);
return type.match(inputTypesRegex)?.input;
});
return wrapSchema({
schema: schema,
transforms: [new RenameTypes(name => (inputTypes.includes(name) ? `Product_${name}` : name))],
});
};
This works. When I go into Apollo Sandbox and look at the schema, all the inputs are successfully preceded with Product_ like I expect :
However, when I use rover subgraph introspect in order to pipe the output to publish to my managed federation, I get the unwrapped schema (the relevant rover subgraph introspect output):
input ProductCountOrderByAggregateInput {
id: SortOrder
sku: SortOrder
description: SortOrder
}
input ProductAvgOrderByAggregateInput {
id: SortOrder
}
input ProductMaxOrderByAggregateInput {
id: SortOrder
sku: SortOrder
description: SortOrder
}
input ProductMinOrderByAggregateInput {
id: SortOrder
sku: SortOrder
description: SortOrder
}
What is going on here? Apollo sandbox shows the correct wrapped schema, yet rover introspect doesn't.

Does not exist on type 'DefaultRootState'. TS2339

I am trying to implement react-redux in login-form input values.
I have added values to the redux state, but I cannot access the data individually from the state object.
Here are the details:
In App.js file
console.log(useSelector((state) => state));
gives result {email: "demo#demo.com" , password: "123456"}
. I am not able to access the email inside the state object using
console.log(useSelector((state) => state.email));
It is giving the error that
'email' does not exist on type 'DefaultRootState'. TS2339
Here is the reducer.js file
let formValues = {
email: "",
password: "",
};
export const inputReducer = (state = formValues, action) => {
switch (action.type) {
case "inputValue":
return { ...state, [action.name]: action.inputValue };
default:
return state;
}
};
Here is the action.txt file
export const handleChange = (name: string, inputValue: string) => {
return {
type: "inputValue",
name: name,
inputValue: inputValue,
};
}
I wrote a function to get rid of this problem :
function getProperty<T, K extends keyof T>(o: T, propertyName: K): T[K] {
return o[propertyName]; // o[propertyName] is of type T[K]
}
You have to pass your object as first parameter, then the name of your property (here it is email or password).
If you want to get all your property at once, you have to encapsulate them in an object property like this:
{ value : {email:"alan.turing#gmail.com",password:"123" } }
i may be late but thought to provide solution. Basically this type of error message appears when you don't provide the typing in the useSelector hook
As per the doc React-Redux which states:
Using configureStore should not need any additional typings. You will,
however, want to extract the RootState type and the Dispatch type so
that they can be referenced as needed.
here in your code block the RootState type is missing, this can be declared in your store file as below
import {createStore} from 'redux';
----------
const store = createStore(rootReducer);
export default store;
export type RootState = ReturnType<typeof store.getState>;
And in your .tsx or .jsx file where exactly you want to access your store values using react-redux hook useSelector add the type as below.
useSelector((state:RootState) => state)

Unable to use Fragments on GraphQL-yoga with Primsa

I am using graphql-yoga with Prisma and Prisma-Bindings
I'm trying to add a fragment to my resolver so that a specific field (id in this situation) is always fetched when the user asks for a custom field, costsToDate.
This is so i can make some additional queries needed to build the result for that field, and i need the ID of the object for that.
Unfortunatley i can't seem to get it to work, and the documentations seems a little lacking on the specifics with graphql-yoga and Prisma.
Here is the definition of the type:
type Job {
id: ID!
projectNumber: String!
client: Client!
name: String!
description: String
quoteNumber: String
workshopDaysQuoted: String!
quoted: String!
targetSpend: String!
costs: [JobCost!]!
estimatedCompletion: DateTime
completed: Boolean!
costTotal: String
workshopDaysUsed: String
costsToDate: String
}
And here is the resolver for the query:
const jobs = {
fragment: `fragment description on Jobs { id }`,
resolve: jobsResolver
}
async function jobsResolver(root, args, context, info) {
await validatePermission(args,context,info,['admin','user','appAuth'])
const {showCompleted} = args
const completedFilter = (typeof showCompleted === 'boolean') ? { completed: showCompleted } : {}
const jobIDS = await context.db.query.jobs({ where: completedFilter }, `{ id }`)
//console.log(jobIDS);
let jobs = await context.db.query.jobs({
where: completedFilter
}, info)
return await getAllJobCostsToDateList(jobs)
}
I am applying the the fragmentReplacements as per below.
const fragmentReplacements = extractFragmentReplacements(resolvers)
console.log(fragmentReplacements)
const port = process.env.PORT || 3010
const graphQLServer = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
resolverValidationOptions: {
requireResolversForResolveType: false
},
context: req => ({
...req,
db: new Prisma({
typeDefs: `src/generated/prisma.graphql`,
fragmentReplacements,
endpoint: PRISMA_ENDPOINT,
secret: PRISMA_KEY,
debug: false
})
})
})
If i console.log the fragmentReplacements object i get the following, so it does seem to be picking up the fragments.
[ { field: 'job', fragment: 'fragment costsToDate on Job { id }' },
{ field: 'jobs',
fragment: 'fragment costsToDate on Jobs { id }' } ]
So my expectation here is that if i make a query against jobs or job that asks for the costsToDate field that it will also fetch the id for the job/each job.
However if i make the following query.
query{
jobs{
description
costsToDate
}
}
But i see no id fetched, and nothing in the root parameter on the resolver function.
Apologies as i am probably barking up completely the wrong tree here, seems like a somewhat simple requirement, but i can't quite work it out. Sure i'm missing something fundamental.
Thanks!
Gareth
A fragment is used to always retrieve given fields on a given type.
It follows the following format:
fragment NameOfYourFragment on YourType { ... }
You currently can't apply a given fragment conditionally as it is always applied.
Moreover, you specified a fragment on Jobs, but the type name used by Prisma is Job (even if you have the job and jobs resolvers)
You probably only need the following resolver:
const job = {
fragment: `fragment JobId on Job { id }`,
resolve: jobsResolver
}

Emit deprecation warnings with Apollo client

Background
We are working on a fairly large Apollo project. A very simplified version of our api looks like this:
type Operation {
foo: String
activity: Activity
}
type Activity {
bar: String
# Lots of fields here ...
}
We've realised splitting Operation and Activity does no benefit and adds complexity. We'd like to merge them. But there's a lot of queries that assume this structure in the code base. In order to make the transition gradual we add #deprecated directives:
type Operation {
foo: String
bar: String
activity: Activity #deprecated
}
type Activity {
bar: String #deprecated(reason: "Use Operation.bar instead")
# Lots of fields here ...
}
Actual question
Is there some way to highlight those deprecations going forward? Preferably by printing a warning in the browser console when (in the test environment) running a query that uses a deprecated field?
So coming back to GraphQL two years later I just found out that schema directives can be customized (nowadays?). So here's a solution:
import { SchemaDirectiveVisitor } from "graphql-tools"
import { defaultFieldResolver } from "graphql"
import { ApolloServer } from "apollo-server"
class DeprecatedDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field ) {
field.isDeprecated = true
field.deprecationReason = this.args.reason
const { resolve = defaultFieldResolver, } = field
field.resolve = async function (...args) {
const [_,__,___,info,] = args
const { operation, } = info
const queryName = operation.name.value
// eslint-disable-next-line no-console
console.warn(
`Deprecation Warning:
Query [${queryName}] used field [${field.name}]
Deprecation reason: [${field.deprecationReason}]`)
return resolve.apply(this, args)
}
}
public visitEnumValue(value) {
value.isDeprecated = true
value.deprecationReason = this.args.reason
}
}
new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
deprecated: DeprecatedDirective,
},
}).listen().then(({ url, }) => {
console.log(`🚀 Server ready at ${url}`)
})
This works on the server instead of the client. It should print all the info needed to track down the faulty query on the client though. And having it in the server logs seem preferable from a maintenance perspective.

Cannot initialize the state of ngrx (v. 4.x) store

I am currently looking into using the ngrx store (v. 4.0.3) for state management. It seems like a great project.
I have hit a bit of a bump in the road while trying to initialize the state of my store. The documentation makes it look rather simple, but yet I am not able to see where I am going wrong.
Here's the relevant code snippets:
in app.state.ts
export interface AppState {
searchText: string;
}
In search-text.reducer.ts
export const UPDATE = 'UPDATE';
export class UpdateSearchTextAction implements Action {
readonly type: string = UPDATE;
constructor(public readonly text: string) {}
}
export function searchTextReducer(state: string, action: UpdateSearchTextAction) {
switch(action.type) {
case UPDATE:
return action.text;
}
};
In app.module.ts
export const reducers: ActionReducerMap<AppState, UpdateSearchTextAction> = {
searchText: searchTextReducer
};
export const initialState: InitialState<AppState> = {
searchText: 'sds'
};
....
imports: [
....
StoreModule.forRoot(reducers, initialState)
]
in some Component
constructor(private store: Store<AppState>) {
this.searchBoxText = store.select('searchText');
this.searchBoxText.subscribe(text => console.log('value = [' + text + "]"));
}
So, when the application loads, I would expect to see the following logged to the console:
value = [sds]
yet I see
value = [undefined]
Later, once I start typing in an input that triggers an UpdateSearchTextAction the console does indeed log the correct value. So it seems like I've setup the store correctly.
There is probably something real simple I'm missing. Can anyone provide some advice?
Since you are having it as readonly you are not allowed to assign the value,
export class UpdateSearchTextAction implements Action {
readonly type: string = UPDATE;
constructor(public text: string) {}
}
And you need to dispatch the value using a dispatch statement
this.store.dispatch(new UpdateSearchTextAction.UPDATE(<<string>>));
You must specify default value for state argument and return the same state if no action matches. Try to change your reducer to the following:
export function searchTextReducer(state: string = '', action: UpdateSearchTextAction) {
switch(action.type) {
case UPDATE:
return action.text;
default:
return state;
}
};

Resources