dphilipson / typescript-fsa-reducers

Fluent syntax for defining typesafe reducers on top of typescript-fsa.
MIT License
220 stars 16 forks source link

cannot get .caseWithAction to work with my Handler #12

Closed st-clair-clarke closed 7 years ago

st-clair-clarke commented 7 years ago

Hi, I have the following:

import actionCreatorFactory from 'typescript-fsa' import { reducerWithInitialState } from 'typescript-fsa-reducers'

import { FormState, IFormState } from '../../shared/shared.store/store.util'

export interface IEmail { address: string type: string meta: IMeta }

export class Email { type: string = '' address: string = '' meta: IMeta }

const userAcf = actionCreatorFactory( '[Admin ResetEmail]' )

export const addResetEmailDataAction = userAcf( 'ADD_DATA' )

export function addResetEmailDataHandler( state: IEmail, email: IEmail ): IEmail { return Object.assign( {}, state, email ) }

export const resetResetEmailDataAction = userAcf( 'RESET_DATA' )

function resetResetEmailDataHandler( state: IEmail ): IEmail { return Object.assign( {}, state, new Email() ) }

export const resetEmailDataReducer = reducerWithInitialState( new Email() ) .caseWithAction( addResetEmailDataAction, addResetEmailDataHandler, ) // ERROR with addResetEmailDataHandler .case( resetResetEmailDataAction, resetResetEmailDataHandler ) }

My .case works but the .caseWithAction does not. I need your help to use my addResetEmailDataHandler handler to work in the .caseWithAction above.

Thanks

dphilipson commented 7 years ago

Hi st-claire-clarke!

The first reason you're getting an error is because your addResetEmailDataAction has no payload. That is, in the line

export const addResetEmailDataAction = userAcf( 'ADD_DATA' )

since you don't specify a payload type, the actions created by this factory are assumed to have no payload. Based on the fact that your addResetEmailDataHandler function takes an IEmail as its second argument, I assume you want to be able to create actions with IEmail as their payload, but note that as written the following is an error:

let email: IEmail
const action = addResetEmailDataAction(email)
// Argument of type IEmail is not assignable to parameter of type 'undefined'.

Because of this, I would guess that what you want instead is

export const addResetEmailDataAction = userAcf<IEmail>( 'ADD_DATA' )

which allows you to call addResetEmailDataAction(email) which is probably what you want.

Secondly, supposing you make that change you'll still have a type error on the .caseWithAction() line because .caseWithAction() expects a handler function which takes (state, action) => state, by contrast to .case() whose handler function is (state, payload) => state. In your example code, the second argument of addResetEmailDataHandler has type IEmail which is the payload type, not the action type, so you actually do want to be using .case here.

Based on the code you posted, I don't see a reason why you would need to be using .caseWithAction() rather than .case()- the former is mainly useful when you need to either read meta information from the action or pass the entire action on unchanged to a different handler, but the example code does neither.

Please let me know if I am misunderstanding or if you have further questions. Thanks!

st-clair-clarke commented 7 years ago

Hi Phillipson, Thanks for your quick reply.

The meta: IMeta type in the IEmai has the definition as shown below:

export interface IMeta { lastEditedBy: string timeStamp: Date }

export class Meta { lastEditedBy: string = '' timeStamp: Date }

I would like to access these data when the store is updated using .caseWithAction(). So, I would like to update a log with the meta data. With this information, what would the complete code look like, including the handler?

Cheers

dphilipson commented 7 years ago

Any reason this doesn't work?

const userAcf = actionCreatorFactory( '[Admin ResetEmail]' )

export const addResetEmailDataAction = userAcf<IEmail>( 'ADD_DATA' )

export function addResetEmailDataHandler( state: IEmail, email: IEmail ): IEmail {
    // In here, you can access the metadata through email.meta.
    return Object.assign( {}, state, email )
}

export const resetResetEmailDataAction = userAcf( 'RESET_DATA' )

function resetResetEmailDataHandler( state: IEmail ): IEmail {
    return Object.assign( {}, state, new Email() )
}

export const resetEmailDataReducer = reducerWithInitialState( new Email() )
    .case( addResetEmailDataAction, addResetEmailDataHandler)
    .case( resetResetEmailDataAction, resetResetEmailDataHandler )
}

// Example of creating an addResetEmailAction
const meta = new Meta();
meta.lastEditedBy = "someGuy";
meta.timestamp = new Date();

const email = new Email();
email.address = "example@example.com";
email.meta = meta;

const myAction = addResetEmailAction(email);
// myAction looks like: { type: "ADD_DATA", payload: email }.

Note that the metadata as you've written it is different from the meta field on the action, because IEmail is not the type of the action, it's the type of the action's payload. If it were the meta field of the action, then the action would appear:

{
    type: "ADD_DATA",
    payload: {
        type: "",
        address: ""
    },
    meta: { lastEditedBy: "", timestamp: <some-date> }
}

but your actions don't look like that, they look like this:

{
    type: "ADD_DATA",
    payload: {
        type: "",
        address: "",
        meta: { lastEditedBy: "", timestamp: <some-date> }
    }
}

Since meta is just another field on your payload, you don't need to use .caseWithAction to read it and can just pull it out of .case.

dphilipson commented 7 years ago

Closing this for inactivity. Feel free to re-open if you have more questions.