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);
Related
I'm using the Redux-tool kit to set it up. We are now using #testing-library/react to set up testing-related settings.
I got a question while looking at the official document.
// test-utils.js
import React from 'react'
import { render as rtlRender } from '#testing-library/react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
// Import your own reducer
import reducer from '../reducer'
function render(
ui,
{
initialState,
store = createStore(reducer, initialState),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
What function does this part have in the code part above?
// re-export everything
export * from '#testing-library/react'
// override render method
export { render }
I don't know this library, but export * from '#testing-library/react' just means that anything you can import from #testing-library/react, you can now import directly from this file, test-utils.js.
I guess that they found it convenient to have a way to access just the react testing modules in one place, with the render method overwritten with their own custom version defined above.
They are basically creating their own aliased copy of the React Testing Library package where everything is the same except for the render function. This setup is explained in more detail in the testing library docs section Setup: Custom Render.
The custom render function takes the same arguments as the original render function from #testing-library/react so that they can be used interchangeably (though it adds support for extra properties initialState and store in the options object). Internally, the custom render function calls on the library's render function, which they import with an aliased name rtlRender, but it sets a default property for the wrapper option so that components will be rendered inside of a redux Provider component.
Now to the confusing exports. export * from '#testing-library/react' takes all of the exports from the testing library and re-exports them. export { render } overrides the previously exported render function with the custom one, so it needs to come after the export *.
As for why they would create the function in one place and then export it later rather than just doing export function, I think that's just a matter of code style preference. This seems to work fine, as far as I can tell:
import { render as rtlRender } from "#testing-library/react";
// re-export everything
export * from "#testing-library/react";
// override render method
export function render(somethingCustom, ui, { ...renderOptions } = {}) {
return rtlRender(ui, { ...renderOptions });
}
Suppose I have a react pure function named SignIn() in One.js :
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {GoogleSignin, statusCodes} from '#react-native-community/google-signin';
import {getToken, saveToken} from '../actions/token';
const SignIn = async ({token, getToken, saveToken}) => {
const savedToken = await getToken();
console.log(token.loading, savedToken);
SignIn.propTypes = {
token: PropTypes.string.isRequired,
getToken: PropTypes.func.isRequired,
saveToken: PropTypes.func.isRequired,
};
};
const mapStateToProps = state => {
console.log('state : ', state);
return {
token: state.token,
};
};
export default connect(mapStateToProps, {saveToken, getToken})(SignIn);
I want to use this SignIn() function in another Two.js react file so that getToken() which is a redux function and other functions will be called inside file One.js and then i can use those functions inside file Two.js but the problem is because of redux connect, i am not able to export and use them. How can i import and use this kind of function inside Two.js file ?
connect function can only be implemented with react components that renders actual jsx, and for it to work you need to return jsx elements or null and call it like this <SignIn />.. in my opinion if you want to implement some logic with the use of redux, you can make a custom hook, implement useSelector or useDispatch inside it, and either return the data you want or just do your effect inside it then return nothing.
hope this helps.
here's an example from react-redux docs https://react-redux.js.org/api/hooks#usedispatch
What Worked for me was declaring the functions that i want to export inside the redux actions, so i created a new action for any function that i want to use. Make sure to make use of loading state of initial state otherwise functions can be called infinite times because of re-rendering.
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.
i have a file inside action directory which is root.js.
Root.js will compile all the others action inside, and i bind it with bindActionCreators
export const all = (store) => {
AUTH: bindActionCreators(AUTH.actions, store.dispatch),
....: .....
}
From what i learned, bindActionCreators is for the purpose of auto dispatching the action.
If that is the case, then how do i access it from smart component?
I see things like dispatch(action). But since now i already bind it globally, i dont think that i would need to specify dispatch anymore. How do i do it, or is there any part that i misunderstood?
Thank you
bindActionCreators - will create an object of actions each wrapped with the dispatch.
It's good for passing them as refs to non-connected components that should not know anything about redux or dispatch.
Quote from the DOCS:
The only use case for bindActionCreators is when you want to pass some
action creators down to a component that isn't aware of Redux, and you
don't want to pass dispatch or the Redux store to it.
So if you want that connected component to pass action creators to a dumb component, you can set an object via bindActionCreators and pass it with props to the dumb component.
Example:
const myActionCreators = bindActionCreators(Auth.myActions, dispatch)
<DumbComponent {...myActionCreators} />
The recommended approach is to have each connected component file import the action creators it needs, and use the "object shorthand" supported by connect:
import {addTodo, toggleTodo} from "./todoActions";
const actions = {addTodo, toggleTodo};
export default connect(null, actions)(TodoList);
// each TodoList instance now has this.props.addTodo and
// this.props.toggleTodo, which will dispatch actions when called.
I am using React and Redux.
I have a banner component where banners are shown in a carousel.
I have BannerContainer.js which is connected to redux and Banner.js which is component.
On click of a carousel, I need to do 2 things
Redirect user to another url'
Fire a GTM event
None of the above actions modify the state. Should I pass an onClick handler via mapDispatchToProps from my container?
What should be the correct way?
If you don't need to dispatch any action, I don't think you need to pass the function in mapDispatchToProps. A local function inside BannerContainer should be enough for you requirements. BTW, what is GTM event? I don't know the abbr.
I would still pass that in connect, because that's business logic that shouldn't be hidden down your component tree. Otherwise if you need to change the GTM stuff or change routes you will have to find the component.
Create a thunk action creator that performs your side-effects and map it using mapDispatchToProps. If your component is not a top-level Route component, you can use withRouter to get access to the routing context. You can even compose both decorators or enhancers into own in your container:
import { connect, compose } from 'redux'
import { withRouter } from 'react-router'
import YourComponent from '../components/YourComponent'
// your dispatch
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onBannerClick(e){
//do your GTM stuff
//redirect the user!
ownProps.history.push('/gohere')
}
}
}
// combine your enhancers, order is significant if you want
// to have withRouter() props in your connect methods
const enhance = compose(
withRouter(),
connect(mapStateToProps, mapDispatchToProps),
)
export default enhance(YourComponent)