Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New API - v2.0 #22

Closed
piotrwitek opened this issue Mar 26, 2018 · 67 comments · Fixed by #33
Closed

New API - v2.0 #22

piotrwitek opened this issue Mar 26, 2018 · 67 comments · Fixed by #33
Assignees
Milestone

Comments

@piotrwitek
Copy link
Owner

piotrwitek commented Mar 26, 2018

Road to v2.0: New API

I'm working on New API using the new language feature (conditional types) coming to TypesScript in the next release (v2.8)

I have already published working version that is compatible with recent typescript@rc release

> to install yarn add typesafe-actions@next

Some changes are inspired by the suggestion from the past and I would really appreciate some feedback from their authors:

The implementation of a new API is taking place on the next branch, and will be released on the release of TypeScript v2.8.

all of the examples are working and have test's against them so they should be rock solid

Finished

ActionsUnion

conditional mapped type, will provide much cleaner and experience to get Union Type of actions

import { ActionsUnion } from 'typesafe-actions';
import * as actions from './todos-duck-module';

type RootAction = ActionsUnion<typeof actions>;
  • ActionsUnion works nicely with nested actions from new async action creator (check below)
const actions = {
    async: buildAction('GET_USER').async<number, { name: string }, string>(),
};
type RootAction = ActionsUnion<typeof actions>;
// { type: "GET_USER" & "REQUEST"; payload: number; } | { type: "GET_USER" & "SUCCESS"; payload: { name: string; }; } | { type: "GET_USER" & "FAILURE"; payload: string; }
  • It also works recursively for deeply nested actions
const actions2 = {
      very: { deep: { empty: buildAction('INCREMENT').empty() } },
      payload: buildAction('ADD').payload<number>(),
};
type RootAction = ActionsUnion<typeof actions2>;
// { type: "INCREMENT"; } | { type: "ADD"; payload: number; }

ReturnType

getting type declaration of action will now be possible to derive from action creator on call site without the need to declare tons of boilerplate before-hand:

import { ReturnType, createAction } from 'typesafe-actions';

const increment = createAction('INCREMENT');

function incrementReducer(state: State, action: ReturnType<typeof increment>) {
...

buildAction

import { buildAction } from 'typesafe-actions'

function will accept action type and return an object with multiple methods to create different action creators described below:

  • buildAction('INCREMENT').empty()
const increment = buildAction('INCREMENT').empty();
expect(increment()).toEqual({ type: 'INCREMENT' }); //  { type: 'INCREMENT' }
expect(getType(increment)).toBe('INCREMENT'); // 'INCREMENT'
  • buildAction('INCREMENT').payload<P>()
const add = buildAction('ADD').payload<number>();
expect(add(10)).toEqual({ type: 'ADD', payload: 10 }); // { type: 'ADD'; payload: number }
expect(getType(add)).toBe('ADD'); // 'ADD'
  • buildAction('INCREMENT').async<R, S, F>()
type User = { name: string };
const fetchUsers = buildAction('LIST_USERS').async<void, User[], string>();
expect(fetchUsers.request())
    .toEqual({ type: 'LIST_USERS_REQUEST' }); // { type: 'LIST_USERS' & 'REQUEST' }
expect(fetchUsers.success([{ name: 'Piotr' }]))
    .toEqual({ type: 'LIST_USERS_SUCCESS', payload: [{ name: 'Piotr' }] }); // { type: 'LIST_USERS' & 'SUCCESS'; payload: User[] }
expect(fetchUsers.failure('error message'))
    .toEqual({ type: 'LIST_USERS_FAILURE', payload: 'error message' }); // { type: 'LIST_USERS' & 'FAILURE'; payload: string }
expect(getType(fetchUsers.request)).toBe('LIST_USERS_REQUEST'); // 'LIST_USERS' & 'REQUEST'
expect(getType(fetchUsers.success)).toBe('LIST_USERS_SUCCESS'); // 'LIST_USERS' & 'SUCCESS'
expect(getType(fetchUsers.failure)).toBe('LIST_USERS_FAILURE'); // 'LIST_USERS' & 'FAILURE'
  • buildAction('NOTIFY').fsa<P>(payloadCreator, metaCreator)

API compatible with redux-actions

const notify = buildAction('NOTIFY').fsa<{ username: string; message?: string }>(
    ({ username, message }) => `${username}: ${message || ''}`,
    ({ username, message }) => ({ username, message })
); // { type: 'NOTIFY'; payload: string; meta: { username: string; message?: string }; }
...

Breaking changes

🎉NO BREAKING CHANGES 🎉

Known Issues

Migration to 1.2.0

Here is an migration example of example in the docs:

  • simple payload action
// current v1.0
const add = createAction('ADD',
  (amount: number) => ({ type: 'ADD', payload: amount }),
);

// migration to v1.2 (complete type-safety is still preserved)
const add = buildAction('ADD').payload<number>;
  • advanced Flux Standard Action
// current v1.0
const notify = createAction('NOTIFY',
  (username: string, message?: string) => ({
    type: 'NOTIFY',
    payload: `${username}: ${message || ''}`,
    meta: { username, message },
  }),
);
// migration to v1.2 (complete type-safety is still preserved)
const notify = buildAction('NOTIFY').fsa<{ username: string; message?: string }>(
    ({ username, message }) => `${username}: ${message || ''}`,
    ({ username, message }) => ({ username, message })
);
@piotrwitek piotrwitek added this to the v1.2.0 milestone Mar 26, 2018
@piotrwitek piotrwitek self-assigned this Mar 26, 2018
@flq
Copy link

flq commented Mar 26, 2018

This all looks very useful to me. With regard to

buildAction('INCREMENT').payload<P>()

The following one we also use quite often:

a1 = buildAction('FAIL').error();
a1(new Error("Damn"));

@piotrwitek
Copy link
Owner Author

piotrwitek commented Mar 26, 2018

@flq thanks for feedback, could you also add what would be expected action shape from calling a1 and it's type signature? :)
More specifically, are you're interested in setting error: boolean property of action like in the FSA standard?

@flq
Copy link

flq commented Mar 26, 2018

See, I never stop learning, I wasn't quite aware of said standard. In that context it'd probably have the same signature as payload<P>, but the resulting action also sets error to true.

@piotrwitek
Copy link
Owner Author

piotrwitek commented Mar 26, 2018

yes .payload<Error>() would be enough, I could add logic in action creator to check if payload is instanceOf Error and in this case add an error property with the value true to the action and of course change the type signature accordingly

@farzadmf
Copy link

Looking forward to using this new API. Just one thing, it maybe a stupid question, but can you please explain what fsa stands for? Is it flux-specific? (I haven't ever used flux)

@piotrwitek
Copy link
Owner Author

@farzadmf as @flq mentioned above it stands for Flux Standard Action: https://github.com/redux-utilities/flux-standard-action, most redux related libraries are following it more or less

@duro
Copy link

duro commented Mar 27, 2018

@piotrwitek Do you think it would be useful to add a way to set the async suffix that is used? For example if I wanted the suffix the be /@REQUEST instead of _REQUEST

Could make creating generic middleware for promisifying these action creators more sane.

Let me know if I'm not making sense.

@duro
Copy link

duro commented Mar 27, 2018

@piotrwitek Not sure if I found a bug, or am just doing something wrong.

Given the following code:

setToken: buildAction(SessionActionType.SET_TOKEN).fsa<{
  token: string
  resolve?: () => void
  reject?: (error: Error) => void
}>(
  ({ token }) => ({ token }),
  ({ resolve, reject }) => ({ resolve, reject })
)

I am getting the following type errors in VSCode using TS2.8

actions_ts_ _freebird-native

actions_ts_ _freebird-native

@duro
Copy link

duro commented Mar 27, 2018

I was able to get my use case to work with the following:

setToken: buildAction(SessionActionType.SET_TOKEN).fsa<
  { token: string; resolve: () => void; reject: (error: Error) => void },
  { token: string },
  { resolve: () => void; reject: (error: Error) => void }
>(
  ({ token }) => ({ token: 'fgfg' }),
  ({ resolve, reject }) => ({ resolve, reject })
)

Now I seem to be having some oddities around using the ActionsUnion with .async() created actions. Since that call returns three methods, instead of what would normally satisfy a the Redux.Action type, the compiler is complaining.

Any thoughts there?

@piotrwitek
Copy link
Owner Author

piotrwitek commented Mar 27, 2018

@duro fsa is WIP, for the moment it works differently like this:

setToken: buildAction(SessionActionType.SET_TOKEN).fsa(
  ({ token }: { token: string }) => ({ token }),
  ({ token }) => ({ resolve: ..., reject: ... })
)

But I plan to change it to using only the generic type for payload, and the rest will use inference

@duro
Copy link

duro commented Mar 27, 2018

@piotrwitek Did you see my last comment #22 (comment). I was able to get it working with three type args.

That said, see the second half of the comment. New issue is with use of .async() and ActionsUnion

I'm no TS wiz, but perhaps for that case, we could have a named interface for the return of the .async and do an additional conditional that digs one level deeper if that type?

@piotrwitek
Copy link
Owner Author

piotrwitek commented Mar 27, 2018

Yes I saw it and working on it :) Trying to modify mapped with conditional type

@piotrwitek
Copy link
Owner Author

piotrwitek commented Mar 27, 2018

@duro Ok got it working :)

const actions = {
      empty: buildAction('INCREMENT').empty(),
      payload: buildAction('ADD').payload<number>(),
      fsa: buildAction('SHOW_NOTIFICATION').fsa((message: string) => message),
      async: buildAction('GET_USER').async<number, { name: string }, string>(),
    };
    type RootAction = ActionsUnion<typeof actions>;
// { type: "INCREMENT"; } | { type: "ADD"; payload: number; } | { type: "SHOW_NOTIFICATION"; payload: string; } | { type: "GET_USER" & "REQUEST"; payload: number; } | { type: "GET_USER" & "SUCCESS"; payload: { name: string; }; } | { type: "GET_USER" & "FAILURE"; payload: string; }

I'll do new release in a few minutes

@duro
Copy link

duro commented Mar 27, 2018

@piotrwitek Awesome! Can't wait to see it. I was fiddling with it over here, and I wonder how close I was to the right track.

@piotrwitek
Copy link
Owner Author

@duro I deployed a new version to npm

@duro
Copy link

duro commented Mar 27, 2018

@piotrwitek Works great! And I was getting close. Your library has taught me more about TS that the docs :)

@manvesh
Copy link

manvesh commented Mar 29, 2018

@piotrwitek buildAction doesn't seem to be returning the right shape for the action (in empty/payload/fsa) with the latest RC.2.

screen shot 2018-03-28 at 10 29 39 pm

@piotrwitek
Copy link
Owner Author

@manvesh it works fine I tested. Please check in console with tsc compiler, you have probably selected default ts version in vscode, not from node_modules

@CzBuCHi
Copy link

CzBuCHi commented Mar 29, 2018

i like all your good work on this. 👍

I have one question: Why is used this syntax?

const actions = {
      empty: buildAction('INCREMENT').empty(),
      payload: buildAction('ADD').payload<number>(),
      fsa: buildAction('SHOW_NOTIFICATION').fsa((message: string) => message),
      async: buildAction('GET_USER').async<number, { name: string }, string>(),
};

im asking, because it look little wierd to me create simple js object by calling 2 functions only to make ts happy ...

PS: not sure if typescript can infer return type based on number of type parameters passes in - something like this:

buildAction('FOO')  // no type params -> buildAction('FOO').empty();
buildAction<T>('BAR')  // single param with single argument -> buildAction('BAR').payload<T>();
buildAction<T>('FSA', pc, mc)  // single param with 3 arguments -> buildAction('FSA').fsa<T>(pc,mc);
buildAction<R, S, F>('RSF') // three params -> buildAction('RSF').async<R,S,F>();

if this works i think it would be cleaner syntax (or at least less typing syntax :) )

@piotrwitek
Copy link
Owner Author

@CzBuCHi I tried that already and I couldn't make it work, seems like it's not possible but I'm not 100% sure
If that is possible I would consider using it

@CzBuCHi
Copy link

CzBuCHi commented Mar 29, 2018

@piotrwitek ah okay ... i will try to experiment with it when i get some time and get result here ...

@manvesh
Copy link

manvesh commented Mar 29, 2018

@piotrwitek I was able to use actions that are not async.

But as soon as I use an async action in ActionUnion, I'm not able to use getType correctly. - All the payloads are checked against request type of the async action.

Errors on using async -
screen shot 2018-03-29 at 9 41 53 am
For example, error on refreshToken is

[ts]
Property 'refreshToken' does not exist on type '{} | { isAuthenticated: boolean; displ...'.
  Property 'refreshToken' does not exist on type '{}'.

Works fine without async -
screen shot 2018-03-29 at 9 38 47 am

I'm not sure if I'm doing something wrong, or if async usage is currently WIP.

@piotrwitek
Copy link
Owner Author

piotrwitek commented Mar 30, 2018

@manvesh I think I don't have a type tests for this scenario yet, so yes it's WIP, I'll look to fix it tomorrow :)

@runeh
Copy link
Contributor

runeh commented Apr 3, 2018

This new is looking really nice! Tried converting some of my project, ran into an issue with actionCreators that take a boolean:

Given

const actions = {
  putLoadingState: buildAction('PUT_LOADING_STATE').payload<boolean>(),
}

I get this error from the callsite where I try to invoke putLoadingState(true):

Cannot invoke an expression whose type lacks a call signature. Type '((payload: true) => PayloadAction<"PUT_LOADING_STATE", true>) | ((payload: false) => PayloadActio...' has no compatible call signatures.

Is this expected to work, or have I misunderstood the API?

@runeh
Copy link
Contributor

runeh commented Apr 3, 2018

I added a test here: https://github.com/runeh/typesafe-actions/tree/next . It fails when running npm run test but succeeds when running npm run test:watch . Weird.

@piotrwitek
Copy link
Owner Author

@runeh thanks a lot for help!
You found 2 nice improvements to the project :)

  • npm run test runs type tests before runtime tests, but I obviously forgot to add it to test:watch :)
  • regarding boolean, I'll look into it right away

@piotrwitek
Copy link
Owner Author

@runeh the fix is already deployed to npm rc.3
thanks for adding the test case I cherry-picked from your repo :)
Enjoy!

@Methuselah96
Copy link

@piotrwitek Don't know what I did, but it seems to be working now. Thanks.

@Methuselah96
Copy link

@piotrwitek Should tslib be under dependencies instead of devDependecies? I'm getting errors saying that it can't resolve tslib when trying to run tests, so I had to install it manually.

@piotrwitek
Copy link
Owner Author

piotrwitek commented Apr 5, 2018

@Methuselah96 yes correct, is should be dependencies :) thanks

EDIT: fix published as v2.0.0-1

@farzadmf
Copy link

farzadmf commented Apr 6, 2018

Dear @piotrwitek , I would really appreciate if you can provide a "tutorial" for this new API (similar to the one you already have in the Github README for the current API).

@piotrwitek
Copy link
Owner Author

@farzadmf sure I'll try to do it, let's track it in this issue: #25

@chasecaleb
Copy link

Playing around with this API and I overall I really like, although I'm a little stuck: is there a way to add logic buildAction(..).async<R, S, F>(), kind of like how fsa(..) takes functions to create the payload and meta?

Use case: Handling async API requests in a cross-cutting way. Currently, I have action creator functions that delegate to this utility method:

interface ApiRequestAction<T extends string> {
    readonly type: T;
    readonly payload: { readonly url: string, readonly init?: RequestInit },
    readonly meta: { readonly statusType: "REQUEST" | "RESPONSE" },
}
const createApiRequest = <T extends string>(
    type: T,
    url: string,
    init?: RequestInit
): ApiRequestAction<T> => ({
    meta: { statusType: "REQUEST" },
    payload: { url, init },
    type
});

For instance:

const submitSearch: (values: SearchFormValues) = (
    values: SearchFormValues
) =>
    createApiRequest("SEARCH_API", "/api/search", {
        body: JSON.stringify(values),
        method: "POST"
    });

Using redux-saga, I have a saga listening for actions with meta: { statusType: "REQUEST" } that calls fetch(..) and then dispatches a similar action containing the response. Overall I like the type safety and elegance of your api much more, but I do like having the request logic in my action creators and a single saga that handles all api requests.

Do you have any suggestions for how to approach this with your api? The best I've come up with so far is writing an extra action creator factory function like this (untested, but you get the idea):

interface RequestPayload {
    readonly url: string,
    readonly init?: RequestInit
}
const actions = {
    search: buildAction("SEARCH_API").async<RequestPayload, Response, string>()
};
const createSearch = (values: SearchFormValues) =>
    actions.search.request({
        init: {
            body: JSON.stringify(values),
            method: "POST"
        },
        url: "/api/search"
    });

@cjol
Copy link

cjol commented Apr 11, 2018

I agree that the syntax is a bit ugly; I would much rather have a bunch of separate functions that I could call like buildPayloadAction<number>("FOO"). I had a play to understand why this wasn't possible, and I thought I would write up my findings for future reference.

The problem is that there are actually two generic type arguments for a function like buildPayloadAction - one for the payload type P (number in the example above), and another for the string literal type T ("FOO" in the example above). In our case, we want users to be able to specify P, but not need to specify T, because we can infer it from the function parameter. Unfortunately, in TypeScript inference is currently all or nothing, which means that we cannot have a single function where only one of the generic params is inferred. The workaround is to split the generic params over two functions, as piotrwitek has done here.

Someone else reached the same conclusion to their problem (stated here) and explained their results in a little more detail here.

In terms of function naming, I think my preference would still be to have several specialised top-level functions, so things could look more like:

emptyAction("FOO").build();
payloadAction("FOO").withType<number>();
asyncAction("FOO").withTypes<RequestPayload, Response, string>();

But I realise that doesn't save very much, and might still be controversial.

@piotrwitek
Copy link
Owner Author

piotrwitek commented Apr 11, 2018

Hi @cjol, thanks for your summary and feedback!

This is exactly the reason why I went with such API, moreover I was also considering switching to a specialized functions approach as you suggested (empty action API won't be necessary because you could do payloadAction("FOO").withType<void>()).

I was thinking to clean the new API to this form, which would handle all the use-cases from initial proposal:

const noPayload = buildAction('NO_PAYLOAD').withType<void>(); // noPayload()
const createUser = buildAction('CREATE_USER').withType<PayloadType>();
const createUser = buildAction('CREATE_USER').withTypes<PayloadType, MetaType>();
const notify = buildAction('NOTIFY').withMappers<InputParameterType>( // output types are inferred
  ({ username, message }) => `${username}: ${message || ''}`, // payload mapper
  ({ timestamp }) => ({ timestamp }) // meta mapper
);

const fetchUser = buildAsyncAction(
  'FETCH_USER_REQUEST',
  'FETCH_USER_SUCCESS',
  'FETCH_USER_FAILURE'
).withTypes<string, User, Error>();

const fetchUser = buildAsyncAction(
  'FETCH_USER_REQUEST',
  'FETCH_USER_SUCCESS',
  'FETCH_USER_FAILURE'
).withMappers<string, User, Error>(
  id => ({ id }), // request mapper
  ({ firstName, lastName }) => `${firstName} ${lastName}` // success mapper
  ({ message }) => message // error mapper
);

PS: I saw TS team were discussing on a design meeting possibility to add a partially specified type arguments with inference, so when this feature arrives we will iterate and update API to the new and cleaner form (by removing withType chain call)(microsoft/TypeScript#22368 (comment))

@mykle90
Copy link

mykle90 commented Apr 11, 2018

100% chance I misunderstand a lot of stuff in this new release, but here is a problem I have ran into.

Actions:

export const actions = {
  fetchModelFamily: buildAction("FetchModelFamily").async<string, ModelFamily, ResponseError>(),
//Here will come more later on
};
export type rootAction =  ActionsUnion<typeof actions>;

Reducer:

export default function modelFamilyReducer(
  state: ModelFamilyState = initialState,
  action: rootAction
): ModelFamilyState {
  switch (action.type) {
    case getType(actions.fetchModelFamily.success):
      return { ...state, family: action.payload};
    default:
      return state;
}

The payload in this example is string | ModelFamily | ResponseError, but to my understanding it should only be ModelFamily. Where is the mistake / misunderstanding?

@piotrwitek
Copy link
Owner Author

piotrwitek commented Apr 11, 2018

@mykle90 #24
you got it right, but there was a hidden issue I didn't predicted in my initial design

EDIT: I should release a fix to the API later today

@chasecaleb
Copy link

@piotrwitek Have you had a chance to release the fix you mentioned? Not trying to pester you about it, just wanted to check in case you forgot since you said later that day.

@piotrwitek
Copy link
Owner Author

@chasecaleb I got it, but I'm trying to complete an entire new test suite (which is bigger than the code base itself) to be sure everything is working smooth and I will be able to release a stable production ready v2.0.
There are also a few minor improvement to utils and big update to the documentation.
Maybe I'll try to do a pre-release today when the test suite is finished.

@Methuselah96
Copy link

Methuselah96 commented Apr 16, 2018

@piotrwitek How do I hook this up with mapDispatchToProps? When I try to connect it I get this error:

Error:(44, 4) TS2345: Argument of type 'typeof AnalysisSetHistoryPanel' is not assignable to parameter of type 'ComponentType<{ setAnalysisSetHistoryPanel: PACreator<"ANALYSIS_HISTORY_PANEL_SHOW_SET", boolean>...'.
  Type 'typeof AnalysisSetHistoryPanel' is not assignable to type 'StatelessComponent<{ setAnalysisSetHistoryPanel: PACreator<"ANALYSIS_HISTORY_PANEL_SHOW_SET", boo...'.
    Type 'typeof AnalysisSetHistoryPanel' provides no match for the signature '(props: { setAnalysisSetHistoryPanel: PACreator<"ANALYSIS_HISTORY_PANEL_SHOW_SET", boolean>; } & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.

I can fix it by making the type of setAnalysisSetHistoryPanel to be PACreator<"ANALYSIS_HISTORY_PANEL_SHOW_SET", boolean> but that seems messy. Here's my code:

interface Props {
  setAnalysisSetHistoryPanel: (boolValue: boolean) => void;
  // setAnalysisSetHistoryPanel: PACreator<'ANALYSIS_HISTORY_PANEL_SHOW_SET', boolean>;
}

type State = {
  expandedKeys: string[];
};

class AnalysisSetHistoryPanel extends Component<Props, State> {
  state: State = {
    expandedKeys: [],
  };

  render() {
    return (
      <Dropdown

      />
    );
  }
}

const mapStateToProps = (state: StoreState) => ({
  
});

export default connect(mapStateToProps, {
  setAnalysisSetHistoryPanel: actions.analysisHistoryPanelShowSet,
})(AnalysisSetHistoryPanel);

How should I be typing this?

@kuzvac
Copy link

kuzvac commented Apr 16, 2018

@Methuselah96 i use this example as base.
And find a way to use this with Class: https://gist.github.com/kuzvac/7b6bb5c146fcf42f47de5bea6eaf5a9d
You can try it

@Methuselah96
Copy link

Methuselah96 commented Apr 16, 2018

@kuzvac Yeah, I'm pretty sure I'm matching that base example (I've tried splitting out the mapDispatchToProps as per the example), but I'm still getting the same error. Does it work with the latest bindAction function introduced here, because it doesn't seem to be working for me.

@kuzvac
Copy link

kuzvac commented Apr 16, 2018

@Methuselah96 i think yes, i use bindAction (but not async). Add actions file to Gist.

@Methuselah96
Copy link

Here's the updated component that more closely matches the base example:

interface IDispatchProps {
  setAnalysisSetHistoryPanel: (boolValue: boolean) => void;
}

interface State {
  expandedKeys: string[];
}

class AnalysisSetHistoryPanel extends Component<IDispatchProps, State> {
  state: State = {
    expandedKeys: [],
  };

  render() {
    return (
      <Dropdown
      />
    );
  }
}

const mapStateToProps = (state: StoreState) => ({

});

const mapDispatchToProps = (dispatch: Dispatch<StoreState>) => bindActionCreators({
  setAnalysisSetHistoryPanel: actions.analysisHistoryPanelShowSet,
}, dispatch);

// const mapDispatchToProps = (dispatch: Dispatch<StoreState>) => ({
//   setAnalysisSetHistoryPanel: (boolValue: boolean) => {
//     dispatch(actions.analysisHistoryPanelShowSet(boolValue));
//   },
// });

export default connect(mapStateToProps, mapDispatchToProps)(AnalysisSetHistoryPanel);

Here's the actions file:

export const actions = {
  analysisHistoryPanelShowSet: buildAction('ANALYSIS_HISTORY_PANEL_SHOW_SET').payload<boolean>(),
};

@Methuselah96
Copy link

@piotrwitek Any thoughts as to how I can get this to work?

@piotrwitek
Copy link
Owner Author

Wait a bit more please, I'm preparing a https://codesandbox.io playground that will work as a documentation and cookbook for all redux patterns (later extended with epics, sagas, reselect etc.) I'll try to post the base today with a pre-release of v2.0 stable

After that I'll focus on the issues and requests because API has been simplified a bit to overcome some type inference limitations and should fix all the issues found in the last experimental release.

@pachuka
Copy link

pachuka commented Apr 27, 2018

Just FYI, was experimenting with typesafe-actions 2.0.0-1 along with redux-observable 1.0.0-alpha.2 + rxjs 6.0.0 (lettable operators) and it seems to be working just fine! (I originally posted an error, but it was just user error... getting too late in the day for me).

const changeTheme: Epic<RootAction, RootState> = (action$, state$) =>
  action$.pipe(
    filter(isActionOf(themeActions.changeTheme)),
    withLatestFrom(state$),
    mergeMap(([action, state]) =>
    ...
    )
);

@piotrwitek
Copy link
Owner Author

@pachuka that's great to hear, thanks

@karlos1337
Copy link

karlos1337 commented May 8, 2018

Hi @piotrwitek , great job with this, I'm a huge TS fan and must work also with React so I'm very gratefull with your guide and utilities.

I have some like a bug or maybe I missunderstood something, I define an async action with

export const recipeActions = {
  fetchRecipes: buildAction(FETCH_RECIPES).async<
    void,
    APIResponse<Recipe[]>,
    string
  >()
};

export type RecipeAction = ActionsUnion<typeof recipeActions>;

But then in the reducer have an error accesing to the payload, seems that when chain types with an OR expect that all types share the same params and since the REQUEST action don't have payload I can access it in any other action.

export function recipeReducer(
  state: RecipesState = RECIPES_DEFAULT_STATE,
  action: RecipeAction
): RecipesState {
  switch (action.type) {
    case getType(recipeActions.fetchRecipes.success):
      return action.payload.data; /* <-- payload property doesn't exist in EmptyAction type... */
    default:
      return state;
  }
}

@TotallWAR
Copy link

TotallWAR commented Jun 9, 2018

Hi. I have next error:
has no exported member 'ActionsUnion'.

However i have used installing with next commands:
npm i typesafe-actions@next
and
yarn add typesafe-actions@next

edit: it's used ActionType now?

@alecmev
Copy link

alecmev commented Jun 9, 2018

@TotallWAR It has been renamed to ActionType. Also, you don't need @next anymore, v2 is out.

@TotallWAR
Copy link

Got it, @jeremejevs thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.