Dispatch in component is UNDEFINED and dispatch in mapDispatchToProps is ok
Action and reducers is ok - 100%;
I want transfer to dispatch in action to create request and run action from action
My component:
import React, { Component, Fragment } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import styled from 'styled-components';
import { carAction, fetchDogAction } from '../../../actions/CarAction';
class CarListContainer extends Component {
render () {
const { cars, carAction, dispatch, dog } = this.props;
console.log(dispatch) // THIS UNDEFINED!!!!
return(
<Fragment>
<Title onClick={() => {
fetchDogAction(dispatch)
}}>HEllo</Title>
{cars.map((elem) =>
<Title onClick={() => {carAction (elem)}} key={elem.id}>{elem.mark}</Title>
)}
{dog ? <img src={dog.message} /> : null }
</Fragment>
)
}
}
const Title = styled('h2')`
color: red;
font-size: 30px;
`;
function mapStateToProps (state) {
return {
cars: state.cars,
dog: state.dog
}
}
function mapDispatchToProps(dispatch) {
console.log(dispatch) //THIS DISPATCH IS OK
return bindActionCreators ({
carAction: carAction,
fetchDogAction: fetchDogAction
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(CarListContainer);
Your error comes from having bindActionCreators() inside mapDispatchToProps() which is not recommended practice, since mapDispatchToProps uses bindActionCreators internally.
bindActionCreators and mapDispatchToProps - Do I need them?
You can try using an arrow function and dispatching actions directly in the mapDispatchToProps method.
import * ACTIONS from './actions'
function mapDispatchToProps(dispatch) {
return {
carAction: (elem) => dispatch(ACTIONS.fetchCarAction(elem)) ,
dogAction: () => dispatch(ACTIONS.fetchDogAction())
}
}
//action creators import them into your component
export const fetchCarAction = (elem) => {
return {
type: ACTION_TYPES.CARACTION,
payload: elem
}
}
export const fetchDogAction = () => {
return {
type: ACTION_TYPES.DOGACTION,
}
}
//render method
<Title onClick={() => this.props.dogAction() }>HEllo</Title>
{cars.map((elem) =>
<Title onClick={() => this.props.carAction(elem) } key={elem.id}>{elem.mark}
</Title>
)}
So in your render you will call the name of the property in mapDispatchToProps and not the name of the action creator, I kept the name of the action creators and name of the properties different so you can see this.
If you want a fully functioning react-redux app you can check out this starter project I built, I got good feedback on it.
https://github.com/iqbal125/modern-react-app-sample
Related
I have an eCommerce React app I'm putting together that has a view of items. Each item has an Add to cart button with an onClick function that dispatches an ADD_ITEM action to update the cart in state.
The problem I'm seeing is that the Action is never firing and the state is never updating, but there aren't any console errors or anything to point me in the direction of what's broken.
I've looked at everything over and over, there's no typos and everything is connected to the store so I'm really at a loss as to why it's not working.
Cart Reducer
import { AnyAction } from "redux";
import CartActionTypes from "./cart.types";
const INITIAL_STATE = {
hidden: true,
cartItems: [],
};
const cartReducer = (state = INITIAL_STATE, action: AnyAction) => {
switch (action.type) {
case CartActionTypes.TOGGLE_CART_HIDDEN:
return {
...state,
hidden: !state.hidden,
};
case CartActionTypes.ADD_ITEM:
return {
...state,
cartItems: [...state.cartItems, action.payload],
};
default:
return state;
}
};
export default cartReducer;
Cart Actions
import CartActionTypes from "./cart.types";
export const toggleCartHidden = () => ({
type: CartActionTypes.TOGGLE_CART_HIDDEN,
});
export const addItem = (item) => ({
type: CartActionTypes.ADD_ITEM,
payload: item,
});
Cart Types
const CartActionTypes = {
TOGGLE_CART_HIDDEN: "TOGGLE_CART_HIDDEN",
ADD_ITEM: "ADD_ITEM",
};
export default CartActionTypes;
Root Reducer
import { combineReducers } from "redux";
import userReducer from "./user/user.reducer";
import cartReducer from "./cart/cart.reducer";
export default combineReducers({
user: userReducer,
cart: cartReducer,
});
Item Component with onClick/mapDispatchToProps function
import React from "react";
import styled from "styled-components";
import { connect } from "react-redux";
import { addItem } from "../../redux/cart/cart.actions";
import { Item } from "../../pages/shop/shop.component";
const CollectionItem = ({ item }: { item: Item }) => {
const { name, price, imageUrl } = item;
return (
<CollectionItemContainer>
<Image
style={{ backgroundImage: `url(${imageUrl})` }}
className="image"
/>
<CollectionFooter>
<Name>{name}</Name>
<Price>{price}</Price>
</CollectionFooter>
<CollectionItemButton
onClick={() => addItem(item)}
className="custom-button inverted"
>
Add to cart
</CollectionItemButton>
</CollectionItemContainer>
);
};
const mapDispatchToProps = (dispatch) => ({
addItem: (item) => dispatch(addItem(item)),
});
export default connect(null, mapDispatchToProps)(CollectionItem);
Item Collection component (Parent of Item Component)
import React from "react";
import styled from "styled-components";
import { Item } from "../../pages/shop/shop.component";
import CollectionItem from "../collection-item/collection-item.component";
const CollectionPreview = ({
title,
items,
}: {
title: string;
items: Array<Item>;
}) => {
return (
<CollectionPreviewContainer>
<Title>{title.toUpperCase()}</Title>
<Preview>
{items
.filter((item, idx) => idx < 4)
.map((item) => (
<CollectionItem key={item.id} item={item} />
))}
</Preview>
</CollectionPreviewContainer>
);
};
export default CollectionPreview;
There is only a very small issue, but very relevant issue in your code: In your CollectionItem component your not using the addItem function from your props, which was injected by connect with your mapDispatchToProps function. You probably meant to destructure it in the function definition of your CollectionItem component but just forgot it.
changing
const CollectionItem = ({ item }: { item: Item }) =>
to
const CollectionItem = ({ item, addItem }: { item: Item, addItem: () => void }) =>
should fix the issue.
Note that you didn't see any error because your action creator is called addItem too. Therefore when you call addItem in the onClick function, the function is still defined even though you didn't destructure it from the props. However calling the action creator instead of the function from mapDispatchToProps will just create the action (a plain js object) and return it, without dispatching it...
To avoid such hard to spot mistakes I would recommend to name the function injected through mapDispatchToProps differently than the action creator.
Example:
const CollectionItem = ({ item /* missing fn here */ }: { item: Item }) => {
const { name, price, imageUrl } = item;
return (
<CollectionItemContainer>
<Image
style={{ backgroundImage: `url(${imageUrl})` }}
className="image"
/>
<CollectionFooter>
<Name>{name}</Name>
<Price>{price}</Price>
</CollectionFooter>
<CollectionItemButton
onClick={() => handleAddItem(item)}
className="custom-button inverted"
>
Add to cart
</CollectionItemButton>
</CollectionItemContainer>
);
};
const mapDispatchToProps = (dispatch) => ({
handleAddItem: (item) => dispatch(addItem(item)),
});
Not the the error would become really obvious, because a handleAddItem function not defined error would be thrown and you'd immediately know that you are missing the handleAddItem function in the first line of this example.
import React from 'react'
import "./login.css"
import {Button} from "#material-ui/core";
import { auth, provider} from "./firebase";
function login() {
const signin= () =>
{
auth.signInWithPopup(provider).catch(error => alert(error.message));
};
return (
<div id="Login">
<div id="login_logo">
<img src="https://cdn.worldvectorlogo.com/logos/discord-logo-color-wordmark-1.svg"/>
</div>
<Button onClick={() => signin} >Sign in</Button>
</div>
)
}
export default login;
I'm creating a module named isMember. This module should be able to check if state.current_user.member is true or not.
1 import { select } from "redux-saga/effects"
2
3 export function* isMember() {
4 const { member } = yield select((state: any) => state.current_user)
5 console.log("isMember: ", member)
6 return member
7 }
I'm trying to test it like this in my view:
import { isMember } from "../utils/isMember"
return (
{ isMember() && <span> is a member </span> }
)
But that yields this error:
select is an Effect object of redux-saga. It is a tool to handle redux's side effects and should be used exclusively in a saga.
Generally, to test your module, you need to put it in a saga watcher that watches over an action:
import { takeEvery } from 'redux-saga'
function* watchTestingAction() {
yield takeEvery(TESTING_ACTION_TYPE, isMember)
}
and dispatch that action in React component:
import { connect } from 'react-redux'
...
return (
<MainContainer withScroll>
<GjeButton
title="isMember?"
onPress={this.props.testingAction} />
...
const mapState = (state) => ({})
const mapDispatch = (dispatch) => ({
testingAction: () => dispatch({ type: TESTING_ACTION_TYPE })
})
export default connect(mapState, mapDispatch)(YourComponent)
EDIT: To get state data, redux-saga is not needed. What you need is connect from react-redux.
import React, { Component } from 'react'
import { connect } from 'react-redux'
...
class MyComponent extends Component
render() {
return (
<MainContainer withScroll>
<GjeButton
title="isMember?"
onPress={() => { console.log(this.props.isMember) }} />
)
}
}
const mapState = (state) => ({
isMember: state.current_user.member || false
})
export default connect(mapState)(YourComponent)
I'm trying to fetch test API using FetchAPI and Redux.
The problem is with dispatch redux action.
Here's my code:
ProductList.js
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as productActions from '../actions/product';
import RaisedButton from 'material-ui/RaisedButton';
function fetchProductsWithRedux() {
console.log("fetchProductsWithRedux-1");
return (dispatch) => {
console.log("fetchProductsWithRedux-2");
dispatch(this.props.action.fetchProdutcsRequest());
return fetchProdutcs().then(([response, json]) => {
if (response.status === 200) {
console.log("success");
dispatch(this.props.action.fetchProductsSucesss(json))
}
else {
console.log("error");
dispatch(this.props.action.fetchProductsError())
}
})
}
}
function fetchProdutcs() {
const URL = "https://jsonplaceholder.typicode.com/posts";
return fetch(URL, { method: 'GET' })
.then(response => Promise.all([response, response.json()]));
}
class ProductList extends Component {
constructor(props) {
super(props);
this.state = {
productList: [
'product 1',
'product 2',
'product 3'
]
}
}
componentDidMount() {
fetchProductsWithRedux();
}
render() {
return (
<div className="ProductList">
<h2>Products</h2>
<ul>
{
this.props.posts &&
this.props.posts.map((post) => {
return (
<li>{post.title}</li>
)
})
}
</ul>
<ol>
<RaisedButton label="Get Products Action" onClick={this.props.action.getProducts} />
</ol>
</div>
);
}
}
function mapStateToProps(state, props) {
return {
product: state.product,
posts: state.posts
};
}
function mapDispatchToProps(dispatch) {
return {
action: bindActionCreators(productActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductList);
product.js (actions)
export function fetchProductsRequest() {
console.log('fetchProductsRequest');
return {
type: 'FETCH_PRODUCTS_REQUEST'
}
}
export function fetchProductsSuccess(payload) {
console.log('fetchProductsSuccess');
return {
type: 'FETCH_PRODUCTS_SUCCESS'
}
}
export function fetchProductsError() {
console.log('fetchProductsError');
return {
type: 'FETCH_PRODUCTS_ERROR'
}
}
product.js (reducer)
export default(state = [], payload) => {
switch (payload.type) {
case 'FETCH_PRODUCTS_REQUEST':
console.log('FETCH_PRODUCTS_REQUEST action');
return state;
case 'FETCH_PRODUCTS_SUCCESS':
console.log('FETCH_PRODUCTS_SUCCES action');
return {...state, posts: payload.payload}
default:
return state;
}
};
store.js
import { createStore } from 'redux';
import rootReducer from './reducers';
export default(initialState) => {
return createStore(rootReducer, initialState);
}
Product.js (pages, component)
import React, { Component } from 'react';
import ProductList from '../../components/ProductList';
import RaisedButton from 'material-ui/RaisedButton';
//import './Product.css';
class Product extends Component {
render() {
return (
<div className="Product">
<h1>ProductList Page</h1>
<RaisedButton label="Default" />
<ProductList />
</div>
);
}
}
export default Product;
The line console.log("fetchProductsWithRedux-2"); in ProductList.js has never been reached.
What's wrong? Any ideas? Thanks in advance.
seem you missed import thunk from 'redux-thunk'. You can't return a function in redux action if you dont use any middleware like 'redux-thunk'
There are a few issues with your code.
Firstly, fetchProductsWithRedux is an action, so you would need to dispatch it rather than calling it directly. As Bjoern mentioned, the way you are calling it, the function call just returns a function, which is never called.
Now, you cannot dispatch it in the current scenario, as it returns a function, rather than object. To fix that, you will have to use redux-thunk, which will allow it to dispatch a function.
You can add it to mapDispatchToProps, as you did for getProducts, but there is a shorthand for that. In your mapDispatchToProps, you can do the following:-
const mapDispatchToProps = { fetchProductsWithRedux, getProducts }
You will notice that it is just returning an object with 2 functions.
From the documentation:
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.
So, it will do exactly what you did earlier with bindActionCreators, but it looks more elegant. Now, instead of onClick={this.props.action.getProducts}, you can do onClick={this.props.getProducts}. Notice the missing action.
Also, your import will change to
import { getProducts } from '../actions/product';
Now, to fix your issue, in componentDidMount, rather than calling the function directly, you will have to do:-
componentDidMount() {
this.props.fetchProductsWithRedux();
}
Hopefully, this will help.
function fetchProductsWithRedux() {
console.log("fetchProductsWithRedux-1");
return (dispatch) => {
console.log("fetchProductsWithRedux-2");
...
}
This returns a function function(dispatch){...}, which is not called in
componentDidMount() {
fetchProductsWithRedux();
}
After entering the text in input field and clicking submit button the error occurs: Uncaught TypeError: dispatch is not a function
at onSubmit
Connecting state and props seems to be correct.
What could be wrong?
*/TODOLIPUT*/
import React from 'react'
import { connect } from 'react-redux'
import {addTodo} from '../actions/index'
import {bindActionCreators} from 'redux'
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
const mapStateToProps = (state) => {
return {
todos: state.todos
}
}
function matchDispatchToProps(dispatch){
return bindActionCreators({addTodo: addTodo}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(AddTodo)
/*TODOLIST*/
import React from 'react';
import {Todo} from './todo';
import { connect } from 'react-redux'
import {bindActionCreators} from 'redux'
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo {...todo} />
)}
</ul>
)
function mapStateToProps(state) {
return {
todos:state.todos
}
}
export default connect(mapStateToProps, null)(TodoList)
/* REDUCER */
import {combineReducers} from 'redux';
export const reducers = combineReducers({
todos:todos
})
export function todos(state=[], action) {
switch(action.type) {
case 'ADD_TODO':
return [
...state,
{
text:text,
completed:false
}
]
default:
return state
}
}
*/ACTION*/
export const addTodo = (text) => {
return {
type: 'ADD_TODO',
text
}
}
You can make two changes to get your code working.
First: If you use dispatch in your component, you need not use mapDispatchToProps since dispatch will be made available to you by default by connect
export default connect(mapStateToProps)(AddTodo)
Second: The other way is to make use of bindActionCreators which binds your action creators to dispatch and hence there is not need for a separate dispatch event in your component
let AddTodo = (props) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
props.addTodo(input.value);
input.value = ''
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
One more thing,since your are resolvind the props to {dispatch} in your AddTodo argument, you wont be having access to the todos state.
You don't need to use dispatch, since you have used bindActionCreators in your mapDispatchToProps .
bindActionCreators is a helper that enables action creators to directly dispatch actions. So you can just invoke your action creator and it should automatically dispatch the action.
You can either use dispatch and not pass mapDispatchToProps, or you can use the props injected by mapDispatchToProps, and not use dispatch. This is why mapDispatchToProps is called this way—it lets you define some other props based on dispatch so you don’t need to use it again.
Check this: https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples
I'm working on setting up a user login screen in React Native using Recompose, with separate actions and reducer files, but my reducer is never being called. Currently, there is just a login button that triggers a doUserLogin() recompose handler:
loginScreen.js
import React from 'react';
import { Button, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { withHandlers, compose } from 'recompose';
import { loginUser } from './user/userActions';
const LoginScreen = ({ user, doUserLogin }) => {
return (
<View style={styles.loginContainer}>
{user ? <Text>Hi, {user.name}!</Text> : <Text>NOT Logged in!</Text>}
<Button title="Log In" onPress={doUserLogin} />
</View>
);
};
export default compose(
connect((state, props) => ({
...state.user,
})),
withHandlers({
doUserLogin: props =>
(event) => {
console.log('LOGIN USER IN HANDLER'); // <-- THIS IS WORKING
loginUser();
},
}),
)(LoginScreen);
The doUserLogin() handler in turn calls loginUser() in my actions file:
userActions.js:
import { LOGIN_REQUEST } from './actionTypes';
export const loginUser = () => {
return (dispatch) => {
console.log('In action'); // <-- THIS IS WORKING
dispatch({ type: LOGIN_REQUEST });
};
};
So far, so good. However, when I dispatch(), my reducer is never called. But the reducer is picking up other actions (from navigation, etc.) - it simply isn't receiving the action from loginUser() above:
userReducer.js:
import { LOGIN_REQUEST } from './actionTypes';
const userReducer = (state = initialState, action) => {
console.log('In reducer'); <-- ** THIS IS NEVER CALLED **
switch (action.type) {
case LOGIN_REQUEST:
return Object.assign({}, state, {
isFetching: true,
});
case LOGOUT:
return initialState;
default:
return state;
}
};
export default userReducer;
Any suggestions would be greatly appreciated.
Ok, looks like I was able to figure this out. In a nutshell, in loginScreen.js I needed to add mapStateToProps and mapDispatchToProps functions, which are passed to connect. withHandlers can then dispatch the loginUser() function in my actions file as a prop.
updated loginScreen.js
import React from 'react';
import { Button, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { withHandlers, compose } from 'recompose';
import { loginUser } from './user/userActions';
const LoginScreen = ({ user, doUserLogin }) => {
return (
<View style={styles.loginContainer}>
{user ? <Text>Hi, {user.name}!</Text> : <Text>NOT Logged in!</Text>}
<Button title="Log In" onPress={doUserLogin} />
</View>
);
};
const mapStateToProps = state => ({
...state.user,
});
const mapDispatchToProps = dispatch => ({
loginUser: () => {
dispatch(loginUser());
},
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withHandlers({
doUserLogin: props =>
() => {
console.log('LOGIN USER IN HANDLER');
props.loginUser();
},
}),
)(LoginScreen);
Any additional advice/suggestions would still be appreciated.
Actually, for this particular case, you can dismiss completely withHandlers helper.
You only need to pass the action creator to the react-redux connect function, in order to bind it to the dispatch function, just as you shown. Even more, check connect docs. You can access the props of the component, in the 3rd parameter of connect, and further create handlers that depend on props.
In your case it could be something like this
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return Object.assign({}, ownProps, stateProps, dispatchProps, {
doUserLogin: () => {
console.log('LOGIN USER IN HANDLER');
console.log('accessing a prop from the component', ownProps.user);
dispatchProps.loginUser();
}
});
}
export default connect(mapStateToProps,
mapDispatchToProps,
mergeProps)(LoginScreen);
Notice how we can create new functions, that will be available as a new prop to the component, in a similar way to withHandler helper