Does anyone know how to wrap a React component with React.memo when one is using the connect function from react-redux?
For example, how would you modify the following?
let Button = (props: Props) => (
<button onClick={props.click}>{props.value}</button>
);
Button = connect(
mapStateToProps,
mapDispatchToProps
)(Button);
I've tried:
let Button = React.memo((props: Props) => (
<button onClick={props.click}>{props.value}</button>
));
Button = connect(
mapStateToProps,
mapDispatchToProps
)(Button);
However, the function returned by connect expects a component to be passed so it errors with:
Uncaught Error: You must pass a component to the function returned by
connect. Instead received {"compare":null}
React.memo is nothing but a HOC, so you can just use:
Without memo:
connect(
mapStateToProps,
mapDispatchToProps
)(Button);
With memo:
connect(
mapStateToProps,
mapDispatchToProps
)(React.memo(Button));
And even wrap to connect: (This should be the solution with connect)
React.memo(
connect(
mapStateToProps,
mapDispatchToProps
)(Button)
);
Like we do with withRouter: withRouter(connect(...)())
Same issue here. Fixed by upgrading react-redux to version 5.1.0.
Your solution should work (I didn't try copy-pasted like that), but you also have to update react-redux to the latest version.
By the way, I think the proper implementation of React.memo within many HOC would be to be the closest to the component itself : the goal of React.memo is to check if all the new props received by the component are the same as the last props. If any HOC transforms or adds any props to the component - which connect does by mapping the Redux store to the props, React.memo should be aware of it in order to decide wether or not to update the component.
So I would go for something like that :
//import what you need to import
const Component = props => <div>{/* do what you need to do here */}</div>
export default compose(
connect(mapStateToProps, dispatchToProps),
/* any other HOC*/
React.memo
)(Component);
Codesandbox demo
As the error message says, you need to pass a component to the returned function from connect.( which means the second pair of () in connect()() )
As React.Memo returns a component, pass it into the second function of connect.Here's how you can do this.
export const MemoizedDemoComponent = connect(mapStateToProps)(React.memo(DemoComponent);
Demo component:
import React from "react";
import { connect } from "react-redux";
const DemoComponent = props => (
<div>
<p>My demo component fueled by: {props.fuel}!</p>
<p>Redux: {props.state}</p>
</div>
);
const mapStateToProps = state => ({
state: "your redux state..."
});
// create a version that only renders on prop changes
export const MemoizedDemoComponent = connect(mapStateToProps)(
React.memo(DemoComponent)
);
For a working example check also codesandbox.
For someone who want to know why react-redux throw this error.
For me, I used version 5.0.7, react-redux/src/components/connectAdvanced.js line: 92
invariant(
typeof WrappedComponent == 'function',
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
);
After upgrading this code is changed to :
invariant(
isValidElementType(WrappedComponent),
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
);
How to check the WrappedComponent is changed to isValidElementType(WrappedComponent) which is exposed by react-is
So, yarn update react-redux to the version that mentioned by #Maxime Chéramy at least
Related
I'm connecting store to the Image component using redux connect. I only expect to retrieve there (in the Image component) one prop from Redux: "images". But I also get an additional prop: "dispatch".
import { connect } from "react-redux";
import Image from "./Image";
import { imagesSelector } from "units/images/selectors";
const mapStateToProps: MapStateToPropsParam = (state) => ({
images: imagesSelector(state),
});
export default connect(mapStateToProps)(Image);
In the Image component I get:
{ images: [...], dispatch: function(){...}}
The question:
How to exclude dispatch from being passed to my component?
From the react-redux docs:
If you don't specify a second argument to connect(), your component
will receive dispatch as a prop by default.
If you don't want this behavior, you can instead pass a second argument to the connect function, which would be the mapDispatchToProps argument.
It can be either a function or an object.
We recommend always using the “object shorthand” form of
mapDispatchToProps, unless you have a specific reason to customize the
dispatching behavior.
Note that:
Each field of the mapDispatchToProps object is assumed to be an action creator
Your component will no longer receive dispatch as a prop
Docs: https://react-redux.js.org/using-react-redux/connect-mapdispatch
export default connect(mapStateToProps, null)(Image);
add null as a second parameter and you're good to go
In order to access to the props of the component inside the mapDispatchToProps i chained my connect like so
export default connect(mapStateToProps, null)(connect(mapStateToProps, mapDispatchToProps)(MyComponent));
And then i manage to access to the props inside the mapDispatchToProps like so
const mapDispatchToProps = (dispatch,ownProps) => {
const myProp = ownProps.theProp
}
Is it a bad things to do ?
Any alternative exists ?
Is it a bad things to do?
IMO, It is certainly bad. connect() is an HOC. connect(...)(connect(...)(MyComponent)) is redundant.
Any alternative exists ?
Use mergeProps instead or break the components properly and use redux-saga to use a common interaction point (the redux store).
The correct way to connect mapDispatch to props and mapStateToProps is like this:
export default connect(mapStateToProps, mapDispatchToProps)(MetaDataTaggingApp);
Also I don't think you should have to access props in mapDispatchToProps.
mapDispatchToProps is basically just telling your component which dispatch actions it's allowed to use.
const mapDispatchToProps = {
aReduxAction,
anotherReduxAction
}
If you need to pass props into these dispatches, it should be when you are invoking them.
I have some experience with ReactJS but now I am trying to start using Redux and I have encoutered several problems. I already know how to create actions, consts, reducers, how to connect them to one single store, but I don't actually now how to use it with React. For example I have a form to gather user's data and I want it all passed to Redux store. So I guess the main question would be how do I trigger the action in ReactJS?
when using react-redux, you'll get a component enhancer called connect.
class Component extends React.Component {
render() {
return (
<button onClick={this.props.onClickButton}>
{this.props.a}
</button>
)
}
}
export default connect(function mapStateToProps(state) {
return { a: state.store.a }
}, { onClickButton: incrementAction })(Component)
What I'm doing here is taking a global store value (state.store.a - state is the global store, .store is the store from a combined store, and a is the value), and telling the React component to listen for changes on this variable (transparently through connect).
Additionally, I'm wrapping an action creator incrementAction (and renaming it to onClickButton). If you're using a middleware like redux-thunk, this will automatically pass in store.dispatch as an arg. Otherwise, this is a standard action creator.
both of these will be available inside the component as props (the args are descriptively named mapStateToProps and mapDispatchToProps)
You'll want to use react-redux. For example, here's a small counter:
import { connect } from "react-redux";
import { increment } from "actions";
import PropTypes from "prop-types";
import React from "react";
function counter ({ count, increment }) {
return <button onClick={increment}>
{count}
</button>;
}
counter.propTypes = {
count: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired
};
export default connect(
(state) => ({
count: state.data.count
}),
{ increment }
)(counter);
The (state) => ({ }) bit passes a property called count to the component's props. The { increment } passes your increment function in the props.
Be sure to include the { increment } part in the connect; if you don't, your redux action won't be dispatched.
To bind redux to react there is a package called react-redux. The description of which is official react bindings for redux.
You can connect the actions to react by using mapDispatchToProps, which will map your actions as props. Then you can call those actions as props. When you call those actions as props, the actions will be triggered and redux state will change.
To access the state you have to use mapStateToProps, which will give you the state as props.
You can use connect method to connect mapStateToProps and mapDispatchToProps to react.
I think it would be easier if you do a tutorial. This is a tutorial by Dan Abramov, creator of Redux.
I'm studying react and I have an example like this
//index.js
const store = createStore(reducer)
render(
<Provider store={store}>
<AddTodo />
</Provider>,
document.getElementById('root')
)
//Apptodo.js
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}>
.......
Why didn't it get this.pros.store but simply call the dispatch() function ?
EDIT: How does it extract the dispatch from this.pros. Isn't the object this.pros.store ? and in this case why don't we just extract store ?
Thank you.
react-redux is the library that is passing these methods to your component as props.
dispatch() is the method used to dispatch actions and trigger state changes to the store. react-redux is simply trying to give you convenient access to it.
Note, however, that dispatch is not available on props if you do pass in actions to your connect function. In other words, in the code below, since I'm passing someAction to connect, dispatch() is no longer available on props.
The benefit to this approach, however, is that you now have the "connected" action available on your props that will automatically be dispatched for you when you invoke it.
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { someAction } from '../myActions';
const MyComponent = (props) => {
// someAction is automatically dispatched for you
// there is no need to call dispatch(props.someAction());
props.someAction();
};
export default connect(null, { someAction })(MyComponent);
Or if we were to use object destructuring as shown in the example you give...
const MyComponent = ({ someAction }) => {
someAction();
};
It's important to note, however, that you must invoke the connected action available on props. If you tried to invoke someAction(), you'd be invoking the raw, imported action — not the connected action available on props. The example given below will NOT update the store.
const MyComponent = (props) => {
// we never destructured someAction off of props
// and we're not invoking props.someAction
// that means we're invoking the raw action that was originally imported
// this raw action is not connected, and won't be automatically dispatched
someAction();
};
This is a common bug that people run into all the time while using react-redux. Following eslint's no-shadow rule can help you avoid this pitfall.
Your addTodo component has access to the store's state and methods(e.g, dispatch, getState, etc). So, when you hooked up your React view with the Redux store via the connect method, you had access to store's state and methods.
({ dispatch }) is simply using JS destructuring assignment to extract dispatch from this.props object.
I am in the middle of my first React Native project. I would like to create a HOC that deals purely with syncing data from an api. This would then wrap all my other components.
If I am correct my DataSync component would enhance all other components by doing the following in the export statement:
export default DataSync(SomeOtherComponent);
The concept I am struggling with is that SomeOtherComponent also depends on the React Redux Connect method for retrieving other redux state. My question is how can I use both together? Something like this?
export default DataSync(connect(mapStateToProps, mapDispatchToProps)(SomeOtherComponent));
I may have completely misunderstood the concept here so I would really appreciate some pointers
EDIT
To explain further:
My DataSync HOC would purely handle the syncing of data between the app and would be the top level component. It would need access to auth state and would set the data in Redux (in this case orders) for all other components.
Components nested within the DataSync HOC need access to the retrieved data, routes and they in turn create state (orders) that must be synced back to the server periodically.
Here is a simple example how it works
const AppWrapper = MainComponent =>
class extends Component{
componentWillmount(){
this.props.fetchSomething()
}
componentDidUnmount(){
this.props.push('/')
}
render(){
return (
<div>
{this.props.fetchedUsers === 'undefined' ?
<div> Do something while users are not fetched </div> :
<MainComponent {...this.props}/>}
</div>
)
}
}
const App = ({users}) => {
// just example, you can do whatever you want
return <div>{JSON.stringify(users)}</div>
};
// mapStateToProps will be in HOC -> Wrapper
// mapDispatchToProps will be in HOC -> Wrapper
/* you may use DataSync as you want*/
export default connect(mapStateToProps, mapDispatchToProps)(AppWrapper(App))
Useful HOC link
May be this is what you wanted:
DataSync.js
export default connect(mapStateToProps, mapDispatchToProps)(DataSync);
SomeOtherComponent.js
export default DataSync(connect(mapStateToProps, mapDispatchToProps)(SomeOtherComponent));
Use connect on your child components as well. Here is WHY
Yes, connect is also HOC and you can nest them arbitrary since a HOC returns a component.
HOC(HOC(...(Component)...)) is OK.
However, I think what you might need is connect(...)(DataSync(YourComponent)) instead of DataSync(connect(...)(YourComponent)) so that DataSync could also access state / props if needed. It really depends on the use case.
I had a very straight forward use case. I wanted to connect my HOC with redux store. In short I wanted to wrap my HOC with redux connect method.
// The HOC
const withMyHoc = (ReduxWrappedComponent) => props => <ReduxWrappedComponent {...props} />
// redux props
const mapStateToProps = (state) => ({});
const mapDispatchToProps = (dispatch) => ({});
// This is important
export default (WrappedComponent) =>
connect(
mapStateToProps,
mapDispatchToProps
)(withMyHoc(WrappedComponent));
There are two many answers in this thread. All of them helped me. Just putting down what actually worked in my case.
I use and like the same approach that #The Reason mentioned. The only problem here is that if you map your actions you won't have dispatch() available.
The way how I managed to make it work in case someone is facing the same problem was the following.
const ConnectedComponentWithActions = connect(
() => { return {}; },
{ myAction },
)(ViewComponent);
export default connect(
state => state,
null,
)(withPreFetch(firstLoadAction, ConnectedComponentWithActions));
Where withPreFetch(firstLoadAction, ConnectedComponentWithActions) is the HOC accepting an action to be dispatched.