Skip to content

Commit

Permalink
Pass non-Redux-store values through the store prop (#1447)
Browse files Browse the repository at this point in the history
* Allow non-Redux-store values as a prop named `store`

* Formatting
  • Loading branch information
markerikson authored Nov 6, 2019
1 parent b832f83 commit 2677705
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 18 deletions.
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

0 comments on commit 2677705

Please sign in to comment.