Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can react-redux connect() -ed containers implement lifecyle methods like componentDidMount?

I've come across a repeated pattern in my react-redux site: A component displays data from a web api, and it should be populated on load, automatically, without any user interaction.

I want to initiate the async fetch from a container component, but as far as I can tell the only way to do it is from a lifecycle event in a display component. This seems to make it impossible to put all the logic in the container and only use dumb stateless functional components for display.

This means I can't use a stateless functional component for any component that needs async data. That doesn't seem right.

It seems like the "right" way to do this would be to somehow initiate async calls from the container. Then when the call returned, the state would be updated and the container would get the new state and would in turn pass those to its stateless component via mapStateToProps().

Making async calls in mapStateToProps and mapDispatchToProps (I mean actually calling the async function, as opposed to returning it as a property) doesn't make sense.

So what I've ended up doing is putting the async call(s) in a refreshData() function exposed by mapDispatchToProps(), then calling it from two or more of the React lifecycle methods: componentDidMount and componentWillReceiveProps.

Is there a clean way to update the redux store state without putting lifecycle method calls in every component that needs async data?

Should I be making these calls higher up the component heierarchy (thereby reducing the scope of this issue, since only "top-level" components would need to listen to lifecycle events)?

Edit:

Just so there's no confusion what I mean by a connect()ed container component, here's a very simple example:

import React from 'react'; import { connect } from 'react-redux'; import {action} from './actions.js';  import MyDumbComponent from './myDumbComponent.jsx';  function mapStateToProps(state) {   return { something: state.xxxreducer.something  }; }  function mapDispatchToProps(dispatch) {   return {         doAction: ()=>{dispatch(action())}   }; }  const MyDumbComponentContainer = connect(   mapStateToProps,   mapDispatchToProps )(MyDumbComponent);  // Uh... how can I hook into to componentDidMount()? This isn't  // a normal React class.  export default MyDumbComponentContainer; 
like image 249
stone Avatar asked Jul 20 '16 20:07

stone


People also ask

What is connect method in React redux?

The connect() function connects a React component to a Redux store. It provides its connected component with the pieces of the data it needs from the store, and the functions it can use to dispatch actions to the store.

What are the three main methods of component lifecycle in React?

Lifecycle of Components The three phases are: Mounting, Updating, and Unmounting.

What is the difference between useEffect and componentDidMount?

Hooks and useEffect() both run after the component is mounted. The difference is that hooks are also run after the DOM content has been painted. So, if the state is updated synchronously within an effect method users will see a flicker as the first frame is replaced with the second frame.

What do you usually do in componentDidMount () and componentDidUpdate ()?

componentDidMount() will be rendered immediately after a component is mounted. This method will render only once and all the initialization that requires DOM nodes should go here. Setting state in this method will trigger a re-rendering. componentDidUpdate() is invoked immediately every time the updating occurs.


2 Answers

Jamie Dixon has written a package to do this!

https://github.com/JamieDixon/react-lifecycle-component

Usage would look like this:

const mapDispatchToProps = {     componentDidMount: getAllTehDatas }  ...  export default connectWithLifecycle(mapStateToProps, mapDispatchToProps)(WrappedComponent)  
like image 123
stone Avatar answered Oct 18 '22 03:10

stone


edit With hooks you are now able to implement lifecycle callbacks in a stateless functional component. While this may not directly address all of the points in the question, it may also get around some of the reason for wanting to do what was originally proposed.


edit to original answer After the discussion in comments and thinking about it more, this answer is more exploratory and can serve as a piece of the conversation. But I don't think it's the right answer.

original answer

On the Redux site there's an example that shows you don't have to do both mapStateToProps and mapDispatchToProps. You can just leverage connect's awesomeness for the props, and use a class and implement the lifecycle methods on the dumb component.

In the example, the connect call is even in the same file and the dumb component isn't even exported, so to the user of the component it's looks the same.

I can understand not wanting to issue async calls from the display component. I think there's a distinction between issuing the async calls from there and dispatching an action which, with thunks, moves the issuing of the async calls up into the actions (even more decoupled from the React code).

As an example, here's a splash screen component where I'd like to do some async action (like asset preloading) when the display component mounts:

SplashContainer.js

import { connect } from 'react-redux' import Splash from '../components/Splash' import * as actions from '../actions'  const mapStateToProps = (state) => {   return {     // whatever you need here   } }  const mapDispatchToProps = (dispatch) => {   return {     onMount: () => dispatch(actions.splashMount())   } }  const SceneSplash = connect(   mapStateToProps,   mapDispatchToProps )(Splash)  export default SceneSplash 

Splash.js

import React from 'react'  class Splash extends React.Component {   render() {     return (       <div className="scene splash">       <span className="fa fa-gear fa-spin"></span>       </div>     )   }    componentDidMount() {     const { onMount } = this.props     onMount()   } }  export default Splash 

You can see the the dispatch happens in the connected container, and you can imagine in the actions.splashMount() call we issue an async http request or do other async things via thunks or promises.

edit to clarify

Allow me to try to defend the approach. I re-read the question and am not 100% sure I'm addressing the main thing it's after, but bear with me. If I am still not quite on track, I have a modified approach below that may be closer to the mark.

"it should be populated on load" - the example above accomplishes this

"I want to initiate the async fetch from a container" - in the example it's not initiated from the display component or the container, but from an async action

"This seems to make it impossible to put all the logic in the container" - I think you can still put any additional logic needed in the container. As noted, the data loading code isn't in the display component (or the container) but in the async action creator.

"This means I can't use a stateless functional component for any component that needs async data." - in the above example the display component is stateless and functional. The only link is the lifecycle method invoking a callback. It need not know or care what that callback does. It is not a case of the display component trying to be the owner of async data fetching - it's merely letting the code that does handle that know when a particular thing has happened.

So far I'm attempting to justify how the example given meets the question's requirements. That said, if what you're after is having a display component that contains absolutely no code related to the async data load even by indirect callbacks - that is, the only link it has is to consume that data via the props it's handed when that remote data comes down, then I would suggest something like this:

SplashContainer.js

import { connect } from 'react-redux' import Splash from '../components/Splash' import * as actions from '../actions'  const mapStateToProps = (state) => {   return {     // whatever you need here   } }  const mapDispatchToProps = (dispatch) => {   dispatch(actions.splashMount())   return {     // whatever else here may be needed   } }  const SceneSplash = connect(   mapStateToProps,   mapDispatchToProps )(Splash)  export default SceneSplash 

Splash.js

import React from 'react'  class Splash extends React.Component {   // incorporate any this.props references here as desired   render() {     return (       <div className="scene splash">       <span className="fa fa-gear fa-spin"></span>       </div>     )   } }  export default Splash 

By dispatching the action in mapDispatchToProps you are letting the code for that action reside entirely in the container. In fact, you are starting the async call as soon as the container is instantiated, rather than waiting for the connected display component to spin up and be mounted. However, if you cannot begin the async call until the componentDidMount() for the display component fires, I think you're inherently bound to have code like in my first example.

I haven't actually tested this second approach to see if react or redux will complain about it, but it should work. You have access to the dispatch method and should be able to call it without problems.

To be honest, this second example, while removing all code related to the async action from the display component does kind of strike me as a bit funny since we're doing non-mapping-of-dispatch-to-props things in the eponymous function. And containers don't actually have a componentDidMount to run it in otherwise. So I'm a bit squirmy with it and would lean toward the first approach. It's not clean in the "feels right" sense, but it is in the "simple 1-liner" sense.

like image 27
jinglesthula Avatar answered Oct 18 '22 05:10

jinglesthula