Redux: Add docs around static typing

Created on 22 Apr 2018  Â·  20Comments  Â·  Source: reduxjs/redux

We don't currently appear to have any documentation around using static types (TS / Flow) with Redux. It would be beneficial if we had docs on what the type definitions look like, and how to handle correctly typing different scenarios.

docs

Most helpful comment

@ohjames would you mind sharing?

All 20 comments

This would be great!

This may actually force me to learn TypeScript...

BTW, I found Dr. Axel Rauschmeyer's intro to TS really helpful for getting me off the ground with typing: http://2ality.com/2018/04/type-notation-typescript.html

Please provide examples for typescript, how to:
Augment dispatch so that you can customize return type for specific action types and have this work in bindActionCreators as well

For me it seem´s this issue is critical because, as of right now no async action library has implemented correct type augmentation. This and the complexity of implementing it, make it quite difficult to use redux with typescript if you want typed async actions.

@ch1ll0ut1 Could you point to some untyped examples and explain what the expected behavior of typescript should be for those?

Would appreciate this.
I was looking for an example with TS and was surprised that there was none.
I ended up having to write it myself
Might be good as a starting point.

From https://redux.js.org/advanced/async-actions

import fetch from 'cross-fetch'
​
export const REQUEST_POSTS = 'REQUEST_POSTS'
function requestPosts(subreddit) {
  return {
    type: REQUEST_POSTS,
    subreddit
  }
}
​
export const RECEIVE_POSTS = 'RECEIVE_POSTS'
function receivePosts(subreddit, json) {
  return {
    type: RECEIVE_POSTS,
    subreddit,
    posts: json.data.children.map(child => child.data),
    receivedAt: Date.now()
  }
}
​
export const INVALIDATE_SUBREDDIT = 'INVALIDATE_SUBREDDIT'
export function invalidateSubreddit(subreddit) {
  return {
    type: INVALIDATE_SUBREDDIT,
    subreddit
  }
}
​
// Meet our first thunk action creator!
// Though its insides are different, you would use it just like any other action creator:
// store.dispatch(fetchPosts('reactjs'))
​
export function fetchPosts(subreddit) {
  // Thunk middleware knows how to handle functions.
  // It passes the dispatch method as an argument to the function,
  // thus making it able to dispatch actions itself.
​
  return function (dispatch) {
    // First dispatch: the app state is updated to inform
    // that the API call is starting.
​
    dispatch(requestPosts(subreddit))
​
    // The function called by the thunk middleware can return a value,
    // that is passed on as the return value of the dispatch method.
​
    // In this case, we return a promise to wait for.
    // This is not required by thunk middleware, but it is convenient for us.
​
    return fetch(`https://www.reddit.com/r/${subreddit}.json`)
      .then(
        response => response.json(),
        // Do not use catch, because that will also catch
        // any errors in the dispatch and resulting render,
        // causing a loop of 'Unexpected batch number' errors.
        // https://github.com/facebook/react/issues/6895
        error => console.log('An error occurred.', error)
      )
      .then(json =>
        // We can dispatch many times!
        // Here, we update the app state with the results of the API call.
​
        dispatch(receivePosts(subreddit, json))
      )
  }
}

A example to type the above code should result in:
The line store.dispatch(fetchPosts('reactjs'))should have a inferred return type of Promise<{...}> (the type inside the promise depends on the return type inside the thunk action).

I've just updated a redux project to use TypeScript, took a few days. The provided redux typings weren't quite good enough though, I rewrote them using TS 2.8 conditional types and now I have a level of type safety I'm really happy with.

@ohjames would you mind sharing?

This PR does not resolve this issue, but is a first step in documenting Redux with TypeScript and will be useful to have a concrete example when adding documentation, let me know if anyone has any thoughts on this exmaple: https://github.com/reduxjs/redux/pull/3092

Would like to check in on the status of this issue. If no one is working on this I would love to help out for typescript. @markerikson

Sure.

Some useful docs I've found that might be helpful for reference:

It's generally useful to have a look at the tests for a libraries types in flow-typed, e.g. this explains how to type redux + redux-thunk: https://github.com/flow-typed/flow-typed/blob/1751d5bf0ac340abe8c6d6aab58bf6213494f053/definitions/npm/react-redux_v5.x.x/flow_v0.68.0-/test_Provider.js#L39-L48

Are you guys aware that this exists?

I implemented TypeScript for React and React Redux this way. Let me know if you want a Mini-Doc for this for the Redux docs.

@janhesters : Yeah, I've seen it (briefly), and I'm kinda wondering if we might be able to leverage or duplicate some of its behavior in our redux-starter-kit package.

We've got an open PR at #3201 to add a TS usage doc, but I haven't had time to look at it yet, and I'm not a TS user myself. I'd appreciate any help reviewing that.

@markerikson I read through it and it looks good. Seems like HershVar knows his stuff. The way he did it is more "self-made" than using the typesafe-actions package.
I usually prefer well maintained packages over DIY, which is why I chose to go with the package. And as for your last question within that thread, integrating react-redux with TypeScript is pretty easy imo, because there is already @types/react-redux (same for reselect). So (advancing on HershVar's docs) the only thing that you have to do is to use AppState in your mapStateToProps and - if you use mapDispatchToProps or its shorthand-notation - to let your Props interface know about your actions:

interface Props {
  addMessage(message: Message): void;
}

// ... component code

const mapStateToProps = (state: AppState) => ({
  activeMessages: activeMessagesSelector(state)
});

export default connect(mapStateToProps, { addMessage })(MyComponent)

One thing I often do is:

interface OwnProps {
  // props the component should receive when mounted
}

interface ReduxProps {
  // props the component wants from redux
}

// ownProps is optional
// not specifying it at all is a performance gain in react-redux
function mapStateToProps(state: AppState, ownProps: OwnProps): ReduxProps {
  // the return type annotation above ensures this object does actually conform
  return {}
}

const mapDispatchToProps = {
  // action creators go here
}

type Props = OwnProps & ReduxProps & typeof mapDispatchToProps

export default connect(mapStateToProps, mapDispatchToProps)(Component)

// I find it more readable to make use of hoisting and put the definition
// after the connect; but if it's a class component you're stuck putting it first
function Component(props: Props) {
  // ...
  return <>{/* component contents */}</>
}

This does run into a problem with redux-thunk action creators, though. Their return type stays ThunkAction instead of being the result of the thunk action.

I work around that with:

/**
 * TypeScript can't transform function signatures, so functions given to
 * mapDispatchToProps are not correctly converted from returning ThunkAction into
 * returning the ThunkAction's result.
 *
 * Use this helper on the result of the function to get at the actual result.
 */
export function unwrapThunk<T>(
  dispatchedThunkAction: ThunkAction<T, any, any>
): T {
  return dispatchedThunkAction as any
}

One alternative, now that TS has ReturnType types, is to just use ReturnType<typeof mapStateToProps> instead of having an interface ReduxProps.

// I find it more readable to make use of hoisting and put the definition
// after the connect; but if it's a class component you're stuck putting it first
function Component(props: Props) {
  // ...
  return <>{/* component contents */}</>
}

Well I guess you are in love with hooks then 😄

Yeah, I know I've seen people complain about trying to type thunks as well. Dunno if we can get any info on that into this doc?

I'd argue that we keep this document to the core. Any other libraries, including React Redux, should get their own page (and RR's should be on its docs, with a link to it from the core docs).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ilearnio picture ilearnio  Â·  3Comments

elado picture elado  Â·  3Comments

ms88privat picture ms88privat  Â·  3Comments

CellOcean picture CellOcean  Â·  3Comments

parallelthought picture parallelthought  Â·  3Comments