Dispatch Redux action in React - reactjs

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();
}

Related

Redux-Initial Api call Not working from action creator

I have just gone through some redux tutorial and started to implement
i have one API call that has to be happen from redux as soon as page loads.. its possible with ComponentDidMount ,but i need to know how redux helps in achieving this.
For easiness i had shared the code in
https://codesandbox.io/s/quirky-sunset-s95gu?fontsize=14
My index.js look like
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { createStore,applyMiddleware } from "redux";
import allReducer from "./reducers";
import { Provider } from "react-redux";
import thunk from 'redux-thunk';
import "./styles.css";
let store = createStore(
allReducer,
applyMiddleware(thunk)
);
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
My Action creater look like(action/index.js)
import UserApi from "../UserApi";
export function loadUserSuccess(user) {
return { type: "LOAD_USER_SUCCESS", user };
}
export function loadUsers() {
return function(dispatch) {
return UserApi.getAllUsers()
.then(user => {
console.log("midhun");
dispatch(loadUserSuccess(user));
})
.catch(error => {
throw error;
});
};
}
and its subsequent api caliing function look like
class UserApi {
static getAllUsers() {
return fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
console.log("response", response);
return response.json();
})
.catch(error => {
return error;
});
}
}
export default UserApi;
My Reducer look like
import initialState from "./InitialState";
export default function IsLoggedReducer(state = initialState.user, action) {
console.log(state, action);
switch (action.type) {
case "LOAD_USER_SUCCESS":
return state;
default:
return state;
}
}
and my App.js look like
import React from "react";
import { connect } from "react-redux";
import * as userActions from "./action/index";
import UserList from "./UserList";
class App extends React.Component {
render() {
return (
<div>
<h1>MYpage</h1>
<UserList user={this.props.user} />
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
export default connect(mapStateToProps)(App);
I had put couple of console in action creator and its subsequent function,but its not triggering.
Any help will be much much apreciated and will be highly help for beginners
You guys can check the complete set of code
https://codesandbox.io/s/quirky-sunset-s95gu?fontsize=14
In addition to the info lavor gaved, since you use combineReducers, you need to access to state by using your reducer key.
App.js
import React from "react";
import { connect } from "react-redux";
import {loadUsers} from "./action/index";
import UserList from "./UserList";
class App extends React.Component {
componentDidMount() {
this.props.loadUsers();
}
render() {
return (
<div>
<h1>MYpage</h1>
<UserList users={this.props.users} />
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return {
users: state.IsLoggedReducer
};
}
export default connect(mapStateToProps, {loadUsers})(App);
I also made some corrections in the reducer file, we need to return the new state with the given payload.
import initialState from "./InitialState";
export default function IsLoggedReducer(state = initialState.user, action) {
console.log("ap", action.payload);
switch (action.type) {
case "LOAD_USER_SUCCESS":
return [...action.payload]
default:
return state;
}
}
And action file:
import UserApi from "../UserApi";
export function loadUserSuccess(users) {
return { type: "LOAD_USER_SUCCESS", payload: users };
}
export function loadUsers() {
return function(dispatch) {
return UserApi.getAllUsers()
.then(users => {
dispatch(loadUserSuccess(users));
})
.catch(error => {
throw error;
});
};
}
You can check this codesandbox for the working app.
https://codesandbox.io/s/cranky-colden-v6r6w
You are not dispatching your action, try to do it in componentDidMount (you need to map dispatch to props first):
App.js
componentDidMount() {
this.props.loadUsers();
}
// ...
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
function mapDispatchToProps(dispatch) {
return {
loadUsers: () => dispatch(userActions.loadUsers())
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);

my mapdispatch to props is automatically calling

Post component:
import React from 'react';
import './post.styles.scss';
import { connect } from 'react-redux';
import { requestContents } from '../../redux/post/post.actions';
class Post extends React.Component {
componentWillMount(){
}
render(){
return (
<div>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onRequestContents : dispatch(requestContents())
}
}
const mapStateToProps = (state) => {
return {
posts: state.post.posts,
isPending: state.post.isPending
}
}
User component:
export default connect(mapStateToProps, mapDispatchToProps)(Post)
import React from 'react';
import './user.styles.scss';
import { connect } from 'react-redux';
import { requestUsers } from '../../redux/user/user.actions';
class User extends React.Component {
componentWillMount(){
this.props.onRequestUsers();
}
render(){
return (
<div>
</div>
}
}
const mapDispatchToProps = (dispatch) => {
return {
onRequestUsers: () => dispatch(requestUsers())
}
}
const mapStateToProps = (state) => {
return {
users: state.user.users,
isPending: state.user.isPending
}
}
export default connect(mapStateToProps, mapDispatchToProps)(User)
Here I am using redux with React.
In user component i am calling onRequestUsers inside componentWillMount()
but in post component i am not calling onRequestContents inside componentWillMount()
But still how it is calling and display in my redux-logger
I am calling mapDispatchToProps only inside user component
Please have a look
You're calling requestContents in your mDTP call:
const mapDispatchToProps = (dispatch) => {
return {
onRequestContents: dispatch(requestContents())
}
}
That's what those two parens do:
requestContents()
Omit them:
onRequestContents: dispatch(requestContents)
That said: there's something odd with your function. Normally you'd mDTP with a function, e.g.,
onRequestContents: () => dispatch(requestContents())
(Used when you need to pass parameters, like an event.)

React-redux connect() not subscribing to dispatched actions properly

I have a simple component I'm trying to make work with redux. I map both props and dispatch actions, however only the props I initially get from the store work properly. I traced it all down to my actions: they are being dispatched, but respective reducers don't really do anything. Pretty simple stuff I came up with according to the tutorial and everything looks good to me, but I can't wrap my head around the problem here.
Here is a simplified version of the app:
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Search from './Search'
import { Provider } from 'react-redux'
import store from './store'
const root = document.querySelector('#app')
ReactDOM.render(
<Provider store={store}>
<Search/>
</Provider>, root)
// Search.js
import React from 'react'
import { setText } from '../../actions/appActions'
import { connect } from 'react-redux';
const mapStateToProps = state => {
return {
text: state.app.searchText
}
}
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
class Search extends React.Component {
constructor() {
super()
}
render() {
return (
<input type="text" onChange={() => this.props.setText("text")} value={this.props.text}/>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Search)
// store.js
import { createStore, combineReducers } from 'redux'
import app from './reducers/appReducer'
export default createStore(combineReducers({/*other non-relevant reducers*/, app}))
// appActions.js
export function setText(text) {
return {
type: "APP_SET_TEXT",
payload: text,
}
}
// appReducer.js
const initialState = {
isSearchActive: true,
searchText: "Text",
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case "APP_SET_TEXT":
console.log("fart")
return {
...state,
searchText: action.payload,
}
default:
return state
}
}
What I'm trying to to is to simply make the input value change according to the redux state. I do get the text from {this.props.text}, the change handler onChange={() => this.props.setText("text")} is being dispatched, but the reducer for some reason fails to catch the action that was dispatched.
I think you should change the mapDispatchToProps variable like the following:
const mapDispatchToProps = dispatch => {
return {
setText = (text) => dispatch(setText(text)),
}
}
There are two ways to achieve this
// MODIFYING DISPATHCER
const mapDispatchToProps = dispatch => {
return {
changeText: data => dispatch(setText(data)),
}
}
or
// CONNECT
export default connect(mapStateToProps, {
setText
})(Search)
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
change to
const mapDispatchToProps = dispatch => {
return {
changeText: text => dispatch(setText(text)),
}
}
And in your component use this.props.changeText function
as most of the answers suggests you can dispatch the actions or else you can simply have mapDispatchToProps an object.
mapDispatchToProps = {
setText,
dispatch
}
Your HOC connect should take care of dispatching not need to external definition
Use bindActionCreators from redux
import { bindActionCreators } from 'redux';
const mapDispatchToProps = dispatch => {
const setText = bindActionCreators(setText, dispatch);
return setText;
}
Since you're mapping your dispatch to props like this:
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
You'll need to explicitly call dispatch in your component:
class Search extends React.Component {
constructor() {
super()
}
render() {
const {dispatch, setText} = this.props;
return (
<input type="text" onChange={() => dispatch(setText("text"))} value={this.props.text}/>
)
}
}
It is easier just to map dispatch to props like this: setText = (text) => dispatch(setText(text))

Object(...) is not a function

I learning redux by following some tutorial. Here I am getting an error
Object(...) is not a function
in this line
export default connect(null, { fetchPosts } )(Posts);
of the following container
import React, { Component } from 'react';
import { connect } from 'react'
import axios from 'axios';
import { fetchPosts } from '../actions/postActions.js';
class Posts extends Component {
componentWillMount() {
this.props.fetchPosts();
}
render() {
const postItems = this.state.posts.map(el => {
return (
<div key={el.id}>
<h3>{el.title} </h3>
<p>{el.body} </p>
</div>
)
})
return (
<div>
<h1> Posts </h1>
{postItems}
</div>
);
}
}
export default connect(null, { fetchPosts } )(Posts);
Ps: I know it will also throw the map error but I am not worried about it at the moment.
Since it says Object(...) is not a function and marks the line export, the only object we have inside export is the fetchPosts which looks like this (it is an action function)
import { FETCH_POST, NEW_POST } from './type.js'
import axios from 'axios';
export const fetchPosts = () => {
return function (dispatch) {
axios.get("https://jsonplaceholder.typicode.com/posts").then((response) => dispatch({
type: FETCH_POST,
payload: response
}))
}
}
Since I am not sure the relevant code for the same, the fetchPosts dispatches an acton to the following reducer
import { FETCH_POST, NEW_POST } from '../actions/type.js'
const initialState = {
items: [],
item: {}
}
export default function(state = initialState, action) {
switch (action.type) {
case FETCH_POST:
return {
...state,
items: action.payload
}
default:
return state
}
}
Which is later combined using combineReducers in our rootreducer.js file (which is then imported to store.js where we create store)
Can Anyone tell me what I am doing wrong and How can we fix it?
React does not have a named connect export, it belongs to react-redux. So, you should import it like that:
import { connect } from "react-redux";

React Redux - this.props.actions.fetchPosts is not a function

i have issue with calling async action from my component, i think i did everything what was needed to work but seems like not, i used:
mapDispatchToProps
and inside i return
actions: bindActionCreators(fetchPosts, dispatch)
and i connect it.
After all these things, i try to call this action in my component -
this.props.actions.fetchPosts()
in result i get this error in console -
this.props.actions.fetchPosts is not a function
And i can not understand what's the problem with it as i did everything, here will be the full source:
Component
import React, { Component } from 'react';
import { Link } from 'react-router';
import styles from './Home.css';
import { fetchPosts } from '../actions/counter';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class Home extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<div className="container">
<div className="banner_animated">
<p> dadasda</p>
</div>
</div>
<div className="container-fluid">
<div className="center">
<input type="text"/>
<button className="btn-2 btn-2a btn" onClick={this.props.actions.fetchPosts()}>Button</button>
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(fetchPosts, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Action
import { FETCHING_WIN_RATES, FETCHED_WIN_RATES } from '../const';
import { firebaseDb } from './firebase';
const ref = firebaseDb.ref("win_rate");
function fetchingWinRates() {
return {
type: FETCHING_WIN_RATES
};
}
function fetchedWinRates(winRates) {
return {
type: FETCHED_WIN_RATES,
winRates
};
}
// win rate champions
export function fetchPosts() {
return dispatch => {
dispatch(fetchingWinRates());
ref.on("value", function(snapshot) {
dispatch(fetchedWinRates(snapshot));
console.log(snapshot.val());
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
}
}
Write if you need some more files to help me, thank you.
If you pass a function to bindActionCreators, it will return a function. See the documentation for bindActionCreators here (in the Returns section): http://redux.js.org/docs/api/bindActionCreators.html.
You are effectively assigning this.props.action = fetchPosts here, meaning you would call fetchPosts like so: this.props.action().
If you want to access via this.props.actions.fetchPosts, you need to do the following:
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ fetchPosts }, dispatch)
};
}
Notice the shorthand { fetchPosts } which is the same as { fetchPosts: fetchPosts }.
You don't need to use bindActionCreators http://redux.js.org/docs/api/bindActionCreators.html
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(fetchPosts(id))
})
}
and then access via this.props.onClick

Resources