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.
Related
I have created simple react component and write test cases of components that are working correctly. I have got coverage report for the test cases.
Now, I have added react redux in my other component. this component contains componentDidMount() and export default connect(null, updateProps)(ComponentName) methods. I need to write unit test cases for these methods.
Please refer to the below code sample,
class MyComponent extends Component {
componentDidMount = () => {
//some code here
)
handleSignIn = (e) => {
//some code here
}
render() {
return (
<div>
<form onSubmit={this.handleSignIn}>
<Input
type="text"
name="inputText"
placeholder="Text"
autoFocus
required
/>
</form>
</div>
);
}
const updateProps = (dispatch) => {
return {
//some code here
};
};
export default connect(null, updateProps)(MyComponent);
In your code you have two things:
class MyComponent
and
const thisIsBasicallyAnotherComponent = connect(null, updateProps)(MyComponent);
So if you want to test your component you basically have two options. You can test your component wrapped and connected to the redux store or you can write a simple unit test for your class component as it is.
What I would recommend doing is to export your class component
- class MyComponent extends Component { // replace this
+ export class MyComponent extends Component { // with this
And then you can test your React component with Jest like any other component.
test('Link changes the class when hovered', () => {
const component = renderer.create(
<MyComponent {...mockProps} /> // !! keep in mind that you have to manually pass what you have in `updateProps` because the component is not connected to Redux store anymore
);
// ... write your test and expectations here
});
Otherwise, you can test your connected component (what is exported by default) but then you would have to wrap the component in Redux provider in order to test it.
You can find more information about testing here:
How to test components
How to test connected components
You can use Provider from react-redux or redux-mock-store to avoid need to use real reducer:
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import MyComponent from './MyComponent.jsx';
const mockStore = configureStore([thunk]);
it('does something on mount', () => {
// let's mock some Redux state
const store = mockStore({ slice1: { id: 2, name: '11' } });
mount(<Provider store={store}><MyComponent /></Provider>);
expect(store.getActions()).toContainEqual({
type: 'some-type',
payload: ....
});
});
But this is that easy only to simple actions. What if you use redux-thunk and there is some loading? There are 2 ways:
Pass redux-thunk middleware to mockStore and mock network, say, by using mock-fetch or nock. Easier to set up, but also it might be overkill if you already test your Redux directly, repeating tests for "loading failed", "loading takes long" etc also to your component would mean double work.
You can mock ../yourPath/actions.js so every action there would be plain object, not a thunk. I typically go this way.
But what about "exporting unwrapped component so we could test component in isolation, without Redux"? You see, it was working when connect was the only possible API. But now with hooks like useSelector, useDispatch, useStore in mind, it's way more reliable to make tests for "my component IN Redux" first. Otherwise with "double exports" approach we may find out than converting component from class to function means way more work on patching tests, not on component itself.
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.
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
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 am currently building an app with React, React Router and React Redux
Versions:
React - v15.5.4
React Router - v4.0
React Redux - v.5.0.6
I am new to React and even newer to Redux and right when I got my head around the connect HOC I started to have this error that I cant seem to figure out.
When I connect a component to my redux store after a <switch> element and some <Route> elements. My connect within that returns my props as false boolean values where as the component within the connect has the correct props.
See code and error below for example.
Component
UserDashboardPage = connect(state => {
console.log("STATE", state);
return {
user: state.user.user,
userAuth: state.user.userAuth,
userFetched: state.user.fetched
};
})(UserDashboardPage);
UserDashboardPage.propTypes = {
user: PropTypes.shape(),
userAuth: PropTypes.shape(),
userFetched: PropTypes.boolean,
dispatch: PropTypes.func
};
CONSOLE LOG STATE
Connect with boolean prop values
Component with correct props
ERROR:
You are overwriting the local UserDashboardPage variable with the result of calling connect(). You then set PropTypes on the component returned by connect().
While you can do that, what you want in this case is to set the PropTypes of the wrapped component, not the wrapper component. Just swapping the order of execution will do it:
UserDashboardPage.propTypes = {
};
UserDashboardPage = connect(state => {
...
})(UserDashboardPage);
But you may want to consider using a different variable name for one component or the other, e.g.
UserDashboardPage.propTypes = {
};
const ConnectedUserDashboardPage = connect(state => {
...
})(UserDashboardPage);
This is usually not a problem since most people just immediately export the connected component as the default export:
export default connect(...)
The false values you're seeing are from React assigning default values to those props that failed validation. And they will always fail validation since those props are pulled from context, not passed down as normal props.
why are you passing UserDashboardPage into connect? This should be your non connected component