Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dispatch Redux action from stateless component when route is loaded?

Goal: when loading a react-router route, dispatch a Redux action requesting asynchronic Saga worker to fetch data for the underlying stateless component of that route.

Problem: stateless components are mere functions and don't have lifecycle methods, such as componentDidMount, so I can't(?) dispatch Redux action from inside the function.

My question is partly related to Converting stateful React component to stateless functional component: How to implement "componentDidMount" kind of functionality? , but my goal is to merely dispatch a single Redux action requesting data to be populated to the store asynchronously (I use Saga, but I think that's irrelevant to the problem, as my goal is to merely dispatch an ordinary Redux action), after which the stateless component would re-render due to the changed data prop.

I am thinking of two approaches: either use some feature of react-router, or Redux's connect method. Is there a so-called "React-way" to accomplish my goal?

EDIT: the only solution I have come up with so far, is dispatching the action inside mapDispatchToProps, this way:

const mapStateToProps = (state, ownProps) => ({     data: state.myReducer.data // data rendered by the stateless component });  const mapDispatchToProps = (dispatch) => {     // catched by a Saga watcher, and further delivered to a Saga worker that asynchronically fetches data to the store     dispatch({ type: myActionTypes.DATA_GET_REQUEST });     return {}; };  export default connect(mapStateToProps, mapDispatchToProps)(MyStatelessComponent); 

However, this seems somehow dirty and not the correct way.

like image 881
Kitanotori Avatar asked Sep 29 '16 04:09

Kitanotori


People also ask

How do you call Redux action outside component?

To trigger a Redux action from outside a component with React, we call store. dispatch from anywhere in our project. import { store } from "/path/to/createdStore"; function testAction(text) { return { type: "TEST_ACTION", text, }; } store.

Is it possible to dispatch an action without using mapDispatchToProps?

Without mapDispatchToPropsNotice that the component receives a dispatch prop, which comes from connect() , and then has to use it directly to trigger the actions.

How can I call Redux action from functional component?

There are two ways to dispatch actions from functional components: Using useDispatch hook provided by react-redux . If you want to use this hook, then you need to import it from the react-redux package. This hook returns a function that can be used to dispatch actions.


2 Answers

I don't know why you absolutly want a stateless component, while a stateful component with componentDidMount would do the job in a simple way.

Dispatching actions in mapDispatchToProps is very dangerous and may lead to dispatching not only on mount but whenever ownProps or store props changes. Side effects are not expected to be done in this method that should remains pure.

One easy way to keep your component stateless is to wrap it into an HOC (Higher-Order Component) that you could easily create:

MyStatelessComponent = withLifecycleDispatch(dispatch => ({    componentDidMount: function() { dispatch({ type: myActionTypes.DATA_GET_REQUEST })}; }))(MyStatelessComponent) 

Note that if you use Redux connect after this HOC, you can easily access dispatch from props directly as if you don't use mapDispatchToProps, dispatch is injected.

You can then do something very simple like:

let MyStatelessComponent = ...  MyStatelessComponent = withLifecycle({    componentDidMount: () => this.props.dispatch({ type: myActionTypes.DATA_GET_REQUEST }); })(MyStatelessComponent)  export default connect(state => ({    date: state.myReducer.data }))(MyStatelessComponent); 

HOC definition:

import { createClass } from 'react';  const withLifeCycle = (spec) => (BaseComponent) => {   return createClass({     ...spec,     render() {       return BaseComponent();     }   }) } 

Here is a simple implementation of what you could do:

const onMount = (onMountFn) => (Component) => React.createClass({    componentDidMount() {      onMountFn(this.props);    },    render() {        return <Component {...this.props} />    }   });  let Hello = (props) => (    <div>Hello {props.name}</div> )  Hello = onMount((mountProps) => {    alert("mounting, and props are accessible: name=" + mountProps.name) })(Hello) 

If you use connect around Hello component, they you can inject dispatch as props and use it instead of an alert message.

JsFiddle

like image 143
Sebastien Lorber Avatar answered Sep 27 '22 23:09

Sebastien Lorber


Now days you can use the useEffect hook as such:

import React, { useEffect } from 'react'; const MyStatelessComponent: React.FC = (props) => {    useEffect(() => {       props.dispatchSomeAction();    });    return ... } 

This is the equivalent for the componentDidMount/componentWillMount life cycle methods of functional/stateless components.

For further reading on hooks: https://reactjs.org/docs/hooks-intro.html

like image 31
Itamar Avatar answered Sep 27 '22 21:09

Itamar