How to trigger callback function in Redux? - reactjs

React and Redux experts.
I am new to React and Redux. My question is related to trigger callback (function) invocation when a Redux state is changed. I am stuck into this implementation. In my understanding, the presenter/view is updated via the props. Let me illustrate more in the following example.
<ParentComponent>
<Child1Component/>
<Child2Component/>
</ParentComponent>
class ParentComponent extends Component {
onChild1Click() {
this.props.dispatch(setTool(Tools.CHILD1TOOL))
}
render() {
return (
<div>
<Child1Component onChild1Click={this.onChild1Click.bind(this)}/>
<Child2Component/>
</div>
)
}
}
const mapStateToProps = state => {
return {state}
}
export default connect(
mapStateToProps
)(ParentComponent)
class Child1Component extends Component {
componentDidUpdate() {
// Question: How to get the Redux state here?
}
render() {
return (
<button onClick={this.props.onPencilClick}>Pencil</button>
)
}
}
Suppose a button is present in the Child1Component and a onclick is attached to such button. In my understanding of Redux, an action should be attached to this onclick and it should be dispatched. Such state will be modified in the ParentComponent and trigger props update. Afterwards, the UI/Presenter of Child1Component will be updated via props instead of any callback of Child1Component.
Is it possible to trigger a callback in Child1Component when a state is altered? The reason I need to make such implementation is that a 3rd party library is adopted. It requires to trigger callback. Actually, the onclick can trigger the function (callback) directly. However, the state cannot be maintained.
Could any expert advise it, please? Thanks a million.
P.

As I understand, this is not directly related to redux. You can use the react life cycle methods for this purpose. In your case, I think you need the componentDidUpdate or componentWillUpdate methods.
You can read more about life cycle methods here,
https://reactjs.org/docs/react-component.html
Explanation
First, make sure that you have connected the components to the Redux store using the react-redux bindings. Then, if you have correctly defined the mapStateToProps function, your child component will update whenever the state changes. Thus, whenever the component is updated, the componentWillUpdate and componentDidUpdate methods will be called.
Example in ES6 style
First, we'll bind the full redux state to the child component. Note: Generally you would not bind the full state, but only a branch of it.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import ChildComponent from './ChildComponent';
function mapStateToProps(state) {
return {
// this will bind the redux state to the props of the child component
reduxState: state
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
// some action creators
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChildComponent);
Then we can access the redux state from the child component.
class ChildComponent extends React.Component {
componentWillMount(nextProps, nextState) {
// Do something here
console.log(this.props.reduxState)
// this.props.reduxState is accessible from anywhere in the component
}
render() {
return <div>{/*Some jsx here*/}</div>
}
}
I strongly recommend you to read about redux usage with react section from redux docs and about smart-dumb component separation

First off, thank you for the replies. I came up the solution eventually. Here it is.
// actions.js
export const SET_TOOL = 'SET_TOOL'
export const Tools = {
CHILD1TOOL: 'child1tool',
DEFAULT: 'default'
}
export function setTool(tool) {
return {
type: SET_TOOL,
tool
}
}
// reducers.js
import { combineReducers } from 'redux'
import { SET_TOOL, Tools } from './actions'
const { DEFAULT } = Tools
function currentTool(state = DEFAULT, action) {
switch(action.type) {
case SET_TOOL:
return action.tool
default:
return state
}
}
const myApp = combineReducers({
currentTool
})
export default myApp
// ParentComponent.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Tools, setTool } from './actions'
import Child1Component from './Child1Component.jsx'
class ParentComponent extends Component {
render() {
return (
<div>
<Child1Component onChild1Click={this.props.onChild1Click'}/>
</div>
)
}
}
const mapStatesToProps = state => {
return {state}
}
const mapDispatchToProps = dispatch => {
return {
onChild1Click: () => {
dispatch(setTool(Tools.CHIDL1TOOL))
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ParentComponent)
// Child1Component.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Tools } from './actions'
class Child1Component extends Component {
componentDidUpdate() {
if (this.props.state.currentTool === Tools.CHILD1TOOL) {
this.callbackHandleClick()
}
}
render() {
return <button onClick={this.props.onChild1Click}>Child 1 Button</button>
}
callbackHandleClick() {
/* callback implementation */
}
}
const mapStateToProps = state => {
return {state}
}
export default connect(
mapStateToProps
)(Child1Component)

Related

React, Redux - pass function from component A to other components

import React from "react";
import OtherComponent from "./OtherComponent";
class Main extends React.Component {
constructor(props) {
super(props);
this.runMyFunction = this.runMyFunction.bind(this);
this.myFunction = this.myFunction.bind(this);
}
runMyFunction(event) {
event.preventDefault();
this.myFunction();
}
myFunction() {
return console.log("I was executed in Main.js");
}
render() {
return (
<div>
<OtherComponent runMyFunction={this.runMyFunction} />
</div>
);
}
}
export default Main;
import React from "react";
class OtherComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.runMyFunction();
}
render() {
return (
<div>
<button onClick={this.handleClick} />Click me to execute function from Main </button>
</div>
);
}
}
export default OtherComponent;
I'm new in redux and don't know how to pass and run that function in other component. It was easy not using redux, just pass as props like in example above.
I have folder with actions, components, containers and reducers.
Now I have Main.js where I have
import React from "react";
const Main = ({data, getData}) => {
const myFunction = () => {
return "ok";
};
return (
<div>
<p>This is main component</p>
</div>
);
};
export default Main;
In MainContainer.js I got:
import Main from "../../components/Main/Main";
import { connect } from "react-redux";
import {
getData
} from "../../actions";
function mapStateToProps(state) {
return {
data: state.main.data
};
}
const mapDispatchToProps = (dispatch) => {
return {
getData: () => dispatch(getData())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Main);
So how I can run function myFunction() in OtherComponent.js:
import React from "react";
const OtherComponent = ({executeFunctionInMainComponent}) => {
return (
<div>
<button onClick={executeFunctionInMainComponent}>run action</button>
</div>
);
};
export default OtherComponent;
I need to just run, not pass whole function, just execute myFunction in Main.js but action to run this function will came from OtherComponent.
So first i have to mention that i believe that you have a misconception of redux. This isn't to allow for functions created in components to be reused in different locations. This is to move that logic to a reducer outside of your function which would allow it to be used wherever you wired it with {connect} from react-redux. So you will need a couple of files (for clarity). First you're going to need an action file which we'll name myReturnOkAction.
export const myReturnOkAction = (/*optional payload*/) => {
return {
type: 'PRINT_OK',
}
}
Redux Actions
This is what you're going to call in your mapDispatchToProps function where you're going to trigger this event. You're going to have to import it into your OtherComponent so import {myReturnOkAction} from "/*wherever the files exists*/" and to include it in your mapDispatchToProps as okFunction: () => dispatch(myReturnOkAction())
Once you have your action your connect Higher Order Component (HOC) wrapping your main component is going to need a Reducer to modify your current store state as well as do any actions.
export const myReturnOkReducer = (state, action) => {
if(action.type === 'PRINT_OK'){
/*This is where you update your global state*/
/*i.e. return {...store, valueWanted: ok}*/
}else{
return state
}
}
Redux Reducers
So the way that this is going to move is that you're function, somewhere is going to call the action. Once the action is called its going to trigger the reducer and make any changes to the store which you need. Once the reducer has updated the store with new values its then going to update any components which are connected to it through the connect HOC which will cause them to re-render with new information.
Also my favorite image to describe how redux works.
I hope this helps.
I found an answer:
I still can pass as props in redux but I can't do this in this way: OtherComponent = ({executeFunctionInMainComponent}) => {}. I need to do in this way: OtherComponent = (props) => {} and then inside that component I have an access via props.executeFunctionInMainComponent

componentWillReceiveProps is not called on redux props change

My component is straight forward:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as instanceActions from '../../../store/instances/instancesActions';
class InstanceDetailsPage extends Component {
componentWillReceiveProps(nextProps) {
console.log('will receive props');
if (nextProps.id !== this.props.id){
this.updateInstanceDetails();
}
}
updateInstanceDetails = () => {
this.props.actions.loadInstance(this.props.instance);
};
render() {
return (
<h1>Instance - {this.props.instance.name}</h1>
);
}
}
function getInstanceById(instances, instanceId) {
const instance = instances.filter(instance => instance.id === instanceId);
if (instance.length) return instance[0];
return null;
}
function mapStateToProps(state, ownProps) {
const instanceId = ownProps.match.params.id;
let instance = {id: '', name: ''};
if (instanceId && state.instances.length > 0) {
instance = getInstanceById(state.instances, instanceId) || instance;
}
return {
instance,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(instanceActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(InstanceDetailsPage);
My reducer which I'm pretty sure I'm not mutating the state:
import * as types from '../actionTypes';
import initialState from '../initialState';
export default function instancesReducer(state = initialState.instances, action) {
switch (action.type){
case types.LOAD_INSTANCES_SUCCESS:
return action.instances.slice(); // I probably don't event need the .slice() here, but just to be sure.
default:
return state;
}
}
I know for sure that the state change which triggers props change because I logged this.props on the render method, and the props changed couple of times!
With that, the componentWillReceiveProps wasn't called even once.
What can cause that?
there are a few reasons why componentWillReceiveProps will not be called,
if its not receiving a new props object from the redux store
additionally these methods are not called if a component is mounted. So you may see your component update, but it is really just mounting and unmounting. to fix this, look into the parent rendering this component and check to see if it is rendering the component with a different key or if there is some sort of conditional render that may return false and cause react to unmount it.

Redux error: mapDispatchToProps throws error

I am new to react-redux and I am having some difficulty in understanding the syntax. I am pasting my sample code below... please help me understand if there are any syntactical errors.
SampleParent.js:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchNames, fetchDownloadLink } from '../../actions/actions'
import SampleChild from '../ui/SampleChild'
class SampleParent extends Component {
constructor(props) {
super(props) ;
}
componentDidMount() {
const { dispatch } = this.props
dispatch(fetchNames());
}
render() {
return(<div><ul id="myUL">{this.props.reports.map((report) => (
<li>
<SampleChild
key={report.id}
label={report.label}
uri={() => fetchDownloadLink("http://localhost:8080/sample"+this.props.uri+".pdf")}
/>
</li>))}</ul></div>)}
}
function mapStateToProps(state) {
const { reports } = state
return {
reports
}
}
const mapDispatchToProps = dispatch => {
return {
fetchDownloadLink(url) {
dispatch(
fetchDownloadLink(url)
)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ReportsApp)
SampleChild.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchDownloadLink } from '../../actions/actions'
class OpenReport extends Component {
constructor(props) {
super(props) ;
}
render(){
return(<div className="in_sample" id={this.props.label}>
{this.props.label}
<a href={this.props.uri}>
<img src="../images/pdf-file_128.png" height="25px" width="25px"></img></a><br></br></div>
)
}
}
module.exports = OpenReport;
Currently I am getting this error:
Uncaught TypeError: dispatch is not a function
at ReportsApp.componentDidMount (bundle.js:39883)
Basically what I need to do is get a url as a string from the 'fetchDownloadLink ' function and pass this string to my child component. Is there any other way to do that?
Please suggest...
Thanks in advance!
According to the Documentaion:
mapDispatchToProps returns an object that somehow uses dispatch to bind
action creators in your own way.
However in your case you are returning an object without keys. Change your function to
const mapDispatchToProps = dispatch => {
return {
fetchDownloadLink: (url) => dispatch(fetchDownloadLink(url))
}
}
}
MoreOver, using connect function you need to connect mapStateToProps and mapDispatchToProps function to the component in which you will be using the action creators which in your case is SampleParent
Also if you pass mapDispatchToProps as the second parameter to connect, then dispatch is not available as a prop to your component.
So change your code to the following
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchNames, fetchDownloadLink } from '../../actions/actions'
import SampleChild from '../ui/SampleChild'
class SampleParent extends Component {
constructor(props) {
super(props) ;
}
componentDidMount() {
const { dispatch } = this.props
this.props.fetchNames();
}
render() {
return(<div><ul id="myUL">{this.props.reports.map((report) => (
<li>
<SampleChild
key={report.id}
label={report.label}
uri={() => this.props.fetchDownloadLink("http://localhost:8080/sample"+this.props.uri+".pdf")}
/>
</li>))}</ul></div>)}
}
}
function mapStateToProps(state) {
const { reports } = state
return {
reports
}
}
const mapDispatchToProps = dispatch => {
return {
fetchDownloadLink: (url) => dispatch(fetchDownloadLink(url)),
fetchNames: () => dispatch(fetchNames)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SampleParent)
The problem is that you are passing your action creator as a prop uri to your child component, and then using that prop as an HREF tag.
You should instead pass it as onChildClick (example name) prop to your report item component, and call it on the onClick prop of the <a>.
<a onClick={ this.props.onChildClick }>xxxx</a>
The previous answer about the action creator mapping is fine, but you don't even need a function: if you use an object with functions as keys, they will be wrapped with dispatch for you.
const mapDispatchToProps = {
fetchDownloadLink
}
EDIT after your updated question
I see the problem now. You don't have dispatch as prop because you're using mapDispatchToProps to provide some action creators as props. It doesn't make sense to map dispatch to fetchDownloadLink and not doing it as well for fetchNames. Map both or neither, but you shouldn't mix and match.

React / Redux wait for store to update

I have a problem that a react component is rendering before the redux store has any data.
The problem is caused by the React component being rendered to the page before the existing angular app has dispatched the data to the store.
I cannot alter the order of the rendering or anything like that.
My simple React component is
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
// *** at this point I have the store in state prop
//but editorFlow array is not yet instanced, it's undefined
const tasks = this.props.state.editorFlow[0].flow.tasks
return (
<div>
Flow editor react component in main container
</div>
);
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)
So how can I hold off the rendering until editorFlow array has elements ?
You can use Conditional Rendering.
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
// *** at this point I have the store in state prop
//but editorFlow array is not yet instanced, it's undefined
const { editorFlow } = this.props.state;
let tasks;
if (typeof editorFlow === 'object' && editorFlow.length > 0) {
tasks = editorFlow[0].flow.tasks;
}
return (
{tasks &&
<div>
Flow editor react component in main container
</div>
}
);
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)
As far as I know, you can't.
the way redux works is that it first renders everything, then actions take place with some async stuff(such as loading data), then the store gets populated, and then redux updates the components with the new state(using mapStateToProps).
the lifecycle as I understand it is this :
render the component with the initial state tree that's provided when you create the store.
Do async actions, load data, extend/modify the redux state
Redux updates your components with the new state.
I don't think mapping the entire redux state to a single prop is a good idea, the component should really take what it needs from the global state.
Adding some sane defaults to your component can ensure that a "loading" spinner is displayed until the data is fetched.
In response to Cssko (I've upped your answer) (and thedude) thanks guys a working solution is
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
const { editorFlow } = this.props.state;
let tasks;
if (typeof editorFlow === 'object' && editorFlow.length > 0) {
tasks = editorFlow[0].flow.tasks;
}
if(tasks){
return (
<div>
Flow editor react component in main container
</div>
)
}
else{
return null;
}
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)

addEventListener react redux with mapped dispatch

I am currently trying to add an event listener to an application I'm making in react. I'm doing this by hooking into the componentDidMount API, which runs only once the component is rendered and not more than that. My problem is that I'm using connect from react-redux to bind my action creators to store.dispatch. I'm not sure how to bind the event listener to the version of the action creator that is bound to the store with dispatch. Is there a graceful way to do this?
import React, {PropTypes} from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import GridApp from '../components/GridApp';
import * as GridActions from '../actions/gridActions';
class App extends React.Component {
render() {
const { gridAppState, actions } = this.props;
return (
<GridApp gridAppState={gridAppState} actions={actions} />
);
}
componentDidMount() {
console.log("mounted")
// the following line won't be bound to the store here...
document.addEventListener("keydown", GridActions.naiveKeypress );
}
}
function mapStateToProps(state) {
return {
gridAppState: state.gridAppState
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(GridActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Simply get it from this.props:
componentDidMount() {
console.log("mounted")
// the following line won't be bound to the store here...
const { actions } = this.props;
document.addEventListener("keydown", actions.naiveKeypress );
}
I believe you also need to unsubscribe from the keydown event on component unmount event though. (even if it does not do that ever, just for sake of completeness and robustness).

Resources