React/Redux/Immutable - confusion regarding strategy for HOC with child component - reactjs

I hope I can make sense of all of this in question format. I have been trying to work my way through this was the past 7 or so hours without success. My brain has simply run dry at this point and I am at a dead end regardless.
I am working with react 15.6.1, redux 3.7.1, react-redux 5.0.5, redux-immutable 4.0.0, redux-form 7.2.0, react-select 1.2.1.
My application has search functionality with field A and field B using 2 different forms (different forms for different pages). I don't it is pivotal to this question but I am using redux-form and react-select for the forms and search fields. I am storing user-entered search criteria in my Search reducer to synchronise autopopulation etc of select lists across the different forms.
redux-form---->react-select(field A)
---->react-select(field B)
My Search reducer is using Immutable.fromJs() in the initialState(). The reducer is working as expected. The problem I have is where to use the HOC in order to convert the Map object returned from the reducer into the JS array required for the react-select component.
MainSearchForm.js: -
import React, {Component} from 'react'
import { Field, reduxForm } from 'redux-form'
import { connect } from 'react-redux'
import FormFieldA from './FormFieldA'
import FormFieldB from './FormFieldB'
class MainSearchForm extends Component {
render() {
return(
<form>
<FormFieldA options={this.props.fieldAoptions}/>
<FormFieldB options={this.props.fieldBoptions}/>
</form>
)
}
}
function mapStateToProps ({Search}, props) {
return {
fieldAoptions: Search.get('fieldAoptions'),
fieldBoptions: Search.get('fieldBoptions'),
}
}
MainSearchForm = connect(mapStateToProps,{})(MainSearchForm);
export default reduxForm({
form: 'main-search',
})(MainSearchForm)
To simply the example, both FormFieldA and FormFieldB components follow as the same: -
import React, {Component} from 'react'
import Select from 'react-select';
class FormFieldA extends Component {
render() {
const handleOnChange = (value) => {
// call dispatch here
}
return(
<Select
name="field-a-input"
id="field-a"
options={this.props.options}
onChange={handleOnChange}
/>
)
}
}
export default FormFieldA
So the options prop in the react-select component must be a JS array: -
options: [
{ label: 'Red' },
{ label: 'Green' },
{ label: 'Blue' }
]
I can convert this using Immutable.toJS() but the Redux official guide recommends against this for performance reasons, recommending this pattern using an (I assume reusable) HOC component to parse the Immutable Map to the JS array.
My question is, how would I incorporate this? As you can see in the code above, right now my MainSearchForm is connecting to the Redux store to retrieve the options data required as options for the react-select options props. Would the answer be to just not have MainSearchForm and instead have a intermediary component for each field rendered by MainSearchForm, with this intermediary component call the HOC before using connect, as is seen in the guide: -
HOC from the Redux Immutable guide: -
import React from 'react'
import { Iterable } from 'immutable'
export const toJS = WrappedComponent => wrappedComponentProps => {
const KEY = 0
const VALUE = 1
const propsJS = Object.entries(
wrappedComponentProps
).reduce((newProps, wrappedComponentProp) => {
newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(
wrappedComponentProp[VALUE]
)
? wrappedComponentProp[VALUE].toJS()
: wrappedComponentProp[VALUE]
return newProps
}, {})
return <WrappedComponent {...propsJS} />
}
Example intermediary smart component to parse the Immutable Map through the HOC and connect() with FormFieldA: -
import { connect } from 'react-redux'
import { toJS } from './to-js'
import FormFieldA from './FormFieldA'
function mapStateToProps ({Search}, props) {
return {
fieldAoptions: Search.get('fieldAoptions'),
}
}
export default connect(mapStateToProps)(toJS(FormFieldA))
Would that be best practice?
I would sincerely appreciate any help with this. This is my first time working with HOC & Immutable and it's a lot to take in. But I think I have finally got a grasp of the paradigm.

Don't worry too much about best practises, the best practises of today are the anti patterns of tomorrow. Don't get me wrong is good to know what's the "best" way doing things, but the best is always relative so take that into consideration.
The idea is that your FormFieldA and FormFieldB shouldn't care about redux, immutable, foobar, bajouras, wtvLibraryOrToolThatMightCome.
So your HOC, for the immutable should be in, your MainSearchForm:
import React, {Component} from 'react'
import { Field, reduxForm } from 'redux-form'
import { connect } from 'react-redux'
import { toJS } from './to-js'
import FormFieldA from './FormFieldA'
import FormFieldB from './FormFieldB'
class MainSearchForm extends Component {
render() {
return(
<form>
<FormFieldA options={this.props.fieldAoptions}/>
<FormFieldB options={this.props.fieldBoptions}/>
</form>
)
}
}
function mapStateToProps ({Search}, props) {
return {
fieldAoptions: Search.get('fieldAoptions'),
fieldBoptions: Search.get('fieldBoptions'),
}
}
MainSearchForm = connect(mapStateToProps,{})(toJS(MainSearchForm));
export default reduxForm({
form: 'main-search',
})(MainSearchForm)
Leave your FormFieldA and FormFieldB as they are, very dumb and just waiting for a simple array of options.
This is just a personal taste, but I usually even separate the component from the redux stuff.
For example with your case I would have MainSearchForm and a MainSearchFormContainer
import React, {Component} from 'react'
import FormFieldA from './FormFieldA'
import FormFieldB from './FormFieldB'
class MainSearchForm extends Component {
render() {
return(
<form>
<FormFieldA options={this.props.fieldAoptions}/>
<FormFieldB options={this.props.fieldBoptions}/>
</form>
)
}
}
export default MainSearchForm
And the container:
import { Field, reduxForm } from 'redux-form'
import { connect } from 'react-redux'
import MainSearchForm from './MainSearchForm'
import { toJS } from './to-js'
function mapStateToProps ({Search}, props) {
return {
fieldAoptions: Search.get('fieldAoptions'),
fieldBoptions: Search.get('fieldBoptions'),
}
}
const MainSearchFormContainer = connect(mapStateToProps,{})(toJS(MainSearchForm));
export default reduxForm({
form: 'main-search',
})(MainSearchFormContainer)

Related

React Connected Component package without host having redux

I'm new to react and I'm trying to create a connected component (redux) for internal use in the company but I don't want to force the host app to have redux. I want to avoid having my component used like this:
<Provider store={store}><MyComponent /></Provider>
I thought in using something like Higher-Order Components and Contexts, but it is just not clicking.
Even creating my own provider would be acceptable, something like:
<MyComponentProvider><MyComponent /></MyComponentProvider>
Is it possible for a host application to use a Redux Component without having redux as a dependency?
It should be possible. The component can have its own redux store. Keep it as a complete black box.
Here is a demo, consider the package directory is your npm package and we install the redux, react-redux packages in the npm package, not your host or main project.
package/combobox.tsx:
import * as React from 'react';
import { useSelector } from 'react-redux';
export const Combobox = () => {
const state = useSelector((state) => state);
console.log('state: ', state);
return <div>combobox</div>;
};
we can wrap it in a special component that initializes the store in the constructor:
package/index.tsx:
import { Component } from 'react';
import * as React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { Combobox } from './combobox';
const reducer = (state = { counter: 1 }) => {
return state;
};
export default class ComboboxConnected extends Component {
store: ReturnType<typeof createStore>;
constructor(props) {
super(props);
this.store = createStore(reducer);
}
render() {
return (
<Provider store={this.store}>
<Combobox />
</Provider>
);
}
}
Now, each Combobox component instance has its own redux store and state.
Consumer side
App.tsx:
import * as React from 'react';
import Combobox from './package/index';
import './style.css';
export default function App() {
return (
<div>
<Combobox />
</div>
);
}
See Isolating Redux Sub-Apps
stackblitz

Issues with initialising Redux in React while using mapDispatchToProps

I'm trying to learn some redux. Component I'm working on is a simble <div> based button that - when clicked - passes value as a parameter to the dispatch, so it can be displayed later someplace else.
After following both documentation and tutorials over the web I came up with the following code:
main app container
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import configureStore from './../store/configureStore.js'
import Input from './input.js'
let store = configureStore()
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Input />
</Provider>
)
}
}
button container
import React, { Component } from 'react'
import { connect } from 'react-redux'
import printOut from './../actions/actions.js'
import InputComponent from './../components/inputComponent.js'
import PropTypes from 'prop-types'
const mapDispatchToProps = (dispatch) => {
return {
onClick: (input) => dispatch(printOut(input))
}
}
const Input = connect(mapDispatchToProps)(InputComponent)
export default Input
button component
import React, { Component } from 'react'
import PropTypes from 'prop-types'
class Input extends Component {
render() {
return (
<div style={style} onClick={this.props.onClick('lala')}>Button!</div>
)
}
}
Input.PropTypes = {
onClick: PropTypes.func.isRequired
}
const style = {
height: 30,
width: 100,
backgroundColor: '#ff4068'
}
export default Input
Application breaks. I got this from the console:
Uncaught TypeError: (0 , _actions2.default) is not a function
at Object.onClick (index.js:33683)
at Input.render (index.js:33743)
(...)
index.js:22443 The above error occurred in the <Input> component:
in Input (created by Connect(Input))
in Connect(Input) (created by App)
in Provider (created by App)
in App
From what little I understood, there are some issues with button component and the way I'm trying to pass the param to props. So I tried to change it a little and added a function to handle that before render.
...
onClick(input) {
return this.props.onClick(input)
}
render() {
return (
<div style={style} onClick={onClick('lala')}>Button!</div>
)
}
...
The error I get this time is onClick is not defined. Oh, ok. I forgot this keyword before calling this new function. So I add it to the component and now I have
<div style={style} onClick={this.onClick('lala')}>Button!</div>
But the error being returned didn't really changed - it's again the original error of Uncaught TypeError: (0 , _actions2.default) is not a function
I'm starting to run out of ideas now. Could someone please tell me how what my be the issue here?
Help me Stack Overflow, you're my only hope! to quote timeless classic.
Are you sure you are importing printOut in properly? Shouldn't it be import { printOut } from './../actions/actions.js' ?
Then, first argument in connect is mapStateToProps and the second is mapDispatchToProps this is probably why you have dispatch is not a function.
You are importing InputComponent:
import InputComponent from './../components/inputComponent.js'
but inside button component you are exporting it as Input:
export default Input
so change InputComponent with :
import Input from './../components/inputComponent.js'
Use this for connect
export default connect(mapDispatchToProps)(Input)
You are facing 2 problems.
1. Syntax problem in your import
The following problem Uncaught TypeError: (0 , _actions2.default) is not a function is caused by the import of your actions.
Instead of
import printOut from './../actions/actions.js'
It should be
import { printOut } from './../actions/actions.js'
2. You are incorrectly using connect
connect accepts these two arguments with the following order:
mapStateToProps: contains the props you want to give to the component
mapDispatchToProps: contains the actions to dispatch
Even if you could call your action, there is no way the dispatch will happen because you call the reducers instead of the dispatch.
What you should do is the following:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { printOut } from './../actions/actions.js';
import InputComponent from './../components/inputComponent.js';
import PropTypes from 'prop-types';
const mapDispatchToProps = (dispatch) => {
return {
onClick: (input) => dispatch(printOut(input))
}
}
export default connect(null, mapDispatchToProps)(InputComponent);
You can read this documentation for more details: http://redux.js.org/docs/basics/UsageWithReact.html#implementing-container-components

Dispatch is not available in this.props

I'm very new to React and trying to write an application which outputs a portfolio to one part of the page and, based on user interaction with that portfolio, displays some information in a lightbox/modal elsewhere in the DOM.
This requires that my two rendered components have some kind of shared state, and my understanding is that the best (or one of the best) way to achieve this is with Redux. However, being new to React and now adding Redux into the mix, I'm a little out of my depth.
I've created some (for now very dumb) action creators and reducers, all I'm trying to do initially is fetch some JSON and add it to my store. However, I'm not able to access dispatch from within my component and I'm not really sure where I'm going wrong.
If I console.log this.props from within my component I get an empty object, "{}".
Here are the main parts, any pointers would be really appreciated:
App.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import store from './redux/store';
import { Portfolio } from './redux/components/portfolio';
ReactDOM.render(
<Provider store={store}>
<Portfolio />
</Provider>,
document.getElementById('portfolioCollection')
);
actions/actionCreators.js:
export const populatePortfolio = obj => ({
type: POPULATE_PORTFOLIO,
obj
});
export const populateLightbox = obj => ({
type: POPULATE_LIGHTBOX,
obj
});
portfolio.js:
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from '../actions/actionCreators';
export class Portfolio extends React.Component {
componentDidMount() {
this.getPortfolioData();
}
getPortfolioData() {
fetch('/data.json')
.then( (response) => {
return response.json()
})
.then( (json) => {
// dispatch action to update portfolio here
});
}
render() {
return(
// render component
);
}
}
function mapStateToProps(state){
console.log('state', state);
return {
state: state
}
};
function mapDispatchToProps(dispatch) {
console.log('dispatch', dispatch);
return {
actions: bindActionCreators({ populatePortfolio: populatePortfolio }, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Portfolio);
this.props is empty because you have not passed any props. You are using the unconnected component instead of the one that has been connected to redux.
To fix this, replace this line:
import { Portfolio } from './redux/components/portfolio';
with
import Portfolio from './redux/components/portfolio';
You are exporting both the connected and the unconnected component. You probably only want the last export. Since the connected component is exported as default you import it without using {} deconstruction.
unless you need to import the unconnected component in tests or something like that, you can remove the export statement from this line, since it makes no sense to export something that you don't intend to import in another file.
export class Portfolio extends React.Component {
You aren't meant to manually call dispatch in your components. The action creator function is automatically bound to dispatch for you. Simply call this.props.populatePortfolio() in your component.

How to use this higher order component explained in the Redux Doc?

Could anyone please help me understand how to use this Higher Order Component explained in the redux document?
I know what Higher Order Component is and I have used this patter several times but I can't figure out how to use this Higher Order Component.
http://redux.js.org/docs/recipes/UsingImmutableJS.html#use-a-higher-order-component-to-convert-your-smart-components-immutablejs-props-to-your-dumb-components-javascript-props
Here is what it says...
Use a Higher Order Component to convert your Smart Component’s Immutable.JS props to your Dumb Component’s JavaScript props
Something needs to map the Immutable.JS props in your Smart Component to the pure JavaScript props used in your Dumb Component. That something is a Higher Order Component (HOC) that simply takes the Immutable.JS props from your Smart Component, and converts them using toJS() to plain JavaScript props, which are then passed to your Dumb Component.
Here is an example of such a HOC:
import React from 'react'
import { Iterable } from 'immutable'
export const toJS = WrappedComponent => wrappedComponentProps => {
const KEY = 0
const VALUE = 1
const propsJS = Object.entries(
wrappedComponentProps
).reduce((newProps, wrappedComponentProp) => {
newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(
wrappedComponentProp[VALUE]
)
? wrappedComponentProp[VALUE].toJS()
: wrappedComponentProp[VALUE]
return newProps
}, {})
return <WrappedComponent {...propsJS} />
}
And this is how you would use it in your Smart Component:
import { connect } from 'react-redux'
import { toJS } from './to-js'
import DumbComponent from './dumb.component'
const mapStateToProps = state => {
return {
// obj is an Immutable object in Smart Component, but it’s converted to a plain
// JavaScript object by toJS, and so passed to DumbComponent as a pure JavaScript
// object. Because it’s still an Immutable.JS object here in mapStateToProps, though,
// there is no issue with errant re-renderings.
obj: getImmutableObjectFromStateTree(state)
}
}
export default connect(mapStateToProps)(toJS(DumbComponent))
By converting Immutable.JS objects to plain JavaScript values within a HOC, we achieve Dumb Component portability, but without the performance hits of using toJS() in the Smart Component.
Here is my sample code for this!!
DumbComponent
import React from 'react';
const DumbComponent = (props) => {
const {dataList} = props;
const renderList = (list) => {
return list.map((value) => {
return <li>value</li>
})
};
return (
<ul>
{renderList(dataList)}
</ul>
)
};
export default DumbComponent;
SmartComponent
import React, { Component } from 'react';
import { connect } from 'react-redux';
import DumbComponent from './DumbComponent';
//High Order Component
import toJS from './toJS';
class SmartComponent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<DumbComponent dataList={this.props.dataList} />
);
}
}
function mapStateToProps(states) {
return {
//Let's say this is immutable.
dataList: state.dataList,
};
}
//this is how I use Higher Order Component.
//export default connect(mapStateToProps)(SmartComponent);
export default connect(mapStateToProps)(toJS(DumbComponent));
My Question
export default connect(mapStateToProps)(toJS(DumbComponent));
This doesn't even export SmartComponent itself. How can the parent component of SmartComponent whose child is DumbComponent use SmartComponent if it is not even exported??
Please tell me how to use this Higher Order Component in the sample code I prepared for this post.
Update
So, this is the correct way to write?
SmartComponent
import React, { Component } from 'react';
import { connect } from 'react-redux';
import DumbComponent from '../components/DumbComponent';
class SmartComponent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<DumbComponent dataList={this.props.dataList} /> //immutable
);
}
}
function mapStateToProps(states) {
return {
dataList: state.dataList //immutable
};
}
export default connect(mapStateToProps)(SmartComponent);
DumbComponent
import React from 'react';
import toJS from './higher_order_components/toJS';
const DumbComponent = (props) => {
const {dataList} = props;
const renderList = (list) => {
return list.map((value) => {
return <li>{value}</li>
})
};
return (
<ul>
{renderList(dataList)}
</ul>
)
};
export default toJS(DumbComponent);
When you do something like:
let Component = connect(mapStateToProps)(OtherComponent)
Component is "smart" because it has access to the store. What you're doing in your code is doubling up on smart components.
class OtherSmartComponent {
render { return <DumbComponent {...smartProps} /> }
}
let SmartComponent = connect(mapStateToProps)(OtherSmartComponent)
That's why in the example Smart Component there is no intermediate Smart Component, just connect, mapStateToProps and DumbComponent.
Might be worth unraveling it all:
import DumbComponent from './DumbComponent'
import toJS from './toJS'
let DumbComponentWithJSProps = toJS(DumbComponent)
let SmartComponent = connect(mapStateToProps)(DumbComponentWithJSProps)
export default SmartComponent
So really, in your code, SmartComponent isn't really smart. It's just a dumb component that renders another dumb component. The terminology makes this comment seem super harsh. 🤔
To clarify your comment:
If you want a component between your connected one and the one that's run through toJS, then do that. No need to call connect twice:
// Dumb.js
import toJS from './toJS
class Dumb { .. }
export default toJS(Dumb)
-----------------------------------
// Smart.js
import Dumb from './Dumb'
class Smart {
..methods..
render() { return <Dumb {...props} /> }
}
export default connect(mapStateToProps)(Smart)

Which way should I use for Connector in Redux?

I seen 2 ways of doing the same thing but I am not sure what is the proper way.
Component
import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {selectUser} from '../actions/index'
class UserList extends Component {
renderList() {
return this.props.users.map((user) => {
return (
<li
key={user.id}
onClick={() => this.props.selectUser(user)}
>
{user.first} {user.last}
</li>
);
});
}
render() {
return (
<ul>
{this.renderList()}
</ul>
);
}
}
// Get apps state and pass it as props to UserList
// > whenever state changes, the UserList will automatically re-render
function mapStateToProps(state) {
return {
users: state.users
};
}
// Get actions and pass them as props to to UserList
// > now UserList has this.props.selectUser
function matchDispatchToProps(dispatch){
return bindActionCreators({selectUser: selectUser}, dispatch);
}
// We don't want to return the plain UserList (component) anymore, we want to return the smart Container
// > UserList is now aware of state and actions
export default connect(mapStateToProps, matchDispatchToProps)(UserList);
https://github.com/buckyroberts/React-Redux-Boilerplate
Or
import React from "react"
import { connect } from "react-redux"
import { fetchUser } from "../actions/userActions"
import { fetchTweets } from "../actions/tweetsActions"
#connect((store) => {
return {
user: store.user.user,
userFetched: store.user.fetched,
tweets: store.tweets.tweets,
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
}
fetchTweets() {
this.props.dispatch(fetchTweets())
}
render() {
const { user, tweets } = this.props;
if (!tweets.length) {
return <button onClick={this.fetchTweets.bind(this)}>load tweets</button>
}
const mappedTweets = tweets.map(tweet => <li>{tweet.text}</li>)
return <div>
<h1>{user.name}</h1>
<ul>{mappedTweets}</ul>
</div>
}
}
https://github.com/learncodeacademy/react-js-tutorials/tree/master/5-redux-react
The first way uses 2 different functions mapStateToProps() and matchDispatchToProps() while the other way uses #connect(....).
When I use the #connect I get a whole bunch of warnings saying that it has not been finalized and might change.
The # symbol is a decorator which is still considered experimental. So I would use that at your own risk. Your first code block is the safer way to do it as described in the official docs. Both blocks essentially do the same thing but decorators are more sugar than anything.
References:
https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
What's the '#' (at symbol) in the Redux #connect decorator?
I think the first method will give you less problems in the end. Someone else can chime in though too.
The answer by Jackson is right in every sense however he is missing out the importance of using the first version for the usage of unit testing. If you want to be able to unit test a component (which usually means testing with the unconnected version) you need to be able to export the connected and unconnected component.
Using your example and assuming you are using jest/enzyme you could do something like this:
// notice importing the disconnected component
import { UserList } from '../relative/file/path/UserList'
import { mount } from 'enzyme'
describe('UserList', () => {
it('displays the Username', () => {
const users = [{fist: 'Person', last: 'Thing'}, ... ]
const UserList = mount(<UserList users={users} />)
export(UserList.find('li')[0].text()).toEqual('Person Thing')
});
});
Once you build larger projects being able to unit test will provide sanity to your coding life. Hope this helps

Resources