-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Render props version of connect #799
Comments
I don't think we have any plans to change the current API. There are a few existing re-implementations of @jimbolla , @timdorr : we don't actually export the various selectors that |
+1 . I expect you'll get a lot more over time. |
My 2 cents on this. This can be 100% backwards compatible, just adding a diferent way to connect, nobody breaks and we are all happy. Why relying in custom implementation or yet-another-js-library just to connect with render props? |
I've encountered some serious issues with typing around
These are specific problems I've encountered while using A render prop would solve these issues. If, inside of the render prop function, I'm just rendering a regular In general, the pitfalls of HOCs have already been discussed at length elsewhere, so I won't go into detail on that topic. But I would add: code that's difficult for a type system to follow is probably difficult for humans to follow, too. |
@B-Reif : part of the issue is that none of us primary maintainers actually use TS or Flow, to my knowledge. Tim oversaw accepting PRs for TS updates for Redux 4.0, but we don't have the knowledge to actually work on any of the typings ourselves. Beyond that... I get that typing is a big deal to a lot of people, but our core concern is a library that works as JS code. Redux and React-Redux have a lot of dynamic behavior, and I don't think there's any easy solutions for typing most of that. |
@B-Reif I've settled on this pattern that works well for me:
and then, whichever are needed, of:
etc. and then
I don't claim this is wonderful, just very livable, and DRY. Some of the HOC instances may need template parameters, but usually not, because |
@markerikson I only just learned elsewhere that you're open to render props in v6 so as long as that's on the table I have no problem 😄 @estaub This is pretty good! Certainly not ideal but way better than repetition. |
Is there a way we can take a step back from the API? maybe simplify it to be more terse and and expressive.
Just trying to shake things up, and maybe reshape the way we write our connect code. |
@JesseChrestler : as discussed in our roadmap in #950 , the plan is to keep the current API as-is for v6, and then open things up to discussion for possible changes in v7. The point of Sure, you can totally do it by hand inside your component, or by writing an actual I'd encourage you to go back and read through issue #1 , where Dan laid out the desired constraints for the React-Redux API. |
@markerikson I really like your point about mapDispatch. You've got me thinking more, which I appreciate. I will definitely look at issue #1 and more thinking on this. I don't typically like how bindActionCreators takes the developer out of the typical flow and you can't easily tell where things are coming from, however with dispatch you can. Pros and Cons, I get it. Thanks again for the quick response. |
I would generally argue that a "good" React component shouldn't care where its props come from. It should just get data and functions as props, and when it needs to kick off some external behavior, it just calls |
import * as React from 'react'
import { connect } from 'react-redux'
import { Dispatch } from 'redux'
import { ReduxAction } from '../store/actions'
import { ReduxState } from '../types'
interface ReduxProps extends Readonly<{
dispatch: Dispatch<ReduxAction>
state: ReduxState
}> {}
type Props = ReduxProps & Readonly<{
children: ({ dispatch, state }: ReduxProps) => JSX.Element | null
}>
const ConnectView: React.FunctionComponent<Props> = ({
children,
dispatch,
state,
}) =>
children({
dispatch,
state,
})
const mapStateToProps = (state: ReduxState) => ({ state })
const mapDispatchToProps = (dispatch: Dispatch<ReduxAction>) => ({ dispatch })
export const Connect = connect(
mapStateToProps,
mapDispatchToProps,
)(ConnectView) would let us write the following example: import * as React from 'react'
import { Connect } from '../Connect'
import { Decrement, Increment } from '../store/actions'
import { Quantity as QuantityView } from './view'
export const Quantity = () =>
<Connect>
{({ state, dispatch }) => (
<QuantityView
count={state.count}
decrement={() => dispatch(Decrement())}
increment={() => dispatch(Increment())}
/>
)}
</Connect> without ever needing a |
@kylecorbelli : if you do that, your component would now re-render for every dispatched action that resulted in a state change. Also, you're having to You can do that, if you want to, but it's not efficient at all. You might want to review the design constraints listed in issue #1 to understand why the API works this way. (I'm also writing a blog post on this topic, which I hope to have done later this week.) |
Yeah, that's the real sticking point isn't it? 😭 |
import * as React from 'react'
import { connect } from 'react-redux'
import { Dispatch } from 'redux'
import { ReduxAction } from '../store/actions'
import { ReduxState } from '../types'
type MapStateToProps<P> = (state: ReduxState) => P
type MapDispatchToProps<P> = (dispatch: Dispatch<ReduxAction>) => P
interface ConnectProps<StateProps, DispatchProps> extends Readonly<{
children: (props: StateProps & DispatchProps) => JSX.Element | null
mapDispatchToProps: MapDispatchToProps<DispatchProps>
mapStateToProps: MapStateToProps<StateProps>
}> {}
export class Connect<StateProps, DispatchProps> extends React.Component<ConnectProps<StateProps, DispatchProps>> {
public render () {
const { children, mapStateToProps, mapDispatchToProps } = this.props
const Component = connect(
mapStateToProps,
mapDispatchToProps,
)(props => children(props))
return <Component />
}
} will allow us to write: import * as React from 'react'
import { Dispatch } from 'redux'
import { Connect } from '../Connect'
import { Increment, ReduxAction } from '../store/actions'
import { ReduxState } from '../types'
const mapStateToProps = (state: ReduxState) => ({
count: state.count,
})
const mapDispatchToProps = (dispatch: Dispatch<ReduxAction>) => ({
increment: () => dispatch(Increment()),
})
export const Quantity: React.FunctionComponent = () =>
<Connect mapStateToProps={mapStateToProps} mapDispatchToProps={mapDispatchToProps}>
{({ count, increment }) => (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
)}
</Connect> and through the use of generics we even get type safety on the props passed to the This should prevent re-rendering when unrelated state changes occur. |
That example is broken, because you're calling Yes, render props versions of |
¯\_(ツ)_/¯ |
We've been trying to migrate a lot of our containers from HOCs over to components that use render props
For the sake of consistency we're doing the same for
connect
What would be awesome is if it would be possible to add a built in version of connect that does something similar but doesn't require adding this unnecessary wrapping. Would something like that be feasible?
The text was updated successfully, but these errors were encountered: