I'm trying to create an HOC component for a presentational component and having a bit of trouble with the syntax.
Let's say my presentational component is called BlogViewerBase and let's call the HOC component BlogViewerHoc. I want the following:
I want to include some handler functions in my HOC component
I want the HOC component to connect to my Redux store, get state and pass it to the base component
Does this code look right?
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// Actions
import * as myActions from '../actions/myActions';
// Base component
import BlowViewerBase from '../components/blogViewerBase';
function hocBlogViewer(BlogViewerBase) {
return class BlogViewerHoc extends React.Component {
handlerFunction1() {
// Some logic here
}
handlerFunction2() {
// Some logic here
}
render() {
return <BlogViewerBase {...this.props} />
}
}
}
function mapStateToProps(state) {
return {
var1: state.module.variable1,
var2: state.module.variable2
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(myActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(BlogViewerHoc(BlogViewerBase));
Where I'm struggling is that the examples of HOC components I've come across look more like functions and I think I'm forming mine more like a component so not sure if I'm connecting to the store the right way. Not sure if the mapPropsToState, mapDispatchToState and the handler functions are in the right places.
Define your HOC and then pass your presentational component to it. Also, you can bind an action creator to your props in mapDispatchToProps. Something like:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { myActionCreator } from 'yourPathToYourActions';
// Actions
import * as myActions from '../actions/myActions';
// Base component
import BlowViewerBase from '../components/blogViewerBase';
function hocBlogViewer(WrappedComponent) {
return class extends React.Component {
handlerFunction1() {
// Some logic here
}
handlerFunction2() {
// Some logic here
}
componentDidMount() {
// I can dispatch this action now
this.props.myActionInProps();
}
render() {
return <WrappedComponent {...this.props} />
}
}
}
function mapStateToProps(state) {
return {
var1: state.module.variable1,
var2: state.module.variable2
};
}
function mapDispatchToProps(dispatch) {
return {
myActionInProps: dispatch(myActionCreator())
};
}
export default connect(mapStateToProps, mapDispatchToProps)(hocBlogViewer(BlowViewerBase));
Your code seems to be OK to me :)
Maybe for simplicity of reading I would do the following adjustments (but this is just my opinion):
const connector = connect(mapStateToProps, mapDispatchToProps)
...
export default function hocBlogViewer(BlogViewerBase) {
return connector(
class BlogViewerHoc extends React.Component {
...
Related
Using typescript for react as the language i have declared a class with constraints . I need to apply the connect method to it
import * as React from 'react';
import { connect } from 'react-redux';
import { initAskadeFiles } from '../Entities/Askade/Askade.Actions';
import { Dispatch } from 'redux';
interface IProp<T> {
PropOne: T
}
interface IState<T> {
StateOne: T
}
class BaseEdit<T> extends React.Component<IProp<T>, IState<T>> {
}
export function mapDispatchToProps(dispatch: Dispatch, ownProps: any) {
return {
InsertItem: () => dispatch(initAskadeFiles())
}
}
export default connect(null, mapDispatchToProps)(BaseEdit);
And in the calling component below is the syntax
import * as React from 'react';
import BaseEditSample from './BaseEditSample';
import { City } from '../Components/GridFunctionality/City';
export class ComplexEditSample extends React.Component {
public render(): any {
<BaseEditSample<City> />
}
}
When i use the syntax with City being passed to it i get an error
what am i missing in this i need redux to be connected to this component along with the contraints? Thanks
[ts] Expected 0 type arguments, but got 1.
First, you can pass your actions directly to the connect without mapDispatchToProps like this:
export default connect(mapStateToProps, { state, actions })(Component);`
And in your component use the imported actions and states:
type ComponentProps = {state, actions};
export default class Component extends React.Component<ComponentProps> {
render() {
const {
data,
actions,
} = this.props
return (...)
}
I have the following parent component:
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
import _ from "lodash";
import ChildComponent from "./ChildComponent";
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>
I'm at Parent
<ChildComponent/>
</div>
);
}
}
function mapStateToProps(state) {
return { }
}
export default connect(mapStateToProps, null)(ParentComponent);
Inside the parent has a component called ChildComponent that looks like this:
import React, { Component, PropTypes } from "react";
import { connect } from "react-redux";
import { reduxForm } from "redux-form";
import { bindActionCreators } from "redux";
class ChildComponent extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
}
render() {
return (
<div>
at the child
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
},
dispatch
);
}
function mapStateToProps(state) {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(
ChildComponent
);
When I try adding the child component I keep getting this error:
But if I click continue the page turns back to normal. I don't understand how the child component is undefined. It's just embedded and does not include any props.
UPDATE:
I'm not getting the error anymore but I notice my page turns blank when I open this particular component. I'll be doing a bit more troubleshooting.
I tried out your code, and it works fine for me. My thought was maybe what your entry file looks like? or file structure? If you like you can try the following syntax for the parent and child - it worked this way as well:
Child:
const mapStateToProps = () => {
return {}
}
const ConnectedChildComponent = connect(
mapStateToProps,
{})(ChildComponent)
export default ConnectedChildComponent;
Parent:
const mapStateToProps = () => {
return {}
}
const ConnectedParentComponent = connect(
mapStateToProps,
{})(ParentComponent)
export default ConnectedParentComponent;
In your ParentComponent change:
import ChildComponent from "./ChildComponent";
to
import ChildComponent from "./ChildComponent.jsx";
i.e. add the missing ".jsx" extension. Your code is most likely determining the import to be a ".js" file by default, whereas it's actually a ".jsx" file.
React and Redux experts.
I am new to React and Redux. My question is related to trigger callback (function) invocation when a Redux state is changed. I am stuck into this implementation. In my understanding, the presenter/view is updated via the props. Let me illustrate more in the following example.
<ParentComponent>
<Child1Component/>
<Child2Component/>
</ParentComponent>
class ParentComponent extends Component {
onChild1Click() {
this.props.dispatch(setTool(Tools.CHILD1TOOL))
}
render() {
return (
<div>
<Child1Component onChild1Click={this.onChild1Click.bind(this)}/>
<Child2Component/>
</div>
)
}
}
const mapStateToProps = state => {
return {state}
}
export default connect(
mapStateToProps
)(ParentComponent)
class Child1Component extends Component {
componentDidUpdate() {
// Question: How to get the Redux state here?
}
render() {
return (
<button onClick={this.props.onPencilClick}>Pencil</button>
)
}
}
Suppose a button is present in the Child1Component and a onclick is attached to such button. In my understanding of Redux, an action should be attached to this onclick and it should be dispatched. Such state will be modified in the ParentComponent and trigger props update. Afterwards, the UI/Presenter of Child1Component will be updated via props instead of any callback of Child1Component.
Is it possible to trigger a callback in Child1Component when a state is altered? The reason I need to make such implementation is that a 3rd party library is adopted. It requires to trigger callback. Actually, the onclick can trigger the function (callback) directly. However, the state cannot be maintained.
Could any expert advise it, please? Thanks a million.
P.
As I understand, this is not directly related to redux. You can use the react life cycle methods for this purpose. In your case, I think you need the componentDidUpdate or componentWillUpdate methods.
You can read more about life cycle methods here,
https://reactjs.org/docs/react-component.html
Explanation
First, make sure that you have connected the components to the Redux store using the react-redux bindings. Then, if you have correctly defined the mapStateToProps function, your child component will update whenever the state changes. Thus, whenever the component is updated, the componentWillUpdate and componentDidUpdate methods will be called.
Example in ES6 style
First, we'll bind the full redux state to the child component. Note: Generally you would not bind the full state, but only a branch of it.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import ChildComponent from './ChildComponent';
function mapStateToProps(state) {
return {
// this will bind the redux state to the props of the child component
reduxState: state
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
// some action creators
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChildComponent);
Then we can access the redux state from the child component.
class ChildComponent extends React.Component {
componentWillMount(nextProps, nextState) {
// Do something here
console.log(this.props.reduxState)
// this.props.reduxState is accessible from anywhere in the component
}
render() {
return <div>{/*Some jsx here*/}</div>
}
}
I strongly recommend you to read about redux usage with react section from redux docs and about smart-dumb component separation
First off, thank you for the replies. I came up the solution eventually. Here it is.
// actions.js
export const SET_TOOL = 'SET_TOOL'
export const Tools = {
CHILD1TOOL: 'child1tool',
DEFAULT: 'default'
}
export function setTool(tool) {
return {
type: SET_TOOL,
tool
}
}
// reducers.js
import { combineReducers } from 'redux'
import { SET_TOOL, Tools } from './actions'
const { DEFAULT } = Tools
function currentTool(state = DEFAULT, action) {
switch(action.type) {
case SET_TOOL:
return action.tool
default:
return state
}
}
const myApp = combineReducers({
currentTool
})
export default myApp
// ParentComponent.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Tools, setTool } from './actions'
import Child1Component from './Child1Component.jsx'
class ParentComponent extends Component {
render() {
return (
<div>
<Child1Component onChild1Click={this.props.onChild1Click'}/>
</div>
)
}
}
const mapStatesToProps = state => {
return {state}
}
const mapDispatchToProps = dispatch => {
return {
onChild1Click: () => {
dispatch(setTool(Tools.CHIDL1TOOL))
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ParentComponent)
// Child1Component.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Tools } from './actions'
class Child1Component extends Component {
componentDidUpdate() {
if (this.props.state.currentTool === Tools.CHILD1TOOL) {
this.callbackHandleClick()
}
}
render() {
return <button onClick={this.props.onChild1Click}>Child 1 Button</button>
}
callbackHandleClick() {
/* callback implementation */
}
}
const mapStateToProps = state => {
return {state}
}
export default connect(
mapStateToProps
)(Child1Component)
My situation is, I have the Navigation component, which is the base, and is listening to the Navigations state(Redux). It should be extended to HorizontalNavigation and VerticalNavigation, for easy reusable code in the future.
My problem is, right now I already have the "final" version of Navigation.jsx and I can extend it, as a class, but can't override it's methods. It triggers the super(Navigation) method and not the final one. I need to override the methods in Horizontal or Vertical components.
There is no code erros on console, so it isn't something breaking, but that I don't know how to handle how to extend it.
Navigation.jsx
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { itemAction, stageAction } from 'Store/Actions/Actions';
class Navigation extends Component {
// ACTIONS
leftAction () {
this.onLeftAction();
}
onLeftAction () {}
rightAction () {
this.onRightAction();
}
onRightAction () {}
downAction () {
this.onDownAction();
}
onDownAction () {}
upAction () {
this.onUpAction();
}
onUpAction () {}
// STAGES
nextStage (slug) {
this.goToStage(slug);
}
previousStage (slug) {
this.goToStage(slug);
}
goToStage (slug) {
// Just for illustration purpose
// let { dispatch } = this.props;
// dispatch(stageAction(slug));
}
// ITEMS
nextItem (index) {
this.goToItem(index);
}
previousItem (index) {
this.goToItem(index);
}
goToItem (index) {
// Just for illustration purpose
// let { dispatch } = this.props;
// dispatch(itemAction(index));
}
render () {
return ();
}
}
function mapStateToProps (state, props) {
navigation: state.Navigations[props.slug]
}
export default connect(mapStateToProps)(Navigation);
Horizontal.jsx
import React from 'react';
import Navigation from 'Components/Navigation/Navigation';
class HorizontalNavigation extends Navigation {
onLeftAction (previousItemIndex) {
this.previousItem(previousItemIndex);
}
onRightAction (nextItemIndex) {
this.nextItem(nextItemIndex);
}
onTopAction (slug) {
this.previousStage(slug);
}
onDownAction (slug) {
this.nextStage(slug);
}
}
export default HorizontalNavigation;
The VerticalNavigation would be the opposite. Left and right for stage; up and down for items.
I don't want to reuse the Navigation component each time I could use Horizontal or Vertical, and rewrite the same exact logic over and over again.
I'm using the Higher-Order Component pattern, exporting a function to connect the extended component, eg:
import { connect as reduxConnect } from 'react-redux'
...
export class Navigation extends Component{
...
export function connect(Component){
return reduxConnect(
(state, props)=>({...})
)(Component);
}
export default connect(Navigation)
And in the Horizontal.jsx you could do
import { Navigation, connect } from './Navigation';
class Horizontal extends Navigation{
...
export default connect(Horizontal);
This way, you keep the connect(mapStateToProps) in one place.
This is a fun one. At the bottom of Navigation, you're exporting the connecting component, which in essence is exporting the class created in connect, which is not the same class as Navigation. So, when you extend the default exported class, you're actually extending the connect class. That's a mouthful.
To get this to work, you could also export your class (in addition to export default connect(mapStateToProps)(Navigation); at the bottom:
export class Navigation extends Component {
Then to extend it, you can do:
import { Navigation } from './Navigation';
class Horizontal extends Navigation {
// ...
However, you would also need connect the Horizontal component as well in order to get the right props from redux.
If you don't want to use connect, you could take in props to your Navigation component that changes how those up/down/left/right actions work, then you could create a Horizontal/Vertical component that passes in the right props. Something like:
class Horizontal extends React.Component {
constructor(props, context) {
super(props, context);
this.onUp = this.onUp.bind(this);
this.onDown = this.onDown.bind(this);
this.onLeft = this.onLeft.bind(this);
this.onRight = this.onRight.bind(this);
}
onUp() {}
onDown() {}
onLeft() {}
onRight() {}
render() {
return (
<Navigation onUp={this.onUp} onDown={this.onDown} onLeft={this.onLeft} onRight={this.onRight} />
);
}
);
I have the following smart component and I am specifying the initialUpload function is a required func:
import React, { Component, PropTypes } from 'react';
import UploadForm from '../../components/UploadForm/UploadForm';
import HeaderSelection from '../../components/HeaderSelection/HeaderSelection';
import { initialUpload } from '../../redux/modules/Upload';
import { connect } from 'react-redux';
console.log(typeof initialUpload); //function
#connect((state) => {
return {
file: state.getIn(['upload', 'file'])
};
}, {
initialUpload
})
export default class Home extends Component {
static propTypes = {
initialUpload: PropTypes.func.isRequired
};
render() {
return (
<div>
<UploadForm handleFilesChange={this.props.initialUpload}/>
<HeaderSelection/>
</div>
);
}
}
But I get the error message:
warning.js:36Warning: Failed prop type: The prop initialUpload is
marked as required in Connect(Home), but its value is undefined.
The function is wrapped in dispatch and passed down the component hierarchy fine so I am confused as to what is going on.
#connect is assigning the static propTypes to the resulting rapping component and not to Home itself, which is rather a weird behaviour! I think it is caused by ES7 transformation, this behaviour was not present in previous versions
A work around is to define the propTypes on the wrappedComponent outside class definition:
#connect(...)
export default Home extends Component{
render() {
// ....
}
}
Home.WrappedComponent.propTypes = {
initialUpload: PropTypes.func.isRequired
}
another option would be not to use the decorator & use connect directly