Passing multiple variable to Redux Action creator. What format? - react-redux

I am pretty new to Redux but I am getting a good hang of it so far.
I am setting up an actions file and I would need to pass two values to payload.
Please let me know if the question has already been asked elsewhere and I missed it.
Thanks!
kramnic
p.s. below is what I have drafted
export const transferValues = (fromId, toId) => {
return {
type: VALUES_TRANSFER,
payload: { fromId, toId }, //should it be array? not sure about syntax here
};
};

you can also have an action of type { type: string, fromId: number, toId: number } instead of a single "payload". that's a matther of preference I think. you just need to adapt you reducers accordingly. I personally like the above attempt more because it represents single attributes like I have them in my state as well. so I can do like a one-to-one mapping. for example:
// state
export type SessionState = {
+id: number,
+username: string,
+sessionId: string,
}
// reducer here
[...]
case SET_USERNAME:
const setUsernameAction = ((action: any): SetUsernameAction)
return {
...state,
username: setUsernameAction.username,
sessionId: setUsernameAction.sessionId,
}
[...]
// example action
export const setUsername = (username: string, sessionId: string): SetUsernameAction => {
return { type: SET_USERNAME, username, sessionId }
}
but you will certainly never use an array for multiple attributes. you'll lose the named access to properties as well as any chance to type your action. stick with objects, no matther what solution you'll go for.

Related

Use an object property as a value with a different name in GraphQl

Internally in my server my entities are handled using the database's native fields where possible, so the entity's type is keyed with "dgraph.type". My graphql api does not need to know that the database is dgraph, but I don't want to have to change the field name on every resolver. Is it possible to create a Scalar or some other process so that I can send
{
"dgraph.type": "User",
uid: "0x01",
username: "JimNaysium",
}
and have the client receive
{
type: "User",
uid: "0x01",
username: "JimNaysium",
}
If you've found your way to this benighted question, the answer is: There is no built in way to do this. Apollo server's plugins all trigger too early or too late to help and the schema cannot rename properties. I solved this using the following code:
type KeyedList<T = any> = {[key: string]: any};
const getConvertTypesResolver = (
resolver: (parent: any, args: any, context: any, info: any) => Promise<any>
): any => {
return async (parent: any, args: any, context: any, info: any): Promise<any> => {
const result = await resolver(parent, args, context, info);
// Check for serializable data now so the processor does not choke on
// circular references later.
try {
JSON.stringify(result);
} catch (e) {
throw new Error("Resolver results must be serializable.");
}
const nestedKeysArrays: string[][] = [Object.keys(result)];
const path: KeyedList[] = [result];
// Iterate over every nested object in the result body.
while (nestedKeysArrays.length) {
let done = true;
// Iterate over every key of every object.
while (nestedKeysArrays[0].length) {
// Progressively destroy the keys arrays to prevent rework.
const key = nestedKeysArrays[0].shift();
if (!key) {
continue;
}
const current = path[0][key];
// If the current key is an object add it to the beginning of the
// lists and begin processing it now.
if (current && typeof current === "object") {
nestedKeysArrays.unshift(Object.keys(current));
path.unshift(current);
done = false;
break;
}
// Change the "dgraph.type" key to "type". This is where the real
// work is done. Everything else is just navigation.
if (key === "dgraph.type") {
path[0].type = path[0]["dgraph.type"];
delete path[0]["dgraph.type"];
}
}
if (done) {
// Remove the array of keys from the list of keys to be processed.
nestedKeysArrays.shift();
// Return to the previous object.
path.shift();
}
}
return result;
};
};
The function middlewares a resolver. I'm calling it before constructing ApolloServer on every resolver in the project, and using it's result in lieu of the original resolver.
The issue can be more succinctly solved if you don't avoid recursion, but then you would be using recursion. Good luck in your future endeavors.

graphql: sort by nested field

Let's say I have 2 tables:
- Users (id, name, post)
- Posts (id, message, user)
How can I fetch first 10 Posts order by User's name(desc)?
Here's how my schema looks like:
var PostType = new GraphQLObjectType({
name: "Post",
fields: () => ({
id: { type: GraphQLInt },
message: { type: GraphQLString },
user: {
type: UserType,
args: {
orderBy: { type: sortType }
},
resolve(parent, args) {
console.info("Post resolve called.");
return userMap[parent.user];
}
}
})
});
var RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
allPosts: {
type: new GraphQLList(PostType),
resolve(parentValue, args) {
console.info("allPosts resolve called.");
return postData;
}
}
}
});
And Query:
{
allPosts {
message
user (orderBy: {field: "name", direction: ASC}) {
name
}
}
}
Is there any way, I can call user resolver function before allPosts resolver function? Because, I am trying to fetch 10 users sorted by name and then pass post ids to allPosts resolver.
GraphQL fields are resolved in a top-down fashion. That means allPosts is resolved first, then then message and user fields (simultaneously) and then the name field. This has to happen, as the "parent" or root field's resolved value determine's the value that's then passed to the resolver for its children fields as the root value. Information flows from "higher" resolvers to "lower" ones, but not the other way around.
Your orderBy argument here probably should be an argument on the allPosts field rather than the user field. There's two reasons to do that: (1) conceptually, regardless of the sort criteria, you are sorting the Posts returned by allPosts -- by convention, it just makes sense to put the sort there; (2) the argument is probably needed by the allPosts resolver more than it's needed by the user resolver.
To make the above work, you'll probably need to modify how you identify the sort criteria (making field a path like user.name for example). You may also need "lift" the logic for populating the users up into the allPosts resolver. For example:
resolve(parentValue, { sortBy: { path, direction } }) {
const data = postData.map(post => {
post.user = userMap[post.user]
return post
});
// using lodash
return orderBy(data, [(post) => get(post, path)], [direction])
}
It is possible to determine the selection set for other fields inside the request, including the arguments, by parsing the info object that's passed in as the fourth parameter to the resolver function. It's a pain though and I don't know if this particular case really justifies doing all that. You can read more about that approach in this answer.

Update Redux Store

I am trying to update the redux store but when I try to access both points and sessionId, they come back undefined. I am sure there is a problem with my reducer, but I can't figure it out. Any help would be much appreciated.
Here's my reducer:
import { UPDATE_POINTS, SET_SESSION } from '../path'
const initialState = {
sessionId: null,
points: []
}
export default (state = initialState, action) => {
switch (action.type) {
case UPDATE_POINTS:
return {
points: action.points
}
case SET_SESSION:
return {
sessionId: action.session
}
default:
return state;
}
}
Edit:
Action Creators
export function updatePoints(points){
return {
type: UPDATE_POINTS,
points
}
}
export function setSession(session){
return {
type: SET_SESSION,
session
}
}
Within React Component (for simplicity I took most everything else out of this function)
handleSelect(e) {
this.props.setSession(e);
console.log(this.props.sessionId);
}
This function is used when a menu item is chosen from a drop down menu. On the first selection, the console shows whatever is in the initial state for sessionId. Any further drop down selections result in undefined in the console.
You're super close. A reducer in redux needs to return the a new copy of the entire state. Your reducer is returning only the key it's concerned with, which is going to drop the other key. You need to return a new copy of the state with your key updated. For example:
const initialState = {
sessionId: null,
points: []
}
export default (state = initialState, action = null) => {
// Exit early if you don't have an action (returning old state)
if (!action) return state;
// This function will assign your patch onto the old state, and then
// assign all of that onto a NEW object. For redux to do it's job,
// you can't modulate the old object, you have to return a new one.
const update = patch => Object.assign({}, state, patch);
switch (action.type) {
case UPDATE_POINTS:
return update({
points: action.points
});
case SET_SESSION:
return update({
sessionId: action.session
});
default:
return state;
}
}
And for the record, instead of putting your data payload under a unique key each time in your action creators, if you put the payload under a data key then your action will follow the standard flux action format.
export const updatePoints = (points) => ({
type: UPDATE_POINTS,
data: points
});
export const setSession = (session) => ({
type: SET_SESSION,
data: session
});
There you go. Good luck, and if you get stuck, refer back to the Redux docs (they're really good). Link to Redux Docs

Is there a way to use a named function with Hapi validation?

http://hapijs.com/tutorials/validation
I'd like to pass a function in to my validation block that checks for the presence of v as a source and confirms that account, profile and ipAddress are present. The docs say this is possible but don't have an example of using a function var to do it.
When I start up my API I get: Error: Invalid schema content: (account)
How can I use a named function to do validation in Hapi?
Code:
var validateQueryString;
validateQueryString = function(value, options, next) {
console.dir({
value: value,
options: options
});
// do some validation here
return next(null, value);
};
routes.push({
method: 'POST',
path: '/export/{source}/{start}/{end?}',
config: {
validate: {
query: {
account: validateQueryString,
profile: validateQueryString,
ipAddress: validateQueryString
},
params: {
source: joi.string().valid(['a', 'v', 't']),
start: joi.string().regex(utcDateTimeRegex),
end: joi.string().regex(utcDateTimeRegex)
}
}
},
handler: function(apiRequest, apiReply) {}
});
Tried other ways of calling this like:
account: function(value, options, next) {
return validateQueryString(value, options, next); }
with no luck.
I don't think you can have a single function to handle both at the same time.
Typically, the method for the full 'list' of query parameter. Here is a bit of code to illustrate:
function validateQuery(value, options, next){
console.log( 'validating query elements');
for (var k in value) {
console.log( k, '=', value[k]);
}
next(new Error(null, value);
}
And you set it as follow:
routes.push({
...
validate: {
query: validateQuery,
params: ...
}
...
}
Now, let's assume you hit http://server/myroute?a=1&b=2&c=3, you will get the following output:
validating query elements
a = 1
b = 2
c = 3
If you want to throw an error, you have to call next() as follow:
next( new Error('some is wrong'), value );
So the 'proper' way is to have a method for query and params, it seems.
Hope this helps.
I would recommend what you are doing is out of bounds of Joi's intent. Joi is targetted for schema validation against a JS object. What you want is runtime validation against rules that exist outside of the schema itself. Hapi has something built for this called server method. Leveraging server methods, you can apply your business validations there while separating the concerns of input model and output model shape validation through Joi.

fieldASTs on GraphQL resolve

I have been going crazy over this for GraphQL. I have seen a lot of resources referring to this last fieldASTs param for the selectionSet. However, it doesn't seem to be there. I haven't found any solid evidence that it is, but it has been brought up in github issues and tutorials. I am a little confused by this. Is there or is there not a 4th param?
I have also tested the other params to see if I can pull it off of those.
const SomeType = new GraphQLObjectType({
name: 'SomeObject',
fields: () => ({
someItems : {
type: new GraphQLList(SomeCustomType),
resolve: (someItems, params, source, fieldASTs) => {
const projections = getProjection(fieldASTs);
return SomeModel.find({}, projections);
}
}
});
with current version (0.7.0), now it is in the forth argument, third argument is for context.
the following observation from this blog post may help.
http://pcarion.com/2015/09/27/GraphQLResolveInfo/
Welp, I found it. I dove through the changelogs and it was changed to be part of the third param. However, it isn't structured the same way
resolve: (item, params, info, fieldASTs) => {
//used to be
fieldASTs.selectionMap.selection.reduce(someLogic);
//now its simply
fieldASTs.reduce(someLogic);
}
I'm using graphql#0.4.14, and found it here:
info //third argument
.fieldASTs[0]
.selectionSet
.selections
//.reduce...
I guess they are still changing everything, so I've added try/catch to getProjection()
Yes the fourth param comprises of fieldASTs but its hooked under the object as an array
const SomeType = new GraphQLObjectType({
name: 'SomeObject',
fields: () => ({
someItems : {
type: new GraphQLList(SomeCustomType),
resolve: (someItems, params, source, options) => {
const projections = getProjection(options.fieldASTs[0]);
return SomeModel.find({}, projections);
}
}
});
This solved for me.
In express-graphql being graphql v0.8.1 I had to do it like this (note the use of mongoose too):
function getProjection (fieldASTs) {
return fieldASTs.fieldNodes[0].selectionSet.selections.reduce((projections, selection) => {
projections[selection.name.value] = 1;
return projections;
}, {});
}
resolve (root, params, info, fieldASTs) {
let filter = {};
if(params._id){
filter._id = params._id;
}
var projections = getProjection(fieldASTs);
return grocerModel.find(filter).select(projections).exec();
}
I am new to graphql, but this resolved it for me and I think something may have changed in the versions as fieldASTs did not exist on the options (4th param) object, but fieldNodes does that has the necessary child objects to perform the projection.
export default {
type: new GraphQLList(teamType),
args: {
name: {
type: GraphQLString
}
},
resolve(someItems, params, source, options) {
const projection = getProjection(options.fieldNodes[0])
return TeamModel
.find()
.select(projection)
.exec()
}
}
There are 2 competing Open Source libraries to achieve what topic starter was asking for:
graphql-fields-list
graphql-list-fields
They both try to solve the same problem, but the former seem to have some more features, including TypeScript support out of the box.

Resources