I have the following:
...
type RepairsState = {
data: Property[] /* Property is an object coming from another file */
}
type RepairsPropertyLoadAction = {
type: typeof REPAIRS_PROPERTY_LOAD
payload: { models: Property[] } /* the payload will return an object that has a models property of an array of objects that match my property type */
}
/* Reducer */
export const initialState: RepairsState = {
data: [
{
id: '',
jobNo: '',
trade: '',
priority: '',
status: '',
raisedDate: new Date(),
appointmentDate: new Date(),
completedDate: new Date(),
description: ''
}
]
}
export default function reducer(state = initialState, action: RepairsPropertyLoadAction): RepairsState {
switch (action.type) {
case REPAIRS_PROPERTY_LOAD:
console.log(action.payload)
return {
...state,
data: action.payload
}
default:
return state
}
}
export const getRepairsProperty = (state: AppState) => state.repairs.data
...
Property class:
export default class Property {
id: string = ''
jobNo: string = ''
trade: string = ''
priority: string = ''
status: string = ''
raisedDate: Date = new Date()
appointmentDate: Date = new Date()
completedDate: Date = new Date()
description: string = ''
}
however I am getting the following error:
Type '{ models: Property[]; }' is missing the following properties from type 'Property[]': length, pop, push, concat, and 28 more. TS2740
The action returns an {models: Property []} object for the data, but the state has data: Property []
return {
...state,
data: action.payload.model
}
You are missing the models property, which is defined as part of RepairsPropertyLoadAction. The reducer should be returning this instead:
return {
...state,
data: action.payload.models,
}
Related
I have a class "House" like :
class House{
constructor(params){
this.clear();
// this = {...params} // I know that don't work !!!
//--
// if(params.address !== undefined) this.address = {...params.address}
//...
}
clear(){
this.address = {
number: null,
street: null,
zipcode: null,
ton: null,
}
this.access = {
doorcode: null,
stair: null,
}
}
}
I want to create a new instance of House and inject in constructor multiple json like :
const h = new House({address: { /* json */ }, access: { /* json */});
Or only one like :
const h = new House({access: { /* json */});
In constructor, am i obliged to check all values in "params" to insert in good properties (nested object)
I would like to avoid to create other classes like address and access and in the house constructor create new instance of each.
What's the best practice ?
Regards
Using Object.assign() and object destructuring with default parameters in the constructor, you can achieve this quite easily:
class House {
static get defaultAddress () {
return {
number: null,
street: null,
zipcode: null,
town: null
}
}
static get defaultAccess () {
return {
doorcode: null,
stair: null
}
}
constructor({ address = House.defaultAddress, access = House.defaultAccess } = {}) {
this.clear()
Object.assign(this.address, address)
Object.assign(this.access, access)
}
clear () {
const { defaultAddress, defaultAccess } = House
Object.assign(this, { address: defaultAddress, access: defaultAccess })
}
}
// no object
console.log(new House())
// empty object
console.log(new House({}))
// partial object
console.log(new House({ address: { number: 1, street: 'street', zipcode: 12345, town: 'town' } }))
// empty sub-objects
console.log(new House({ address: {}, access: {} }))
// partial sub-objects
console.log(new House({ address: { number: 1, street: 'street' }, access: { doorcode: 321 } }))
// complete object
console.log(new House({ address: { number: 1, street: 'street', zipcode: 12345, town: 'town' }, access: { doorcode: 321, stair: 3 } }))
.as-console-wrapper{min-height:100%!important}
You can loop through the parameters and set them manually. Then, to clear, remove all own properties (properties that aren't inherited).
class House {
constructor(params) {
// set data
Object.assign(this, params);
}
clear() {
for (let key in this) {
if (this.hasOwnProperty(key))
this[key] = undefined; // or `delete this[key];`
}
}
}
let house = new House({type: "normal", height: 40});
console.log(house, house instanceof House);
Of course, you probably want to limit the input keys to a predefined set. You could store those keys in a static class variable and use them to loop through the properties in constructor and clear.
class House {
constructor(params) {
// check for invalid properties
Object.keys(params).forEach(key => {
if (!House.keys.includes(key))
throw `Invalid paramater ${key}`;
});
// set data
Object.assign(this, params);
}
clear() {
for (let key in House.keys) {
if (this.hasOwnProperty(key))
this[key] = undefined; // or `delete this[key];`
}
}
}
House.keys = ['type', 'height'];
let house = new House({type: 'normal', height: 40});
console.log(house, house instanceof House);
let error = new House({helloWorld: true});
I think you want a common namespace for your instance properties - similar to React's props pattern - you can also specify defaults for each instance you are creating:
const defaultProps = { address: {}, access: {} };
class House {
constructor(props = {}) {
this.props = {...defaultProps, ...props};
}
clear() {
this.props = {...defaultProps};
}
}
reducer looks like this
import {ADD_TASK, EDIT_TASK, CHECK_TASK, CHECK_ALL_TASK} from '../constants';
import {OrderedMap, Record} from 'immutable';
const TaskRecord = Record({
id: null,
text: null,
isChecked: false
});
const ReducerState = Record({
entities: new OrderedMap({})
});
const defaultState = new ReducerState();
export default (tasksState = defaultState, action) => {
const {type, payload, randomId} = action;
switch (type) {
case ADD_TASK:
return tasksState.setIn(['entities', randomId], new TaskRecord({...payload.task, id: randomId}));
case CHECK_ALL_TASK:
return tasksState.map(entities => {
return entities.map((task) => {
task.set('isChecked', true);
});
});
};
return tasksState;
};
How change all isChecked in TaskRecord that in entities? I wrote CHECK_ALL_TASK, but it gives an error (tasksState undefined)
When you want to set a new value for a property based on it's current one, .update is what you want to use.
When you want to make a change to each item in a collection, .map is what you want to use.
In your case, you want to set a new value for the property entities by modifying each entity in the collection.
This means you want to use update and map:
function checkAllBoxes(taskState) {
return taskState.update('entities', entities =>
entities.map(entity =>
entity.set('isChecked', true)));
}
const TaskRecord = Immutable.Record({
id: null,
text: null,
isChecked: false
});
const ReducerState = Immutable.Record({
entities: new Immutable.OrderedMap({})
});
const taskState = new ReducerState({
entities: Immutable.OrderedMap({
a: new TaskRecord(),
b: new TaskRecord(),
c: new TaskRecord()
})
});
function checkAllBoxes(taskState) {
return taskState.update('entities', entities =>
entities.map(entity =>
entity.set('isChecked', true)));
}
console.log(checkAllBoxes(taskState))
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/4.0.0-rc.9/immutable.js"></script>
wrote this function, but can it be made as something simpler?
function checkAllCheckboxes(state) {
let newState = state;
for (let id of state.entities) {
newState = newState.setIn(['entities', id[0], 'isChecked'], true);
}
return newState;
}
I use this library to build my forms: https://github.com/mozilla-services/react-jsonschema-form and I have a uischema for date inputs, but when I send the form I want to convert startdate and enddate in unix time.
Here is the code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
import React, { Component, FormGroup, FormControl,ControlLabel } from 'react';
import NavBar from '../Header/NavBar';
import * as dropdown from "../../helpers/dropdown";
import Form from "react-jsonschema-form";
import * as formSubmit from "../../helpers/sumary";
import * as moment from 'moment';
class Sumary extends Component {
constructor(props) {
super(props);
this.state = {
activityschema: {},
machineschema: {},
activitiesTypes: [],
startDate: moment(),
endDate: moment(),
activityType: 0,
step: 0
};
}
componentWillMount = async () => {
const activitiesTypes = await dropdown.getActivitiesType();
const machines = await dropdown.getMachines();
this.setState({
activityschema: {
type: "object",
properties: {
typeNameId: {
type: 'number',
title: 'Type:',
enum: activitiesTypes.map((item) => {
return item.id;
}),
enumNames: activitiesTypes.map((item) => {
return item.name;
})
},
startDate: {type: "string", title: 'Start date:'},
endDate: {type: "string", title: 'End date:'},
}
},
machineschema: {
type: "object",
properties: {
machinesId: {
type: 'number',
title: 'Machine Name:',
enum: machines.map((item) => {
return item.id;
}),
enumNames: machines.map((item) => {
return item.name;
})
},
//startDate: {type: "string", title: 'Start date:'},
//endDate: {type: "string", title: 'End date:'},
}
},
uiSchema: {
startDate: {
"ui:widget": "date"
},
endDate: {
"ui:widget": "date"
}
}
});
};
onSubmit = async ({formData}) => {
if (formData.typeNameId === 10) {
this.setState({
step: 1,
formData: {
...this.state.formData,
...formData
},
});
await formSubmit.getSumary(formData);
console.log(formData);
} else {
await formSubmit.getSumary(formData);
console.log(formData);
}
};
render () {
let schema= null;
schema = this.state.activityschema;
let uiSchema = this.state.uiSchema;
switch(this.state.step){
case 1:
schema = this.state.machineschema;
break;
default:
break;
}
return (
<Form
schema={schema}
uiSchema={uiSchema}
onSubmit={this.onSubmit}
formData={this.state.formData} />
);
}
}
export default Sumary;
The code above works fine, but when I submit data, my formData in startDate and endDate are on this type of format:
Object {typeNameId: 3, startDate: "2017-12-22", endDate: "2017-12-27"}
But I want them in unix time.
I tryed something like this:
onSubmit = async ({formData}) => {
this.state.formData.startDate.unix();
this.state.formData.endDate.unix();
if (formData.typeNameId === 10) {
this.setState({
step: 1,
formData: {
...this.state.formData,
...formData
},
});
await formSubmit.getSumary(formData);
console.log(formData);
} else {
await formSubmit.getSumary(formData);
console.log(formData);
}
};
But this will get:
Unhandled Rejection (TypeError): Cannot read property 'startDate' of
undefined
So I see following problems with your code:
You don't have formData in your React initial state.
If you want to use formData with your state you need to track onChange event to get the latest data
In your onSubmit if you use formData that you get in onSubmit call, it should be enough to remove Rejection error
Date has a format of string, so it's not exactly moment object, you need to construct it from the string and convert to unix format
If you want to input with type date you need to specify format on your schema object, otherwise it's just a text input field
You are right in your approach, there seems to be no straight forward way to do this, without adding additional widgets or fields.
Hope this helps.
My node definitions looks like this:
class Store {}
let store = new Store()
let nodeDefs = nodeDefinitions(
(globalId) => {
let type = fromGlobalId(globalId).type
let id = fromGlobalId(globalId).id
if (type === 'Store') {
return store
}
if (type === 'Video') {
return docClient.query(
Object.assign(
{},
{TableName: videosTable},
{KeyConditionExpression: 'id = :id'},
{ExpressionAttributeValues: { ':id': id }}
)
).promise().then(dataToConnection)
}
return null
},
(obj) => {
if (obj instanceof Store) {
return storeType
}
if (obj instanceof Video) {
return videoType
}
return null
}
)
The problem is that video node is always null, even when actual video is being returned from the database, because for it to not be null I need to look it up based on id or somehow fetch it from database.
This is the video node I am referring to:
video: {
type: videoType,
args: Object.assign(
{},
connectionArgs,
{id: {type: GraphQLString}}
),
resolve: (_, args) => {
return docClient.query(
Object.assign(
{},
{TableName: pokemonTable},
{KeyConditionExpression: 'id = :id'},
{ExpressionAttributeValues: { ':id': args.id }},
paginationToParams(args)
)
).promise().then(dataToConnection)
}
},
and
const videoType = new GraphQLObjectType({
name: 'Video',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: (obj) => obj.id
},
name: { type: GraphQLString },
url: { type: GraphQLString }
}),
interfaces: [nodeDefs.nodeInterface]
})
const allVideosConnection = connectionDefinitions({
name: 'Video',
nodeType: videoType
})
I tried doing database query directly inside node definitions, but that didn't work.
dataToConnection just converts the output of dynamoDB:
video DATA!! { Items:
[ { id: 'f4623d92-3b48-4e1a-bfcc-01ff3c8cf754',
url: 'http://www.pokkentournament.com/assets/img/characters/char-detail/detail-pikachuLibre.png',
name: 'YAHOO' } ],
Count: 1,
ScannedCount: 1 }
into something that graphql relay understands:
video dataToConnection!! { edges:
[ { cursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
node: [Object] } ],
pageInfo:
{ startCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
endCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
hasPreviousPage: false,
hasNextPage: false } }
and the function itself can be found here: https://github.com/dowjones/graphql-dynamodb-connections/pull/3/files
It could be the problem.
Also, asking/querying for id makes the whole video object null:
But omitting id from the query returns something, whether querying with relay id:
or database id
and querying for all of the videos works:
The interesting part is that I get exactly same problem even if I delete the video part from node definitions:
let nodeDefs = nodeDefinitions(
(globalId) => {
let type = fromGlobalId(globalId).type
let id = fromGlobalId(globalId).id
if (type === 'Store') {
return store
}
return null
},
(obj) => {
if (obj instanceof Store) {
return storeType
}
return null
}
)
Any ideas?
UPDATE:
I did some digging and found that interfaces in fact is undefined
const storeType = new GraphQLObjectType({
name: 'Store',
fields: () => ({
id: globalIdField('Store'),
allVideosConnection: {
type: allVideosConnection.connectionType,
args: Object.assign(
{},
connectionArgs
),
resolve: (_, args) => {
return docClient.scan(
Object.assign(
{},
{TableName: pokemonTable},
paginationToParams(args)
)
).promise().then(dataToConnection)
}
},
video: {
type: videoType,
args: Object.assign(
{},
connectionArgs,
{id: {type: GraphQLString}}
),
resolve: (_, args) => {
return docClient.query(
Object.assign(
{},
{TableName: pokemonTable},
{KeyConditionExpression: 'id = :id'},
{ExpressionAttributeValues: { ':id': args.id }},
paginationToParams(args)
)
).promise().then(dataToConnection)
}
}
}),
interfaces: [nodeDefs.nodeInterface]
})
console.dir(storeType.interfaces, { depth: null })
prints undefined
Why? I clearly define them at the top!
Also, I can do that:
But this doesn't work:
This is what is being returned in video: {} resolve:
{ edges:
[ { cursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
node:
{ id: 'f4623d92-3b48-4e1a-bfcc-01ff3c8cf754',
url: 'http://www.pokkentournament.com/assets/img/characters/char-detail/detail-pikachuLibre.png',
name: 'YAHOO' } } ],
pageInfo:
{ startCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
endCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
hasPreviousPage: false,
hasNextPage: false } }
Somehow that's okay for allVideosConnection, but not okay (ends up null) for video
Do I need to convert ids of nodes to global IDs? using toGlobalId ? Just for video ?
Because another thing I noticed is that if I
console.log('fromGlobalId', fromGlobalId(globalId))
inside my node definitions, this query:
{
node(id: "f4623d92-3b48-4e1a-bfcc-01ff3c8cf754") {
id
...F1
}
}
fragment F1 on Video {
url
name
}
becomes this:
fromGlobalId { type: '', id: '\u000e6]_v{vxsn\u001eU/\u001b}G>SW_]O\u001c>x' }
However, if I do
I get
globalId U3RvcmU6
fromGlobalId { type: 'Store', id: '' }
So to make node definitions work, all I had to do was this:
class Video {}
let video = new Video()
return Object.assign(video, data.Items[0])
i.e. create class with the same name as type name
and then Object.assign to it
Just doing this, doesn't work:
return {Video: data.Items[0]}
I also need to create IDs in the database like that: Video:f4623d92-3b48-4e1a-bfcc-01ff3c8cf754, where I am essentially putting type and randomly generated unique id together separated by a colon (:) and then encode it with toGlobalId function of graphql-relay-js library (so I end up with VmlkZW86ZjQ2MjNkOTItM2I0OC00ZTFhLWJmY2MtMDFmZjNjOGNmNzU0Og==), so then I can decode it with fromGlobalId so that node definitions can retrieve both type and id({ type: 'Video', id: 'f4623d92-3b48-4e1a-bfcc-01ff3c8cf754:' }), after which I still need to add fromGlobalId(globalId).id.replace(/\:$/, '')) to remove the trailing colon (:).
`
Also, interfaces are not meant to be accessible, they are just for configuration.
I have a type
export type LocationQuery = {
showWarnings: boolean;
showErrors: boolean;
startDate: string;
endDate: string;
quickDate: number;
}
Now, I want to convert location.query from the history module to be converted into this type.
The inconvenient way of doing this is manual:
let query: LocationQuery;
query.showWarnings = location.query['showWarnings'];
query.showErrors = location.query['showErrors'];
...
But is there a more convenient, one liner way? Bare in mind location.query may have other fields that I would not care about (so if there is location.query['someOtherField'], that should not get into query
There is too many ways to do it.
this could be one of them
import * as assert from "assert";
/**
* will copy all blueprint.keys from source
* or default to blueprint (default) value;
* #returns a copy of blueprint with source values
*/
const copy = <TSource extends {}, TTarget extends {}>
(source: TSource, bluePrint: TTarget): TTarget => {
let result: any = {};
// see: Object.getOwnPropertyNames, its shallow
for (let key of Object.getOwnPropertyNames(bluePrint)) {
result[key] = source.hasOwnProperty(key)
? source[key]
// default to blueprint prop, will be undefined
// but they key will exists
: bluePrint[key];
}
return result;
}
export type LocationQuery = {
showWarnings: boolean;
showErrors: boolean;
startDate: string;
endDate: string;
quickDate: number;
}
interface History {
showWarnings: boolean;
showErrors: boolean;
startDate: string;
endDate: string;
quickDate: number;
somethingElse: any;
}
/**
* Default Value, blueprint, skeleton, shape,etc
*/
const empty: LocationQuery = {
showWarnings: undefined,
showErrors: undefined,
startDate: undefined,
endDate: undefined,
quickDate: undefined,
};
/**
* test's source subject
*/
const history: History = {
showWarnings: false,
showErrors: false,
startDate: '2016-12-01',
endDate: '2016-12-31',
quickDate: 1,
somethingElse: false
}
/**
* LocationQuery it's 'Partial'History
*/
const expected: LocationQuery = {
showWarnings: false,
showErrors: false,
startDate: '2016-12-01',
endDate: '2016-12-31',
quickDate: 1,
}
describe("copy", () => {
it("shallow copy, all desired members", () => {
let result = copy(history, empty);
assert.deepEqual(expected, result);
// All Key Present?
// somethingElse shoudl be missing...
assert.equal(
"showWarnings, showErrors, startDate, endDate, quickDate",
Object.keys(result).reduce( (a,b)=> a+ ", "+b));
});
it("shallow copy, all desired members and defaults to missing props", () => {
// doesn't provide all memebers
const historyLike = {
showWarnings: true,
showErrors: true,
}
let result = copy(historyLike, empty);
const expected_result_with_defaults = {
showWarnings: true,
showErrors: true,
startDate: undefined,
endDate: undefined,
quickDate: undefined,
};
// Values Ok?
assert.deepEqual(expected_result_with_defaults, result);
// All Key Present?
assert.equal(
"showWarnings, showErrors, startDate, endDate, quickDate",
Object.keys(result).reduce( (a,b)=> a+ ", "+b)
)
});
})
Another one for Typescript 2.1+
/**
* Requires Typescript 2.1 up
* copy specified key from derived type
* where TSource is superset of TResult
*/
const copy = <TSource extends TResult, TResult extends {}>(source: {}, ...keys: (keyof TResult)[]): TResult => {
let result: any = {};
for(let key of keys){
result[key] = source.hasOwnProperty(key) ? (<any>source)[key] : null;
}
return result
}
describe("copy", () => {
it("copy specified members ", () => {
let result = copy<History, LocationQuery>(
/*from*/ history,
"showWarnings" ,
"showErrors",
"startDate",
"endDate",
"quickDate");
assert.deepEqual(expected, result);
assert.equal(
"showWarnings, showErrors, startDate, endDate, quickDate",
Object.keys(result).reduce( (a,b)=> a+ ", "+b)
)
});
})
With a list of field names:
function toLocationQuery(source) {
const fields = ['showWarnings', 'showErrors', 'startDate', 'endDate', 'quickDate']
let res = {}
for (let k of fields) {
if (source[k] === undefined)
throw new Error(`Missing field "${k}"`)
res[k] = source[k]
}
return res as LocationQuery
}
let query = toLocationQuery(location.query)
Or the same code but without to redeclare the list of fields for each call:
const toLocationQuery = (function () {
const fields = ['showWarnings', 'showErrors', 'startDate', 'endDate', 'quickDate']
return function (source) {
let res = {}
for (let k of fields) {
if (source[k] === undefined)
throw new Error(`Missing field "${k}"`)
res[k] = source[k]
}
return res as LocationQuery
}
})()
let query = toLocationQuery(location.query)