Dispatching an action from a Redux container without extending React.Component - reactjs

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.

Related

React.js / Redux: Functional Components, legacy state/props and do I have to connect every component that uses the store?

some questions about React.js and Redux:
Can functional components also take advantage of the store and the states saved therein? e.g maybe in combination with React hooks like useEffect()?
In general, I can combine multiple reducers to one rootReducer and createStore(rootReducer) with it, and then pass it to a Provider Component that wraps my Component with it, this way, the store should be globally available in my whole app, correct?
For every component that want to use the store / states, do I always have to import the 2 methods mapStateToProps() and mapDispatchToProps() from react-redux for every Component and then connect them? Or can I also do this on some top-level component and make the usage of redux available in all my components globally, like in question 2) with the store provider?
last question: Can I still use the this.state property in my Components or use them in parallel as an addition (e.g for this Component isolated states) and then get the props from this state as usual with this.state.someState or is this not possible anymore when I already use Redux? And in the same way, can I still use / pass props to my components and read them from my Components as well, or is everything managed by state now only? (Or has the passing of props to my children nothing to do with Redux)?
1) Yes functional components can take advantage of the store. Its arguably much cleaner to read since props can be destructured right away.
const MyComponent = ({ auth }) => {
const [display, setDisplay] = useState(false)
useEffect(() => {
if(auth.user){
setDisplay(true)
}
}, [auth.user])
return(
<div>
{ display ? "Content": "Please sign in" }
</div>
)
}
const mapStateToProps = (state) => {
return{
auth: state.auth
}
}
export default connect(mapStateToProps)(MyComponent)
2) That is correct. You can also use combineReducers() which in some ways is cleaner to read.
import { createStore, combineReducers } from "redux"
import authReducer from "./reducers/authReducer"
import postReducer from "./reducers/postReducer"
const store = createStore(combineReducers({
auth: authReducer,
post: postReducer
}))
export default store
Then import store, wrap your App.js in a Provider and give it a prop of that store.
3) Generally, if you want your component to have direct access to the store it is a recognized pattern to use connect() in each one. Whether you decide to use mapStateToProps() or mapDispatchToProps() is entirely dependent on what that component needs to do. It does not required that you use both, you can just define one or the other in the connect().
import React, { useState } from "react"
import { addPost } from "/actions/postActions"
import { connect } from "react-redux"
const Form = ({ addPost }) => {
const [text, setText] = useState("")
const handleSubmit = (e) => {
e.preventDefault()
addPost(text)
}
return(
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)}/>
</form>
)
}
const mapDispatchToProps = (dispatch) => {
return {
addPost: (text) => dispatch(addPost(text))
}
}
export default connect(null, mapDispatchToProps)(Form)
4) You might have noticed by now that in the context of components, redux-state is stored as props. They are entirely different and isolated streams of data. So state remains untouched and controlled by the component itself. You can still freely use methods like this.state.dog even when your component is connected to the store. This is the isolation between component-state and redux-state.
import React, { useState } from "react"
import { connect } from "react-redux"
class MyDogs extends React.Component{
state = {
dog: "Tucker"
}
render(){
return(
<div>
Component State Value: {this.state.dog} //Tucker
Redux State Value: {this.props.dog} //Buddy
</div>
)
}
const mapStateToProps = (state) => {
return {
dog: state.dog
}
}
export default connect(mapStateToProps)(MyDogs)

React Native HOC's and duplicate lifecycle method functions

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

Subscribe the state - Redux

I'm trying to display some data which will always be updated but when I add some new data to store, the new data is not seen on the screen as I didn't know about subscribe to store method. But I don't know where to use it and how to use it. I couldn't find any suitable example for my project.
First possibility to use as I did search on it (use it like mapStateToProps);
const mapStateToProps = (state) => {
return {
dashboardsList: state.header.dashboardsList,
templatesList: state.header.templatesList
}
}
DashboardDropdown.propTypes = {
dashboardsList: PropTypes.array,
templatesList: PropTypes.array
};
export default connect(mapStateToProps, null)(DashboardDropdown);
Let's say I want to subscribe to state.header.templatesList, how can I write it?
Or should I subscribe the state.header.templatesList in the app-store.js?
This is my store class;
const RootReducer = (state = {}, action) => {
return {
[HeaderModule.constants.NAME]: HeaderModule.reducer(
state[HeaderModule.constants.NAME],
action
),
[AuthModule.constants.NAME]: AuthModule.reducer(
state[AuthModule.constants.NAME],
action
),
[DashboardViewModule.constants.NAME]: DashboardViewModule.reducer(
state[DashboardViewModule.constants.NAME],
action,
),
[TemplateViewModule.constants.NAME]: TemplateViewModule.reducer(
state[TemplateViewModule.constants.NAME],
action,
),
[WidgetContainerModule.constants.NAME]: WidgetContainerModule.reducer(
state[WidgetContainerModule.constants.NAME],
action
)
}
}
const Store = createStore(RootReducer, applyMiddleware(thunk, logger()));
export default Store;
If I should subsribe it here, how can I again write it?
Thanks a lot!
I think I can help you with this - you'll have to add some code to your components that will map the Redux state to that component's props.
First, install react-redux - $ npm install --save react-redux, if you haven't yet.
Something like:
MyComponent.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
const mapStateToProps = state => ({
state
});
class MyComponent extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
console.log(this.props.state)
}
render(){
return(
<div>Hello</div>
)
}
}
export default connect(mapStateToProps, undefined)(MyComponent);
When this loads up, you'll see that console.log(this.props.state) will refer to the Redux state, because we have mapped the state (as in the Redux state) to the props of the component. When Redux updates, that should 'subscribe' the component to those changes.
If DashboardDropdown (the default export of that file) is rendered on the DOM as of now, then you are now subscribed to the store. Whenever the global state (store) changes, every mapStateToProps in every ConnectedComponent will be invoked giving the component (DashboardDropdown) the new props.

React design pattern for fetching items

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.

Wondering where to house api call logic. inside the Container or Component?

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

Resources