Using multiple redux connect HOCs in a single component - reactjs

Hi 👋recently I saw an interesting piece of code.
There was this HOC:
import { connect } from 'react-redux'
const mapProps = store => ({
someProp: store.some.prop,
})
const withSomeProp = connect(mapProps)
export default withSomeProp
and then it was used like this:
export default compose(
withSomeProp,
connect(
mapProps,
mapDispatch
),
)
Is this approach good? Or should I be worried about potential performance issues or any other bugs?

I would generally recommend defining a more complex mapState function that extracts both pieces of data that this component needs, rather than defining multiple connect definitions just to extract different pieces of data.

Related

Importing redux action makes other actions undefined

This is one of the strangest things I have ever seen. It makes absolutely no sense to me. The short version is I have a Redux action creator function. If I import this function into this one particular component file, it makes every function imported from its file undefined.
So, let's start with the file filterInputModal.actions.js. This contains my Redux action functions, created using redux-starter-kit:
export const showAddCategoryModal = createAction('showAddCategoryModal');
That is the function I've been working with. Now, this function has long since been imported into my ManageVideoFilters.js component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { showAddCategoryModal } from 'store/filterInputModal/filterInputModal.actions';
const ManageVideoFilters = (props) => {
/* Component logic */
};
/* PropTypes and mapStateToProps */
const mapDispatchToProps = (dispatch) => bindActionCreators({
showAddCategoryModal: () => showAddCategoryModal() // Done this way to avoid passing in a payload, since certain default event payloads cause Redux to print console errors
});
export default connect(mapStateToProps, mapDispatchToProps)(ManageVideoFilters);
So far so good. Before we go and break everything, let's take a look at my filterInputModal.reducer.js Redux reducer, also created using Redux Starter Kit:
import { createReducer } from 'redux-starter-kit';
import { showAddCategoryModal } from './filterInputModal.actions';
const initialState = {}; // The initial state for the reducer goes here
const handleShowAddCategoryModal = (state) => {
/* handle updating the state */
return state;
};
const actionMap = {
[showAddCategoryModal]: handleShowAddCategoryModal
};
export default createReducer(initialState, actionMap);
The action map uses the action creator functions toString() as the key, and then I provide my own functions to handle updating the state. Again, at this point, everything is perfect. We will come back to the reducer in a sec, first let's break things.
Now we're going to my VideFileEdit.js component. If we add the following line to this component, everything breaks:
import { showAddCategoryModal } from 'store/filterInputModal/filterInputModal.actions';
So, how does it break?
The import of the showAddCategoryModal function in filterInputModal.reducer.js now is undefined.
Because the reducer is using the functions as the keys to handle actions, the reducer is no longer able to handle the action properly and update the state.
It gets weirder though. Here are some of the weird behaviors I'm seeing.
If I import this action into any other component, everything is fine. The import in the reducer is unchanged.
The import of the function in both ManageVideoFilters.js and VideoFileEdit.js is fine.
So, what can I try next? This is really strange and doesn't make any sense to me. I've never seen this before.
As the commenter said, the problem was recursive imports. My filterInputModal.reducer.js exported some constants, which were imported into my filterInputModal.actions.js. The actions from filterInputModal.actions.js were then imported into filterInputModal.reducer.js. Hence the recursive import.
I moved the constants into a new file, filterInputModal.constants.js, and viola, problem solved.

React Apollo GraphQL, many queries, but one component to be reused

I have this situation:
TeamComponent.js:
....more code
<WorkItemComponent workType="Beautiful">
<WorkItemComponent workType="VeryBad">
....more code
WorkItemComponent.js:
import React, { Component } from "react";
import { graphql } from "react-apollo";
import { compose, withHandlers } from "recompose";
import MY_BEAUTIFUL_WORKTYPE_QUERY from "./MY_BEAUTIFUL_WORKTYPE_QUERY";
import MY_VERYBAD_WORKTYPE_QUERY from "./MY_VERYBAD_WORKTYPE_QUERY";
import AmazingComponent from "./AmazingComponent";
class WorkItemComponent extends Component {
<AmazingComponent/>
}
export default compose(
graphql(MY_BEAUTIFUL_WORKTYPE_QUERY), // <-- here I need to change this query
choosing from [MY_BEAUTIFUL_WORKTYPE_QUERY, MY_VERYBAD_WORKTYPE_QUERY] based on "workType" prop in parent component "TeamComponent".
withHandlers({
...
})
)(WorkItemComponent);
I need to change the query "MY_BEAUTIFUL_WORKTYPE_QUERY" choosing MY_BEAUTIFUL_WORKTYPE_QUERY or MY_VERYBAD_WORKTYPE_QUERY
based on "workType" prop in parent component "TeamComponent".
But how?!
Maybe I have to rethink everything?
Where am I wrong?
I think there are two easy approaches you could take here:
1) Execute both queries ... and choose which results set you want inside WorkItemComponent. Obviously this is slightly wasteful as you'll be running one query you don't need.
2) Export two different components. Wrap them in a 3rd component that does the choosing. Example code:
const handlers = withHandlers({
...
});
const ComponentOne = compose(
graphql(MY_BEAUTIFUL_WORKTYPE_QUERY),
handlers
)(WorkItemComponent);
const ComponentOne = compose(
graphql(MY_VERYBAD_WORKTYPE_QUERY),
handlers
)(WorkItemComponent);
const Switcher = ({workType}) => workType === "something" ? <ComponentOne/> : <ComponentTwo/>;
export default Switcher;
So, compose up two different components, and switch between them at render time. Functional components and recompose make this rather simple and elegant!

Shall we use multiple combine reducers?

I have three reducers
Home
Listing
Detail
I have one combine reducer for above three. But I want to divide project structure into multiple reducers. Is it a good practice to have multiple combine reducer in the project?
Yes, you can have multiple combineReducers in your App, You can at the top level combine reducers Home , Listing and Detail and even split each individual reducers into multiple and combine them into one.
According to the Redux docs:
You may call combineReducers at any level of the reducer hierarchy.
It doesn't have to happen at the top. In fact you may use it again to
split the child reducers that get too complicated into independent
grandchildren, and so on.
Some more description about combineReducer:
As your app grows more complex, you'll want to split your reducing
function into separate functions, each managing independent parts of
the state.
The combineReducers helper function turns an object whose values are
different reducing functions into a single reducing function you can
pass to createStore.
The resulting reducer calls every child reducer, and gathers their
results into a single state object. The shape of the state object
matches the keys of the passed reducers.
Yes, you can combine multi reducer and use it as one store,
Firstly, you should import all reducers in a new file. then, combine all reducers with combine-reducer technics,like this:
import { combineReducers } from 'redux';
import {Home,Listing,Detail} from '*/**';
const rootReducer = combineReducers({
Home,
Listing,
Detail
})
export default rootReducer
Secondly, import the new file in Store file and create your store with combined-reducer
import { createStore } from 'redux'
import rootReducer from '*/combinestores'
const store = createStore(rootReducer)
console.log(store.getState())optinal
export default store
In the end, in your container you can use your store like this :
(but before it, you should be sure send store as props to Containers)
const mapStateToProps = state => {
return{
*name of your var* : state.Home.*,
*name of your var* : state.Detail.* /*then you can use this var as props anywhere you need*/
}
}
const mapDispatchToProps = dispatch => {
return{
*name of your var* : () => dispatch ( *name of your func*()),
*name of your var* : () => dispatch ( *name of your func*())
}
}
export default connect(mapStateToProps,mapDispatchToProps)(name of func);

Is it possible to use Recompose to make an app-wide store similar to Redux?

I want to make a simple application as a proof of concept for react components, and I was wondering if there was a way to implement recompose in such a way that it creates a store accesible from anywhere. This is what I have tried:
import App from './app-container'
import { withContext } from 'recompose'
import React, {
Component,
PropTypes,
} from 'react'
// Sets up application store.
const provide = store => withContext(
{ store: PropTypes.object },
() => ({ store })
)
var appStore = {
toast: null,
test: "hi"
}
const AppWithContext = provide(appStore)(App)
export default AppWithContext
Your code just put the store into the context, which is indeed something similar to react-redux/Provider, but you also need to implement something like react-redux/connect, which is more complicated, not recompose good at.

What are drawbacks of using a single mapDispatchToProps?

I try to follow the rules of the Redux documentation about presentational/container components and I connect my containers using react-redux connect(). I use mapDispatchToProps and bindActionCreators to inject the required actions into a props call actions. I never use the second parameter ownProps.
As my app became more and more complex, I end up with a lot of mapDispatchToProps() (one for each container) that are almost identical; they bind all actions from almost all action creators.
So I was wondering: what will be the drawbacks to have only one mapDispatchToProps function that bind all actions and use it in each containers?
Something like that:
import { bindActionCreators } from 'redux'
import * as EventsActionCreators from '../../actions/EventsActionCreators'
import * as TagsActionCreators from '../../actions/TagsActionCreators'
import * as UsersActionCreators from '../../actions/UsersActionCreators'
export default function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(
{
...EventsActionCreators,
...TagsActionCreators,
...UsersActionCreators,
},
dispatch
),
}
}
If your application is simple enough to warrant this, then I would say go for it. The downsides I see, if any, have to do with your connects not being explicit about what actions will be available. To know whats available, you have to go check the definition of mapDispatchToProps.
That said, why even have this as a function? mapDispatchToProps can receive an object, so in your mapDispatchToProps.js, this would suffice:
import * as EventsActionCreators from '../../actions/EventsActionCreators'
import * as TagsActionCreators from '../../actions/TagsActionCreators'
import * as UsersActionCreators from '../../actions/UsersActionCreators'
export default {
...EventsActionCreators,
...TagsActionCreators,
...UsersActionCreators,
}
then
import mapDispatchToProps from './mapDispatchToProps';
import SomeComponent from './SomeComponent';
export ConnectedComponent = connect(null, mapDispatchToProps)(SomeComponent);

Resources