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

Pass non-Redux-store values through the store prop #1447

Merged
merged 2 commits into from
Nov 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/api/connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ export default connect(

The number of declared function parameters of `mapStateToProps` and `mapDispatchToProps` determines whether they receive `ownProps`

> Note: `ownProps` is not passed to `mapStateToProps` and `mapDispatchToProps` if the formal definition of the function contains one mandatory parameter (function has length 1). For example, functions defined like below won't receive `ownProps` as the second argument. If the incoming value of `ownProps` is `undefined`, the default argument value will be used.
> Note: `ownProps` is not passed to `mapStateToProps` and `mapDispatchToProps` if the formal definition of the function contains one mandatory parameter (function has length 1). For example, functions defined like below won't receive `ownProps` as the second argument. If the incoming value of `ownProps` is `undefined`, the default argument value will be used.

```js
function mapStateToProps(state) {
Expand Down
23 changes: 10 additions & 13 deletions docs/api/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ React Redux now offers a set of hook APIs as an alternative to the existing `con

These hooks were first added in v7.1.0.


## Using Hooks in a React Redux App

As with `connect()`, you should start by wrapping your entire application in a `<Provider>` component to make the store available throughout the component tree:
Expand Down Expand Up @@ -212,10 +211,6 @@ export const App = () => {

## Removed: `useActions()`





## `useDispatch()`

```js
Expand Down Expand Up @@ -295,7 +290,6 @@ export const CounterComponent = ({ value }) => {
}
```


## Custom context

The `<Provider>` component allows you to specify an alternate context via the `context` prop. This is useful if you're building a complex reusable component, and you don't want your store to collide with any Redux store your consumers' applications might use.
Expand Down Expand Up @@ -395,7 +389,7 @@ This hook was in our original alpha release, but removed in `v7.1.0-alpha.4`, ba
That suggestion was based on "binding action creators" not being as useful in a hooks-based use case, and causing too
much conceptual overhead and syntactic complexity.

You should probably prefer to call the [`useDispatch`](#usedispatch) hook in your components to retrieve a reference to `dispatch`,
You should probably prefer to call the [`useDispatch`](#usedispatch) hook in your components to retrieve a reference to `dispatch`,
and manually call `dispatch(someActionCreator())` in callbacks and effects as needed. You may also use the Redux
[`bindActionCreators`](https://redux.js.org/api/bindactioncreators) function in your own code to bind action creators,
or "manually" bind them like `const boundAddTodo = (text) => dispatch(addTodo(text))`.
Expand All @@ -410,12 +404,15 @@ import { useMemo } from 'react'

export function useActions(actions, deps) {
const dispatch = useDispatch()
return useMemo(() => {
if (Array.isArray(actions)) {
return actions.map(a => bindActionCreators(a, dispatch))
}
return bindActionCreators(actions, dispatch)
}, deps ? [dispatch, ...deps] : [dispatch])
return useMemo(
() => {
if (Array.isArray(actions)) {
return actions.map(a => bindActionCreators(a, dispatch))
}
return bindActionCreators(actions, dispatch)
},
deps ? [dispatch, ...deps] : [dispatch]
)
}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ Or by setting it globally:
}
```

See https://github.com/facebook/react/issues/14927#issuecomment-490426131
See https://github.com/facebook/react/issues/14927#issuecomment-490426131
13 changes: 10 additions & 3 deletions src/components/connectAdvanced.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,14 @@ export default function connectAdvanced(
// Retrieve the store and ancestor subscription via context, if available
const contextValue = useContext(ContextToUse)

// The store _must_ exist as either a prop or in context
const didStoreComeFromProps = Boolean(props.store)
// The store _must_ exist as either a prop or in context.
// We'll check to see if it _looks_ like a Redux store first.
// This allows us to pass through a `store` prop that is just a plain value.
console.log('Store from props: ', props.store)
const didStoreComeFromProps =
Boolean(props.store) &&
Boolean(props.store.getState) &&
Boolean(props.store.dispatch)
const didStoreComeFromContext =
Boolean(contextValue) && Boolean(contextValue.store)

Expand All @@ -176,7 +182,8 @@ export default function connectAdvanced(
`React context consumer to ${displayName} in connect options.`
)

const store = props.store || contextValue.store
// Based on the previous check, one of these must be true
const store = didStoreComeFromProps ? props.store : contextValue.store

const childPropsSelector = useMemo(() => {
// The child props selector needs the store reference as an input.
Expand Down
15 changes: 15 additions & 0 deletions test/components/connect.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2090,6 +2090,21 @@ describe('React', () => {
expect(actualState).toEqual(expectedState)
})

it('should pass through a store prop that is not actually a Redux store', () => {
const notActuallyAStore = 42

const store = createStore(() => 123)
const Decorated = connect(state => ({ state }))(Passthrough)

const rendered = rtl.render(
<ProviderMock store={store}>
<Decorated store={notActuallyAStore} />
</ProviderMock>
)

expect(rendered.getByTestId('store')).toHaveTextContent('42')
})

it('should pass through ancestor subscription when store is given as a prop', () => {
const c3Spy = jest.fn()
const c2Spy = jest.fn()
Expand Down