react-redux dispatch undefined - reactjs

In my App.js :
const initialAppState = {
nums: {},
};
const reducer = (state = initialAppState, action) => {
if (
action.a === undefined ||
action.b === undefined ||
action.c === undefined
) {
return state;
}
if (state.nums[action.a][action.b] === undefined) {
state.nums[action.a][action.b] = {};
}
return {
nums: (state.nums[action.a][action.b] =
action.c),
};
};
const store = createStore(reducer);
Then passed it with Provider.
When in my Test.js I'm trying to change the value it give me error :
const mapStateToProps = (state) => {
return {
nums: state.nums,
};
};
function mapDispatchToProps(dispatch) {
return {
add: (a, b, c) =>
dispatch({a: a, b: b, c: c}),
};
}
export default connect(mapStateToProps, mapDispatchToProps())(Test);
And the Test functional component looks like this :
function Test({nums, dispatch})
...
function add(data, count) {
dispatch.add('1','2','3');
}
It give me error saying : TypeError: undefined is not an object (evaluating 'dispatch.add')
Shouldn't it recognize dispatch as dispatch is Test's parameter, and add is another function of that functional component ?

It’s not dispatch.add() its props.add()
// add comes in as a prop. that’s why it’s called map dispatch to props.
function Test({nums, add: doAdd})
...
function add(data, count) {
doAdd('1','2','3');
}

I would recommend you organize your code. Try to move actions and reducers into separate files like actions.js, reducers.js.
REDUCER NEED SOME FIXES:
In your example of reducer in the last return you are trying to mutate state.
Also as you can see in your second if there are no return.
PROPOSAL OF ACTIONS AND REDUCERS SEPARATION
Inside your App.js you will createStore:
//App.js
import { reducer } from "./reducers"
const store = createStore(reducer);
<Provider store={ store }>
...
</Provider>
Now let's do actions.js file:
//actions.js
export const ADD_ACTION='ADD_ACTION' // action type
export function addAction(data) {
return { type: ADD_ACTION, data }
}
In reducers you'll move part of code from your App.js. I recommend you to use switch inside your reducer like that:
//reducers.js
import { ADD_ACTION } from "./actions";
const initialAppState = {
nums: {},
};
export const reducer = (state = initialAppState, action) => {
switch(action.type) {
case ADD_ACTION:
return Object.assign({}, state, { nums: action.data });
default:
return state
}
};
As you can see one of the ADD_ACTION is returning new object with updated nums value. This is because one of the essential Redux rules is "Do not mutate state".
Now, let's try our action in Test.js. First import addAction from actions.js. Then call dispatch with addAction as an argument.
//Test.js functional component version
import { addAction } from "./actions";
function Test({dispatch}) {
...
function add(data, count) {
dispatch(addAction(data));
}
}
export default connect(null, null)(Test);
Component receives dispatch from connect. Connect has (null, null) because in this case we are not using mapStateToProps, nor mapDispatchToProps.
Extra tip: For class component you'll use action in a little bit different way.
//Test.js Class component version
import React from 'react';
import { addAction } from "./actions";
import { connect } from 'react-redux';
class TestClass extends React.Component {
render() {
return(
<button onClick={() => this.props.addAction(2)}>Action</button>
)
}
}
const mapDispatchToProps = ({
addAction
});
export default connect(null, mapDispatchToProps)(TestClass);
In class component example you'll call action with value 2 when onClick event fires.
Working sandbox example: react-redux example

Related

How to use Redux and React navigation to make a global counter

I am making a React-Native app in which I have a navigator from React Navigation and I also want to implement Redux. I am trying to create a global counter that updates based on an argument.
Here is the actions:
export const setFlags = (value) => {
return {
type: 'SETFLAGS',
value
}
}
export const setNonFlags = (value) => {
return {
type: 'SETNONFLAGS',
value
}
}
Here is the reducer, because its two things that have identical functionality I thought one would work (I am new to Redux):
const initialState = {
flags:0,
nonFlags:0,
}
const AllFlagReducer = (state = initialState, action) =>{
switch(action.type){
case 'SETFLAGS':
return state.flags = state.flags + action.value
case 'SETNONFLAGS':
return state.nonFlags = state.nonFlags + action.value
}
return state
}
export default AllFlagReducer
And here is the button where I would like to send the local state of the "flag" and "nonFlag" to the redux global states. After which I reset the local states and move to the next screen.
<TouchableOpacity style={styles.resetButton}
onPress= {
// dispatch something like flags(in redux):this.state.flags
// dispatch nonFlags(in redux): this.state.nonFlags
() =>{this.resetAll();
navigation.navigate('Specific Scams')
}}>
Help would be greatly appreciated.
UPDATE 1:
The entire component:
class ScamTree extends React.Component {
constructor(props){
super(props)
this.state = {
flags : 0,
nonFlags: 0,
qAnswered:0
}
}
functions that might matter:
resetAll = () =>{
this.setState({flags:0})
this.setState({nonFlags:0})
this.setState({qAnswered:0})
}
the button, (I did not make a separate component for just the button):
<TouchableOpacity style={styles.resetButton}
onPress= {
// dispatch something like flags:this.state.flags
// dispatch nonFlags: this.state.nonFlags
() =>{this.resetAll();store.dispatch({type:"SETFLAGS",value:5})
navigation.navigate('Specific Scams')
}}>
<Text style={{paddingHorizontal:40}}>NEXT</Text>
</TouchableOpacity>
the export to make React Navigation Work:
export default function(props) {
const navigation = useNavigation();
return <ScamTree {...props} navigation={navigation} />;
}
In your .js class you have to bind your action like this
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux';
<TouchableOpacity style={styles.resetButton}
onPress= {
// dispatch something like flags(in redux):this.state.flags
// dispatch nonFlags(in redux): this.state.nonFlags
() =>{
this.resetAll();
this.props.commanAction.setFlags(your value);
this.props.commanAction.setNonFlags(your value);
navigation.navigate('Specific Scams')
}}>
const mapDispatchToProps = dispatch => {
return {
commanAction: bindActionCreators(commanAction, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Your .js className);
Other code are looks good.
If I understand a comment of yours in another answer, and the snippet of your question update, I believe you need to decorate this functional component:
export default function(props) {
const navigation = useNavigation();
return <ScamTree {...props} navigation={navigation} />;
}
You want to decorate this component with the connect HOC to connect it to your redux store.
First I'd convert that function to a new withNavigation HOC:
const withNavigation = WrappedComponent => props => {
const navigation = useNavigation();
return <WrappedComponent{...props} navigation={navigation} />;
};
withNavigation.displayName = `withNavigation(${WrappedComponent.displayName || 'Component'})`;
Now you can decorate ScamTree as follows:
export default withNavigation(ScamTree);
But now you also need to connect your action creators to your redux store, decorate it with react-redux's connect HOC:
const mapDispatchToProps = {
setFlags,
setNonFlags,
};
export default withNavigation(
connect(null, mapDispatchToProps)(ScamTree),
);
Note: I see it seems you store in local state some flag values, not sure if that is related, but you can map in initial state for this with a mapStateToProps as the first parameter for connect instead of null.
By now you've probably noticed that each new HOC creates some nesting, and this will get worse with the more HOC's used. The solution is to use redux's compose HOC. That's right, it's not just for composing middleware for the store. It can compose all the decorators into a single HOC to wrap your exported component, or in other words, it flattens/eliminates the nesting.
Tips
All compose does is let you write deeply nested function
transformations without the rightward drift of the code. Don't give it
too much credit!
...
import { compose } from 'redux';
...
class ScamTree extends Component { ... }
const mapDispatchToProps = {
setFlags,
setNonFlags,
};
export default compose(
withNavigation,
connect(null, mapDispatchToProps),
)(ScamTree);
Note: Your reducers need to always return new state object references and never mutate existing state. Also, although your returns do work correctly, the reducer pattern is to return unhandled action types in the default switch case:
const AllFlagReducer = (state = initialState, action) => {
switch(action.type){
case 'SETFLAGS':
return { ...state, flags: state.flags + action.value };
case 'SETNONFLAGS':
return { ...state, nonFlags: state.nonFlags + action.value };
default:
return state;
}
}
In your reducer cases right now you writing new state over the whole state instead just in the fit place at state object, it can be written like
const AllFlagReducer = (state = initialState, action) =>{
switch(action.type){
case 'SETFLAGS':
return { ...state, flags: state.flags + action.value }
case 'SETNONFLAGS':
return { ...state, nonFlags: state.nonFlags + action.value }
default: return state
}
}
export default AllFlagReducer

Calling action creator inside the function. Error:Actions may not have an undefined "type" property?

I know action creator should have a type of property only then it would be able to dispatch. Since I am having a function call which ultimately leads to one action creator which have type property then Why it is showing me this problem.
When I tried to directly dispatch start game action creator it works but since I have to implement some more function inside them so I needed then inside the function.
How to implement the same?
Menu.js
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {startGame} from '../actions';
import {loadMenu} from '../actions';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
const page_Banner={
marginTop:'35px',
fontSize:'45px',
textAlign:'center',
letterSpacing:'20px',
fontWeight:'bold'
};
const spacebar_screen={
marginTop:'35px',
color:'grey'
}
class Menu extends Component {
componentDidMount() {
this.props.dispatch(loadMenu());
console.log(this.props.dispatch);
console.log(this.props.isPlaying);
}
render() {
return (
<div style={page_Banner}>
Redux Tetris
{!this.props.isPlaying?<h2 style={spacebar_screen}>Press spacebar to start the game</h2>:null}
</div>
)
}
}
Menu.propTypes={
isPlaying:PropTypes.bool,
}
// function mapDispatchToProps(dispatch){
// return bindActionCreators({loading:loadMenu},dispatch);
// }
const mapStateToProps = (state) => ({
isPlaying: state.gameStatus.currentState !== 'IDLE',
});
export default connect(mapStateToProps)(Menu);
Action.js
import constants from "../gameConstants/constants";
export const startGame=()=>{
const ShapeMapping=constants;
const current_Shapeno=Math.floor(Math.random()*7);
const next_Shapeno=Math.floor(Math.random()*7);
const current_Shape=ShapeMapping[current_Shapeno];
const next_Shape=ShapeMapping[next_Shapeno];
return {
type:"START_GAME",
current_Shape,
next_Shape
};
}
export const pauseGame = () => ({
type: "PAUSE_GAME",
});
export const unpauseGame = () => ({
type: "UNPAUSE_GAME",
});
export const gameOver = () => ({
type: "GAME_OVER",
});
export const loadMenu=()=>({
function(dispatch,getState){
function handleSpacebar(event){
if(event.keyCode==32){
dispatch(loadGame());
window.removeEventListener('keyup',handleSpacebar);
console.log('here')
}
}
window.addEventListener('keyup',handleSpacebar);
}
})
export const loadGame=()=>({
function (dispatch,getState){
dispatch(startGame());
}
})
The issue is in loadMenu and loadGame action creators. You're returning an object with an anonymous function which doesn't make any sense. An action creator is supposed to return an object with a type and the minimal data to define the action and return a function if you're using redux-thunk.
Keep the actions creators clean like you've done in gameOver and handle everything else in reducers or using the redux pub/sub pattern.
See this answer by Dan Abramov https://github.com/reduxjs/redux/issues/787

Dispatched action received by reducer, but not appearing in action list and not rerendering

I have a Comment component that has a delete button. When the button is clicked, it called the action, makes the axios call, and when the call returns it dispatches the update to the reducer. The only problem is that it's not triggering the rerender of the parent component. The action, although it is updating the state, does not appear in the list of dispatched actions in the Redux DevTools. All other actions work and display in the DevTools, but for some reason this one doesn't.
My thought after reading the comment section below is that it's because I'm making a shallow copy of my object. Am I wrong to think that making a shallow copy, modifying a deeper object, and returning the shallow copy wouldn't trigger a rerender? Isn't the fact that the shallow object is a different reference enough to trigger it? I'm confident I'm doing this the same way in other places and I havenn't have a problem elsewhere. It
This is the action list in Redux DevTools after deleting the comment. I would expect that it would have "delete_comment" near the bottom somewhere, but it's not:
The data is passed from the parent components CommentList -> CommentThread -> Comment.
Is this truly dispatching?
This is a simplified component:
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {
deleteComment,
} from "../../actions/comment_actions";
class Comment extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
const {comment, data} = this.props;
if (!data) {
//console.log("mir_data doesnt exist");
return <div/>;
}
return (
<div key={"comment" + comment.id} id={"c" + comment.id}>
<button onClick={() => this.props.deleteComment(comment.id)}>Delete</button>
</div>
);
}
}
function mapStateToProps(state) {
return {
user: state.user
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
deleteComment,
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Comment);
Here's a simplified action file:
export const DELETE_COMMENT = 'delete_comment';
export function deleteComment(id, callback = null) {
return (dispatch, getState) => {
axiosInstance.post('/delete-comment/', {comment_id: id}, getState().api.axios).then(
response => {
dispatch(dispatchDeleteComment(id));
//dispatch(dispatchViewUserInfo(response.data));
if (response.status === 200)
callback();
}
);
}
}
export function dispatchDeleteComment(id) {
return {
type: DELETE_COMMENT,
payload: id
};
}
Here's the simplified reducer:
import {DELETE_COMMENT} from "../actions/comment_actions";
export default function(state = {}, action){
let newState = {...state};
switch(action.type){
case DELETE_COMMENT:
//some logic
delete newState.comments[action.payload];
return newState;
default:
return state;
}
}

Subscribing to single reducer with Redux Connect still delivers all reducers to component

I have a store with 9 reducers in it. I only want my component to listen to one of them discoverSearch. Using shorthand version of mapStateToProps this is my code. However, the component is still being delivered all reducers in componentWillReceiveProps.
Component
import React from 'react'
import { connect } from 'react-redux'
import { View, Text, Animated, Dimensions } from 'react-native'
const _ = require('lodash')
import colors from '../../Color'
import DiscoverSearchResultChannel from './DiscoverSearchResultChannel'
import DiscoverSearchResultEpisode from './DiscoverSearchResultEpisode'
const { height, width } = Dimensions.get('window')
class DiscoverSearchResultsContainer extends React.Component {
constructor() {
super()
this.generateResultsList = this.generateResultsList.bind(this)
}
generateResultsList(results, type) {
const components = []
for (let i = results.length - 1; i >= 0; i--) {
if (type === 'CHANNEL') {
const result =
(<DiscoverSearchResultChannel
entry={results[i]}
key={`dsearch-${results[i].id}`}
navigation={this.props.navigation}
/>)
components.push(result)
} else if (type === 'EPISODE') {
const result =
(<DiscoverSearchResultEpisode
entry={results[i]}
key={`dsearch-${results[i].id}`}
navigation={this.props.navigation}
/>)
components.push(result)
}
}
return components
}
render() {
const { episodes, channels } = this.props.discoverSearch.results
return (
<Animated.ScrollView
style={styles.shell}
contentContainerStyle={styles.innerContent}
>
<Text style={styles.divider}>Podcasts</Text>
{
_.isUndefined(channels) ? null : this.generateResultsList(channels, 'CHANNEL')
}
<Text style={styles.divider}>Episodes</Text>
{
_.isUndefined(episodes) ? null : this.generateResultsList(episodes, 'EPISODE')
}
</Animated.ScrollView>
)
}
}
export default connect(store => (
{ discoverSearch: store.discoverSearch },
dispatch => dispatch
))(DiscoverSearchResultsContainer)
Store
import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import combinedReducers from './reducers/CombineReducers'
const middleware = applyMiddleware(
thunk,
createLogger(),
promise()
)
export default createStore(combinedReducers, middleware)
DiscoverSearchReducer
const initialState = {
results: []
}
const DiscoverSearchReducer = (state = initialState, action) => {
let newState
switch (action.type) {
case 'DISCOVER_SEARCH_REQUEST_OUT':
// TODO
break
case 'DISCOVER_SEARCH_RETURN':
newState = {
...state,
results: action.payload
}
break
default:
return state
}
return newState
}
Reducers
export default combineReducers({
network: NetworkReducer,
audioPlayer: AudioPlayerReducer,
fileHandler: FileHandlerReducer,
currentTrack: CurrentTrackReducer,
trackQueue: TrackQueueReducer,
asyncStatus: AsyncStatusReducer,
makeClip: MakeClipReducer,
userProfile: UserProfileReducer,
scrollListener: ScrollListenReducer,
userClips: UserClipsReducer,
discoverSearch: DiscoverSearchReducer,
})
App Entry
class App extends React.Component {
componentWillMount() {
// TODO
// Initialize Firebase => get UID then...
store.dispatch(fetchUser('7713BNBNPODPIE'))
}
componentDidMount() {
TrackPlayer()
}
render() {
return (
<Provider store={store} >
<View style={{ flex: 1 }}>
<Navigation />
<BottomPlayer />
</View>
</Provider>
)
}
}
export default App
The connect piece of Redux is a little new to me so I might be missing something obvious?
Edits:
Added App.js entry point
Added full DiscoverSearchResultsContainer component minus styles
The error is in your connect function:
export default connect(store => (
{ discoverSearch: store.discoverSearch },
dispatch => dispatch
))(DiscoverSearchResultsContainer)
You have parenthesis wrong, this is equivalent to:
connect(store => {
// this does nothing
{ discoverSearch: store.discoverSearch };
return dispatch => dispatch;
})(...)
Which is actually the same as
connect(state => state)(...)
What you probably meant to write was:
connect(store = ({ discoverSearch: store.discoverSearch }),
dispatch => dispatch)(...)
EDIT: Remove unnecessary dispatch
As commented, mapping the dispatch is useless, you can just do
const mapStateToProps = state => ({ discoverSearch: state.discoverSearch });
connect(mapStateProps)(Component);
The easiest to way use connect is to separate out mapStateToProps and mapDispatchToProps into their own function so as to avoid making syntactical errors and if you are anyways returning dispatch as the second parameter you might as well not use it since if the second parameter to connect is empty, dispatch is returned by default.
According to the connect documentaion
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or
Function): If an object is passed, each function inside it is assumed
to be a Redux action creator. An object with the same function names,
but with every action creator wrapped into a dispatch call so they may
be invoked directly, will be merged into the component’s props.
If a function is passed, it will be given dispatch as the first
parameter. It’s up to you to return an object that somehow uses
dispatch to bind action creators in your own way. (Tip: you may use
the bindActionCreators() helper from Redux.)
If your mapDispatchToProps function is declared as taking two
parameters, it will be called with dispatch as the first parameter and
the props passed to the connected component as the second parameter,
and will be re-invoked whenever the connected component receives new
props. (The second parameter is normally referred to as ownProps by
convention.)
If you do not supply your own mapDispatchToProps function or object
full of action creators, the default mapDispatchToProps
implementation just injects dispatch into your component’s props.
You could use your connect statmeent like
const mapStateToProps = store => {
return { discoverSearch: store.discoverSearch }
}
export default connect(mapStateToProps)(DiscoverSearchResultsContainer)
When you use connect like
export default connect(store => (
{ discoverSearch: store.discoverSearch },
dispatch => dispatch
))(DiscoverSearchResultsContainer)
You actually have your () at the wrong place as you want to return { discoverSearch: store.discoverSearch } and not { discoverSearch: store.discoverSearch }, dispatch => dispatch. It should be
export default connect(store => (
{ discoverSearch: store.discoverSearch })
)(DiscoverSearchResultsContainer)
The above snippet would be the same as
export default connect(store => {
return { discoverSearch: store.discoverSearch }
}
)(DiscoverSearchResultsContainer)
which is what you need. However you must go with the first approach
Is it possible that your DiscoverSearchReducer is returning a new object for each dispatch, even on a no-op?
function reduceSomething(state, action) {
if action.type == "forMe":
return {action.payload}
else
return {...state}
}
rather than:
return state
Because the former will give you a new object for every dispatch, with the expected content, but connect will be unable to suppress passing props to your component.
It's a weird one but it matches your symptoms.

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