So I just discovered HOC's (Higher Order Functions) yesterday and they are pretty sweet. In my development I do use lifecycle methods like componentDidUpdate fairly frequently. I have found that I would like to use many HOCs for one wrapper component like so:
export default compose(
connect(mapStateToProps),
RefreshHOC(FeedScreen),
LoggedInHOC(FeedScreen)
)(FeedScreen)
I have noticed that if I have the same lifecycle (say componentDidUpdate) method in the WrapperComponent and one of the HOCs both lifecycle methods work. The problem arises when I have a Wrapper Component that has a lifecycle method then two or more HOC's also have the same lifecycle method, then only the first HOC's lifecycle method runs (in the above example componentDidUpdate runs in RefershHOC but not in LoggedInHOC).
Is there a better way to design this pattern? Am I just getting some syntax incorrect? Should I just have 1 HOC for each special lifecycle method that I want to group logic?
Edit
Here is some example code that I think is sufficient enough:
class FeedScreen extends Component {
componentDidUpdate(prevProps) {
let {appbase, auth, dispatch} = this.props
console.log('fire')
}
}
const mapStateToProps = (state) => ({
info: state.info,
auth: state.auth,
appbase: state.appbase
})
export default compose(
connect(mapStateToProps),
LoggedInHOC(FeedScreen),
LoggedInHOC2(FeedScreen)
)(FeedScreen)
export const LoggedInHOC = WrapperComponent => props => class
ViewWithPropChanges extends Component {
componentDidUpdate(prevProps) {
console.log('fire LIHOC')
}
render(){
return (<WrapperComponent {...this.props}/>)
}
}
}
export const LoggedInHOC2 = WrapperComponent => props => class ViewWithPropChanges extends Component {
componentDidUpdate(prevProps) {
console.log('fire LIHOC2')
}
render(){
return (<WrapperComponent {...this.props}/>)
}
}
EDIT
Some of your code seems a bit strange to me:
export const LoggedInHOC = WrapperComponent => props => class
// Later
export default compose(
LoggedInHOC(FeedScreen)
)(FeedScreen)
LoggedInHOC here is a function that takes a component and returns a function that returns a component when it should probably be only a function that takes a component and returns a component.
I'm going to assume that the role your LoggedInHOC is to check whether a user is connected somehow, display the wrapped component if that's the case and redirect the user/show a login form otherwise.
You could write it like that:
export const LoggedInHOC = Component => class extends React.Component {
render () {
// Check if the user is connected
if (connected) {
return (
<Component
{...this.props}
/>
);
}
return <p>User not connected</p>;
}
};
And you would wrap your component like that
export default LoggedInHOC(Component);
// Or if you want to chain multiple hocs:
export default compose(
LoggedInHOC,
AnotherHOC
)(Component);
Now back to your original question about chaining multiple HOCs and componentDidUpdate lifecycle. I'm not sure what is the exact problem in your case, but writting:
export default compose(
HOC1,
HOC2
)(Component);
is equivalent to HOC1(HOC2(Component)). So in term of composition you have:
HOC1
HOC2
Component
And you have to keep in mind that when your HOC1 wrapper is updated, that will trigger an update in your HOC2 and in your Component but if you update your HOC2, that will not trigger an update to your HOC1.
I made a example codepen that displays a component wrapped in multiple HOCs each implementing a componentDidUpdate hook
Related
I have base component, let's say BaseContainer that connects to redux and has some methods. Now I want to create few CustomContainer components that should be connected to redux too and should have access to all methods and state of BaseContainer component.
So BaseContainer would be:
class BaseContainer extends React.Component {
state = {};
method1() {};
method2() {};
method3() {};
}
export default connect(mapStateToProps, mapDispatchToProps)(BaseContainer);
And one of CustomContainers should be:
class CustomContainer extends BaseContainer {
// should have access to all imports, methods and props of BaseContainer
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomContainer);
Tried this but seems that inheritance does not work well in React and it is not recommended too.
Here I get error Super expression must either be null or a function.
Tried other approach with using HoC:
class CustomContainer extends React.Component {
// should have access to all imports, methods and props of BaseContainer
}
export default connect(mapStateToProps, mapDispatchToProps)(BaseContainer(CustomContainer));
and now I'm facing error: Unhandled Rejection (TypeError): Object(...) is not a function
What is wrong and how can I achieve that my CustomContainer has access to all imports, props and state of BaseContainer ?
You should probably read over the react docs, specifically Composition vs. Inheritance. React favors composition over inheritance. BaseContainer also isn't a Higher Order Component, but rather it's a regular component, and it doesn't appear to return anything to render.
Higher Order Component
Here's an implementation I think would help get you close to what you're after
const withBaseCode = WrappedComponent => {
class BaseContainer extends Component {
state = {};
method1 = () => {...}
method2 = () => {...}
method3 = () => {...}
render() {
return (
<WrappedComponent
method1={this.method1}
method2={this.method2}
method3={this.method3}
{...this.props}
/>
);
}
}
const mapStateToProps = state => ({...});
const mapDispatchToProps = {...};
return connect(mapStateToProps, mapDispatchToProps)(BaseContainer);
};
Then to use it's just a normal HOC, so given some component
const CustomContainer = ({ method1, method2, method3, ...props}) => {
...
return (
...
);
};
const CustomContainerWithBaseCode = withBaseCode(CustomContainer);
Some App container
function App() {
return (
<div className="App">
...
<CustomContainerWithBaseCode />
</div>
);
}
Demo of above code minus actually connecting to a redux store.
I have a container component within my React and Redux application:
import { connect } from 'react-redux'
import MyComponent from '../components/mycomponent'
const mapStateToProps = state => ({
myData: state.myData[state.activeDataId]
})
export default connect(mapStateToProps)(MyComponent)
If state.myData[state.activeDataId] does not exist then I want to dispatch an action to fetchMyData or fetchMyDataIfNeeded.
Note that, at the moment, my container does not contain any JSX, it just forwards props to a presentational component. I have seen this being called a 'Pure Container' though I'm not sure if that's a common term.
Is there a common pattern to dispatch actions from a Pure Container? I am thinking without:
expecting the presentational component to worry about this logic by passing an onLoad event to it
making the container a React.Component and triggering via componentDidMount
Is it a bad idea to dispatch actions from mapStateToProps, mapDispatchToProps or mergeProps?
As noted elsewhere, doing this in the container is a bad idea.
Instead of worrying about this in the container, it makes sense to fetch the data conditionally in your component. I know you mentioned not wanting to extend react.component, but you should definitely consider making this component a class in order to fetch data in a component lifecycle hook.
As detailed in another answer, connect takes a second argument of mapDispatchToProps. Pass in the fetchData dispatcher there (example of how to do this here.)
Then, in your component you can check myData. If it is not there, then you dispatch via
this.props.whatYouCalledDispatch()
Yes, it is a bad idea to dispatch any action in container.
In your case, the best approach is:
Map your state, action creator to component props
Check the props in componentDidMount (or componentDidUpdate) and fetchDataYouNeed, then component will be updated
Your container should be:
import { connect } from 'react-redux';
import {fetchDataYouNeed} from './actions
import MyComponent from '../components/mycomponent';
const mapStateToProps = state => ({
myData: state.myData[state.activeDataId]
});
const mapDispatchToProps = (dispatch) => {
return {
fetchDataYouNeed: ()=>{
dispatch(fetchDataYouNeed());
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
Your component
class YourComponent extends Component{
componentDidMount(){
let {myData, activeDataId} = this.props;
if(myData && !myData[activeDataId]){
this.props.fetchDataYouNeed();
}
}
render(){
....
}
}
Learn more here https://facebook.github.io/react/docs/react-component.html#componentdidmount
This seems to work, though I'm not sure if it has any unintended effects:
import { connect } from 'react-redux'
import MyComponent from '../components/mycomponent'
import { fetchMyData } from '../actions/mydata'
const mapStateToProps = state => ({
dataId: state.activeDataId,
myData: state.myData[state.activeDataId]
})
const mapDispatchToProps = { fetchMyData }
const mergeProps = (stateProps, dispatchProps) => {
if (!stateProps.myData) {
dispatchProps.fetchMyData(stateProps.dataId)
}
return stateProps
}
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)
Alternatively, brianzinn suggested that by using Redux Saga to manage side effects, this issue becomes redundant.
I have a number of React components that need to fetch certain items to display. The components could be functional components, except for a very simple componentDidMount callback. Is there a good design pattern that would allow me to return to functional components?
class Things extends React.Component {
componentDidMount() {
this.props.fetchThings()
}
render() {
things = this.props.things
...
}
}
I'm using react-redux and I'm also using connect to connect my component to my reducers and actions. Here's that file:
import { connect } from 'react-redux'
import Things from './things'
import { fetchThings } from '../../actions/thing_actions'
const mapStateToProps = ({ things }) => ({
things: things,
})
const mapDispatchToProps = () => ({
fetchThings: () => fetchThings()
})
export default connect(mapStateToProps, mapDispatchToProps)(Things)
Would it make sense to fetch the things in this file instead? Maybe something like this:
import { connect } from 'react-redux'
import Things from './things'
import { fetchThings } from '../../actions/thing_actions'
const mapStateToProps = ({ things }) => ({
things: things,
})
class ThingsContainer extends React.Component {
componentDidMount() {
fetchThings()
}
render() {
return (
<Things things={this.props.things} />
)
}
}
export default connect(mapStateToProps)(ThingsContainer)
Functional components are meant to be components that don't do anything. You just give them props and they render. In fact, if your component needs to fetch anything at all, it's likely your component should be transformed into a container which fetches the data you need. You can then abstract the UI part of your component into one or more pure functional components which your container renders by passing the data it got as props.
I believe the presentational/container component split is the pattern you're looking for here.
I have a container that passes props and an apiCall action to a component which will mainly just render the result of that call. My question is should I leave the invoking of that action up to the component or move it out into the container and just pass the array of items to the component?
Here is my container code. The fetchShowingsListShowings is the one in question. Also, I will be renaming that soon enough so bear with me.
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as actions from '../actions/showingsListActions';
import ShowingsList from '../components/ShowingsList';
const ShowingsListContainer = (props) => {
return (
<ShowingsList
isLoading={props.isLoading}
showings={props.showings}
fetchShowingsListShowings={props.actions.fetchShowingsListShowings}
/>
);
};
ShowingsListContainer.propTypes = {
isLoading: PropTypes.bool.isRequired,
showings: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
const mapStateToProps = (state) => {
return {
isLoading: state.showingsList.isLoading,
showings: state.showingsList.showings
};
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(actions, dispatch)
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ShowingsListContainer);
And my component. Which calls the API action on componentWillMount.
import React, { PropTypes } from 'react';
import ShowingsListItem from './ShowingsListItem';
class ShowingsList extends React.Component {
componentWillMount() {
this.props.fetchShowingsListShowings();
}
render() {
return (
this.props.isLoading ? <h1>Loading...</h1> :
<ul className="list-unstyled">
{this.props.showings.map((showing,index) => <ShowingsListItem showing={showing} key={'showing' + index}/>)}
</ul>
);
}
}
ShowingsList.propTypes = {
isLoading: PropTypes.bool.isRequired,
showings: PropTypes.array.isRequired,
fetchShowingsListShowings: PropTypes.func.isRequired
};
export default ShowingsList;
Thanks in advance.
So in React with Redux the term 'Container' just means a component that is connected to the Store, essentially whatever you use the react-redux 'connect' method with. Your ShowingsList can be a 'dumb' (or functional) component meaning it's just a component that takes in data and displays content. The general 'best' practice is to have your dumb components just be concerned with presentation, and your container components handle all the logic interacting with the Redux Store. If you follow this logic, fetch the data in the container, and pass the data to the nested component. That being said, it'll work either way so you don't really need to change anything if you're happy with it now.
To follow this pattern do something like this:
modify your Container component to be an ES6 class extends React.Component.. and optionally change your ShowingsList to be a functional component (like your ShowingsList is now)
put a componentWillMount in your Container and put the API call there.
pass the list to the presentational component.
Here's an article written by Dan Abramov, the author of Redux on this very topic.
https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.g695y2gwd
I have a component SampleComponent that mounts another "connected component" (i.e. container). When I try to test SampleComponent by mounting (since I need the componentDidMount), I get the error:
Invariant Violation: Could not find "store" in either the context or
props of "Connect(ContainerComponent)". Either wrap the root component
in a , or explicitly pass "store" as a prop to
"Connect(ContainerComponent)".
What's the best way of testing this?
Enzyme's mount takes optional parameters. The two that are necessary for what you need are
options.context: (Object [optional]): Context to be passed into the component
options.childContextTypes: (Object [optional]): Merged contextTypes for all children of the wrapper
You would mount SampleComponent with an options object like so:
const store = {
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ... whatever state you need to pass in ... })
}
const options = {
context: { store },
childContextTypes: { store: React.PropTypes.object.isRequired }
}
const _wrapper = mount(<SampleComponent {...defaultProps} />, options)
Now your SampleComponent will pass the context you provided down to the connected component.
What I essentially did was bring in my redux store (and Provider) and wrapped it in a utility component as follows:
export const CustomProvider = ({ children }) => {
return (
<Provider store={store}>
{children}
</Provider>
);
};
then, I mount the SampleComponent and run tests against it:
it('contains <ChildComponent/> Component', () => {
const wrapper = mount(
<CustomProvider>
<SampleComponent {...defaultProps} />
</CustomProvider>
);
expect(wrapper.find(ChildComponent)).to.have.length(1);
});
Option 1)
You can wrap the container component with React-Redux's Provider component within your test. So with this approach, you actually reference the store, pass it to the Provider, and compose your component under test inside. The advantage of this approach is you can actually create a custom store for the test. This approach is useful if you want to test the Redux-related portions of your component.
Option 2)
Maybe you don't care about testing the Redux-related pieces. If you're merely interested in testing the component's rendering and local state-related behaviors, you can simply add a named export for the unconnected plain version of your component. And just to clarify when you add the "export" keyword to your class basically you are saying that now the class could be imported in 2 ways either with curly braces {} or not. example:
export class MyComponent extends React.Component{ render(){ ... }}
...
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
later on your test file:
import MyComponent from 'your-path/MyComponent'; // it needs a store because you use "default export" with connect
import {MyComponent} from 'your-path/MyComponent'; // don't need store because you use "export" on top of your class.
I hope helps anyone out there.
There is also the option to use redux-mock-store.
A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which serve as an action log for tests.
The mock store provides the necessary methods on the store object which are required for Redux.
You can specify optional middlewares and your app specific initial state.
import configureStore from 'redux-mock-store'
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState = {}
const store = mockStore(initialState)
const wrapper = mount(<SampleComponent store={store}/>)
You can use name export to solve this problem:
You should have:
class SampleComponent extends React.Component{
...
render(){
<div></div>
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SampleComponent)
You can add a export before class:
export class SampleComponent extends React.Component{
and import this component with no redux store:
import { SampleComponent } from 'your-path/SampleComponent';
With this solution you don't need to import store to your test files.
in an attempt to make the use of decorator syntax more testable I made this:
https://www.npmjs.com/package/babel-plugin-undecorate
input:
#anyOldClassDecorator
export class AnyOldClass {
#anyOldMethodDecorator
method() {
console.log('hello');
}
}
output:
#anyOldClassDecorator
export class AnyOldClass {
#anyOldMethodDecorator
method() {
console.log('hello');
}
}
export class __undecorated__AnyOldClass {
method() {
console.log('hello');
}
}
Hopefully this can provide a solid Option 3!