addEventListener react redux with mapped dispatch - reactjs

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).

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

How to trigger callback function in Redux?

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)

How to dispatch an action when a button is clicked?

//action code
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
export const clearCompleted = () => {
return{
type: CLEAR_COMPLETED
}
}
//reducer code
case CLEAR_COMPLETED:
return state.map(todo => {if (todo.completed)
{return {...todo, show:false}}
else {return todo}})
Problem dispatching action on Todo application in react-redux.
import React from 'react'
import { connect } from 'react-redux'
import { clearCompleted } from '../actions'
const ClearButton = ({dispatch}) => {
return(
<button fluid onClick={e => {dispatch(clearCompleted())}}>
Clear Completed
</button>
)
}
export default ClearButton
Trying to change the store by clicking on Clear Completed Button. Clear Completed Button should remove the completed todos from the store and todo list should be updated. I am trying to call 'clearCompleted' action with Clear Completed Button.
The difficulty you're having here is that your component doesn't know anything about the Redux store, and the dispatch function will not be in its props. The most basic way you can make dispatch available would be this:
export default connect()(ClearButton)
This will allow you to use dispatch(clearCompleted()) without messing around further with mapDispatchToProps. You'd have to change its definition so it's not a stateless component though.
However, you should probably ask yourself whether a tiny button really needs connect at all? You could probably just pass the correct function down from the containing component:
// TodoList.js
class TodoList extends Component {
render () {
return (
...
<ClearButton clearCompleted={this.props.clearCompleted} />
)
}
}
const mapStateToProps = state => ({
// ...
})
const mapDispatchToProps = dispatch => ({
clearCompleted: () => dispatch(clearCompleted())
})
export default connect(mapStateToProps, mapDispatchToProps)(TodoList)
Then the function will be in ClearButton's props without it needing to be connected:
<button onClick={this.props.clearCompleted}>
You can do it by wrapping your component in connect.
connect accepts two arguments as first call, mapStateToProps for mapping your store properties into your component's props and mapDispatchToProps for mapping action creators into your component's props. It's also followed by another call to that function with the Component name of yours written in class syntax.
If you insist in using stateless components with connect, you can use compose utility from redux.
import React from 'react'
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux'
import { clearCompleted } from '../actions'
class ClearButton extends React.Component {
render() {
const {clearCompleted} = this.props;
return(
<button fluid onClick={clearCompleted}>
Clear Completed
</button>
)
}
}
const mapDispatchToProps = dispatch => bindActionCreators({ clearCompleted }, dispatch);
export default connect(null, mapDispatchToProps)(ClearButton);

dispatch is not defined on my functions using react and redux

I am trying to use react-redux-loading-bar to show a loading bar during fetching data from API servers, I don't use promise middleware so I decided to use it without, the example says do this
import { showLoading, hideLoading } from 'react-redux-loading-bar'
dispatch(showLoading())
// do long running stuff
dispatch(hideLoading())
And it gives me this.
Uncaught ReferenceError: dispatch is not defined
I had similar issues with other libraries and gave up that time, this time I want to actually understand how this works, so any info is greatly appreciated. Heres the code that causing the error, speicifc function and class names stripped.
import React from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { showLoading, hideLoading } from 'react-redux-loading-bar'
import * as xxxxxActions from '../../actions/xxxxx'
class xxxxxx extends React.Component {
constructor(props) {
super(props)
this.handleclick = this.handleclick.bind(this)
}
handleclick(){
dispatch(showLoading())
asynchronousGetFunction( target_url, function (data) {
dispatch(hideLoading())
})
}
render() {
return <li onClick={this.handleclick}>yyyyyyy</li>
}
}
function mapStateToProps( state ){
return {
}
}
function mapDispatchToProps(dispatch, state) {
return {
xxxxxActions: bindActionCreators( xxxxxActions, dispatch )
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(xxxxxx)
Once you connect your component, dispatch becomes a prop. The same applies for xxxxxActions...
In that case, the handle would be:
handleclick(){
this.props.dispatch(...)
}
You need to pass dispatch function to your props:
function mapDispatchToProps(dispatch, state) {
return {
xxxxxActions: ....,
showLoading: function () {
dispatch(showLoading());
},
hideLoading: function () {
dispatch(hideLoading());
},
};
}
Then, use it in your component:
this.props.showLoading();
...
this.props.hideLoading();
You don't need use "dispatch" in components. Bind your functions with dispatch in mapDispatchToProps.
Read more about mapDispatchToProps.

react redux props undefined on event listener when using mapDispatchToProps

My first time using react/redux and I'm trying to bind a simple action addClick to the 'click' event, but when I click I receive the error:
Uncaught TypeError: Cannot read property 'props' of undefined
My (stripped down) code is:
import {addClick} from './actions'
const mapDispatchToProps = {addClick}
class App extends Component {
componentDidMount() {
document.addEventListener('click', this.props.addClick)
}
componentWillUnmount() {
document.removeEventListener('click', this.props.addClick)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Before I had it implemented without mapDispatchToProps using an action creator defined within the class and bound to this in the constructor. But I thought part of the point of mapDispatchToProps was to bind the action creator to this (as well as wrapping it in a dispatch)
What am I missing?
Thanks!
From what I can tell from the documentation, the object short-hand (const mapDispatchToProps =
{addClick}) you use for mapDispatchToProps doesn't bind this to anything. It just sees to it that your addClick action creator gets called with dispatch. So that if you, in your component execute addClick(3), then that will result in a call looking like this dispatch(addClick(3)).
I'm not sure why your action creator would need access to this though. Can't you just pass it what ever data it needs as a parameter? So that the call in your component might look like
componentDidMount() {
const {addClick, someOtherProp} = this.props;
document.addEventListener('click', () => addClick(someOtherProp));
}
Do you use props inside addClick action?
Check this example:
import React from "react";
import { render } from "react-dom";
import { connect, Provider } from "react-redux";
import { createStore } from "redux";
function addClick(event) {
return {
type: "CLICK",
payload: `pageX: ${event.pageX} | pageY: ${event.pageY}`
};
}
const mapStateToProps = state => {
return {
clickXY: state
};
};
const mapDispatchToProps = { addClick };
class App extends React.Component {
componentDidMount() {
document.addEventListener("click", this.props.addClick);
}
componentWillUnmount() {
document.removeEventListener("click", this.props.addClick);
}
render() {
return (
<h1>
Click message: {this.props.clickXY}
</h1>
);
}
}
function clickReducer(state = "None", action) {
switch (action.type) {
case "CLICK": {
return action.payload;
}
default:
return state;
}
}
let store = createStore(clickReducer);
const AppContainer = connect(mapStateToProps, mapDispatchToProps)(App);
class Root extends React.Component {
render() {
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
}
}
render(<Root />, document.getElementById("root"));
Link to editor
So bind works well in this code.

Resources