Redux: Asynchronous action creator not being called - javascript

I am using redux-thunk to create an asynchronous action so that I can call an API method using 'fetch'. I have attached my redux store to my root App component like this:
import React, { Component } from 'react';
import {Provider} from 'react-redux';
import Grid from '../src/Components/Grid/Grid';
import rootReducer from './Reducer';
import './App.css';
import { createStore,applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import logger from 'redux-logger';
class App extends Component {
render() {
return (
<Provider store={createStore(rootReducer,{},applyMiddleware(ReduxThunk))}>
<div className="App">
<Grid></Grid>
</div>
</Provider>
);
}
}
export default App;
I have defined my asynchronous action creator as shown here:
import config from 'react-global-configuration';
import fetch from 'cross-fetch';
export default function textChangeActionCreator(data){
console.log('in action createor'+data)
return async function(dispatch){
console.log('fetching....')
return await fetch(data).
then((data)=>{
console.log(data);
dispatch({type:'text_change',payload:data})
}).
catch((err)=>{console.log(err)})
}
}
My problem is, when I call the above action creator from any component it gets called properly however, the second console.log('fetching....') is not showing up and the fetch() call seems to not be happening.
What am I doing wrong?
Update
Here is the component code which is calling the action creator:
class Grid extends React.PureComponent{
constructor(props)
{
super(props);
this.state={dynamicButtonText:'',seachText:''}
this.updateDynamicText=this.updateDynamicText.bind(this);
this.onTextChanged=this.onTextChanged.bind(this);
this.updateSearchText=this.updateSearchText.bind(this);
}
onTextChanged()
{
textChangeActionCreator(searchURL);
}
render()
{....}
}
const mapStateToProps=(state)=>{return state;}
export default connect(mapStateToProps,{textChangeActionCreator})(Grid);

This is usually caused by incorrect action invocation. Make sure that you're you're both calling the textChangeActionCreator() action creator, and passing the result to dispatch() in the propsToDispatch object that you pass to connect():
export default connect(mapStateToProps, dispatch => {
/* Call textChangeActionCreator() and pass the result to dispatch() */
return {
textChange : (data) => dispatch(textChangeActionCreator(data))
}
})(YourComponent);
Update
Just reviewing your code, it seems you're calling the action creator from onTextChanged() directly. You need to call it via the component's props like so:
onTextChanged()
{
this.props.textChangeActionCreator(searchURL);
}

Try something like this:
const textChangeActionCreator = (data) => {
console.log(`in action createor ${data}`);
return (dispatch) => {
console.log('fetching...');
return fetch(data)
.then((data)=>{
console.log(data);
dispatch({ type:'text_change', payload:data });
})
.catch((err)=>{ console.log(err); });
};
};
You don't need await or async, because you are returning a promise, you should await where you are calling this function.
Also, you are using currying here, so you have to call something like:
const fetchedData = await textChangeActionCreator(data)(dispatch);

Related

Accessing redux store / functions from Navigation.js inside a tabBar

I'm struggling a bit accessing some redux functions from Navigation.js. I want to be able to call a redux function when I press a tab on a tabBar. To do this, I am calling
Navigation.js:
tabBarOnPress: () => {
console.warn("I am here")
store.dispatch({ type: nameOfMyReduxFunction, })}
I correspondingly have, in a separate reducer / redux file:
export const nameOfMyReduxFunction = () => {
console.warn("nameOfMyReduxFunction being called")
return dispatch => {
dispatch({ type: "NAME_OF_MY_REDUX_FUNCTION" });
};
};
When I run the app, I get "I am here". However, the redux function appears to never run, as "nameOfMyReduxFunction being called" is never printed. Could anyone give me some tips on why this is?
You can use connect from react-redux package.
So first, we import the nameOfMyReduxFunction from that file and dispatch it from a function (which is named here as mapDispatchToProps).
While exporting the component we use the connect to connect the mapDispatchToProps function with the component like export default connect(null, mapDispatchToProps)(Navigation.js).
You can call the nameOfMyReduxFunction within the component from prop like props.nameOfMyReduxFunction.
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { nameOfMyReduxFunction } from 'thatReduxFile';
...
const mapDispatchToProps = (dispatch) => {
return {
nameOfMyReduxFunction: bindActionCreators(
nameOfMyReduxFunction,
dispatch
),
};
};
...
tabBarOnPress: () => {
console.warn("I am here")
store.dispatch({ type: props.nameOfMyReduxFunction, }) //Access nameOfMyReduxFunction from props
}
...
export default connect(null, mapDispatchToProps)(Navigation.js)
For more info refer connect, mapDispatchToProps. bindactioncreators

Async with redux works? But still gives error, should I ignore it?

I am using a mix of laravel, react.
Attempting to implement the redux-thunk middleware.
I am having issues with async calls.
I would like to use jquery for ajax (which successfully retrieve the API data, but I am getting an error which reads,
"Error: dispatch is not a function", meaning I cannot make any changes to the store. From what I understand dispatch and GetState is passed through the thunk middle-ware. Correct?So why am I not able to use the function?
It also gives me an error which reads: "Error: Actions may not have an undefined "type" property. Have you misspelled a constant?"
Another issue that arises, after trying to deal with the above issue is: "Error: Actions must be plain objects. Use custom middleware for async actions."
I have read many similar questions but I still cannot seem to get it to work.
"Actions must be plain objects. Use custom middleware for async actions."
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import FilterBar from './SideBar/FilterBar';
import Store from '../redux/store/mainStore';
import { REMOVE_ATTRIBUTE_FILTER,ADD_ATTRIBUTE_TO_FILTER, removeAttribute } from '../redux/actions/actions';
Store.subscribe(()=>{
console.log("store changes", Store.getState())
})
console.log(Store.getState());
Store.dispatch({
type:ADD_ATTRIBUTE_TO_FILTER,
payload:{'make':23}
})
if (document.getElementById('InventoryDisplay')) {
ReactDOM.render(
<Provider store={Store}>
<FilterBar/>
</Provider>
,document.getElementById('FilterBar'));
}
mainstore.js
```
import { createStore,applyMiddleware,combineReducers,compose } from 'redux';
import thunk from 'redux-thunk';
import {inventoryFilter,availableAttributes} from '../reducers/reducer';
const Store = createStore(
///combine imported reducers
combineReducers({
activeFilter:inventoryFilter,
availableAttributes:availableAttributes
},
///initilize store
{},
applyMiddleware(thunk)
));
export default Store;
```
actions.js what is relevant
```
///first case
const getAttributes2 = (dispatch) => {
return(
$.ajax('/getFilteredAttributes/', {
type: 'GET',
dataType : 'json'
}).done(response => {
dispatch(addAttribute("make",32));
}).fail((xhr, status, error) => {
console.log("failed");
})
)
};
///second case
const getAttributes = (dispatch) => {
return ()=>{}
}
export {
ADD_ATTRIBUTE_TO_FILTER,addAttribute,
REMOVE_ATTRIBUTE_FILTER,removeAttribute,
GET_INVENTORY,getInventory,
GET_AVAILABLE_ATTRIBUTES,getAttributes,
}
```
component connect to store that dispatch action
```
import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import * as ActionCreators from '../../../redux/actions/actions';
class DropDownList extends React.Component{
componentDidMount(){
this.props.addAttributes("make",32)
this.props.getAttributes()
this.props.removeAttributes("make",32)
}
render(){
return(
<div>
</div>
)
}
}
function mapStatesToProps(state){
return{
activeFilters:state.activeFilter,
availableAttributes:state.availableAttributes
}
};
const mapDispatchToProps = dispatch => {
return {
addAttributes: (type,value) => {
dispatch(ActionCreators.addAttribute(type,value))
},
removeAttributes: (type,value) => {
dispatch(ActionCreators.removeAttribute(type,value))
},
getAttributes: () => {
dispatch(ActionCreators.getAttributes())
}
}
}
DropDownList.propTypes = {
availableAttributes: PropTypes.object.isRequired,
activeFilters: PropTypes.object.isRequired,
}
export default connect(mapStatesToProps,mapDispatchToProps)(DropDownList)
```
My solution for case one for the second error was to just place the ajax function call into in object that contains the "type" property. Like such
return (
{
type: "Something",
$.ajax('/getFilteredAttributes/', {
type: 'GET',
dataType: 'json'
}).done(response => {
dispatch(addAttribute("make", 32));
}).fail((xhr, status, error) => {
console.log("failed");
})
})
The ajax call is made, but dispatch still isn't available, I am lost and looking for the best solution? Maybe I am overthinking something or missed a minor detail. I have tried other solutions but none has worked for me.
Please Help.
It's pretty difficult to know exactly what's going on, but I definitely wouldn't recommend ignoring / hacking round an error you're getting using a common feature of a popular library. It's important to get your implementation right for simplicity moving forward.
It looks to me like the way you are using thunk is a little odd.
The function that is returned by the action you dispatch has dispatch and getState as arguments:
In your case your thunk action might look like this
In your actions.js:
export function getAttributes2(){
return function(dispatch, getState){
// you could dispatch a fetching action here before requesting!
return $.ajax('/getFilteredAttributes/', {type: 'GET', dataType: 'json'})
.done(response => dispatch(saveTheResponseAction(response)))
.fail((xhr, status, error) => console.log("failed"))
}
Mapping that thunk function to your props:
import {getAttributes2} from '../../../redux/actions/actions';
const mapDispatchToProps = dispatch => {
return {
getAttributes2: () => dispatch(getAttributes2()),
}
}
This way you can dispatch an action from the .done part of your api call with the result in it, you can respond to errors, you can even dispatch an action before returning your api call to let redux know you've requested but not yet received the data, allowing you to do all kinds of loading states.
I hope this helps, let me know :)

storing data from props of reducers

I have a function in actions which makes an api call.
index.js of actions
export const GET_QUERY_LIST='GET_QUERY_LIST';
export const loadData=()=>{
return(dispatch)=>{
axios({
method:'GET',
url:Constants.URLConst+"/Query,
headers:Constants.headers
}).then((response)=>{
return dispatch({
type:GET_QUERY_LIST,
response
});
}).catch((e)=>{
console.log(e);
})
}
}
The same function I am using in Reducers like this-
index.js of reducers
function getQueryData(state=[],action){
switch(action.type){
case GET_QUERY_LIST:
return Object.assign({},state,{
result_get_query:action.response
})
default:
return state
}
}
const data=combineReducers({
getQueryData
})
export default data;
I am using this reducer function in my js file, say home.js as follows
import React,{Component} from 'react';
import './App.css';
import {loadData} from './actions';
import {connect} from 'react-redux';
import Header from './Header.js';
// import './Home.css';
export class Home extends Component{
constructor(props){
super(props);
this.state={
querylist:[]
}
this.handleChange=this.handleChange.bind(this);
}
componentDidMount(){
this.props.loadData();
}
handleChange(){
this.setState({
querylist:this.props.resultCame
})
}
render(){
console.log("home.js",this.state.querylist);
//this.props.resultCame.resultMeta.data.ProfileData.UserId
if(this.props.resultCame.resultMeta){
return(
<div>
<Header/>
<div>{this.handleChange()}</div>
</div>
)
}else{
return null;
}
}
}
const mapStateToProps=(state)=>{
return{
resultCame:state.getQueryData
}
}
export default connect(mapStateToProps,{
loadData:loadData
})(Home);
I am storing the data in resultCame variable. In the render function if I do,
console.log(this.props.resultCame)
then the result comes which means the api is getting called properly, but I want to store the result in a state variable.So in componentDidMount() after calling the loadData(), I am setting the state in querylist.
But this.state.querylist is coming empty which means the data is not getting set.
How to set the data properly?
You are using axios which works asynchronously. This means that it waits for the response to arrive from the API before doing anything. When you use ComponentDidMount() and call the action and then immediately call setState, the probable cause of this.state.queryset being empty is because it is assigned before axios has received anything from API call. You will have to setState after receiving response from axios, not just running it.
You should use ComponentWillReceiveProps(nextProps) to update the state as the reply of the axios success call will come in the updated next props as you will be receiving them as the redux store on dispatch of
type:GET_QUERY_LIST, payload.

react ajax call is undefined at first

I'm using Axios to make an AJAX call and the data returns undefined and then it consoles the array after a few seconds. I've tried componentDidMount and componentWillMount. I've tried making a constructor with initial state as the props. getInitial state is deprecated unless using React.createClass.
Here's my code, anything helps!
actions/index.js
import axios from 'axios';
import { FETCH_STRAINS } from './types';
const ROOT_URL = `https://www.cannabisreports.com/api/v1.0/strains?sort=name&page=3`;
export function fetchStrains() {
return dispatch => {
axios.get(ROOT_URL)
.then(response => {
dispatch({
type: FETCH_STRAINS,
payload: response.data.data
})
})
.catch( error => console.log(error));
}
}
reducer/index.js
import { FETCH_STRAINS } from '../actions/types';
import initialState from './initialState';
export default function(state = initialState.strains, action) {
switch(action.type) {
case FETCH_STRAINS:
return { ...state, strains: action.payload };
default:
return state;
}
}
app.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from './actions';
import './App.css';
class App extends Component {
componentWillMount() {
this.props.fetchStrains();
}
render() {
return (
<div className="App">
{this.props.strains === undefined ? console.log("this is undefined") : console.log(this.props.strains)}
</div>
);
}
}
function mapStateToProps( state ) {
return { strains: state.strains.strains }
}
export default connect(mapStateToProps, actions)(App);
The issue you're facing isn't because your code is wrong. From a quick glance it looks like you're doing it right.
The problem is that your app exists and is showing before you have all the data ready. The axios call takes a very long time to complete. Until it's done, your app is showing something to the user, whether you like it or not.
So between startup and data arrival, strains is going to be undefined. You'll have to decide what to show the user while they're waiting. A common solution is a spinner.
You need to use async actions & need to import thunk-middleware while you combine your reducers.
export function fetchStrains() {
// Thunk middleware knows how to handle functions.
// It passes the dispatch method as an argument to the function,
// thus making it able to dispatch actions itself.
return function (dispatch) {
// First dispatch: the app state is updated to inform
// that the API call is starting.
// The function called by the thunk middleware can return a value,
// that is passed on as the return value of the dispatch method.
// In this case, we return a promise to wait for.
// This is not required by thunk middleware, but it is convenient for us.
axios.get(ROOT_URL)
.then(response => {
dispatch({
type: FETCH_STRAINS,
payload: response.data.data
})
})
.catch( error => console.log(error));
}
}

React/Redux can't iterate over array in state

I'm in the process of learning React/Redux and I've run into an issue while converting a single page web app of mine to the framework/paradigm. What i'm trying to do is let the initial state of the web app have an array that is to be populated by objects from an API request, this array is called "makes". I want to do this so I can display "makes" from the API on the first page of the website upon it loading. This can be seen in the index.js file below:
import App from './App';
import './index.css';
import configureStore from './redux/store'
import { Provider } from 'react-redux'
let makesUrl = 'the url to the API'
let cached = cache.get(makesUrl)
let makes = []
// Future cache setup.
if(!cached) {
console.log("NOT CACHED")
}
else {
console.log("CACHED")
}
// Get the makes via API.
fetch(makesUrl).then((response) => {
// Pass JSON to the next 'then'
return response.json()
}).then((json) => {
json.makes.forEach((make) => {
makes.push(make)
})
})
let initialState = {
grids: [],
makes: makes
}
let store = configureStore(initialState)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
The state and dispatch are mapped to the props and passed down to the components that need them in my App.js file as such:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import './App.css'
import Head from './components/Head'
import Middle from './components/Middle'
import Foot from './components/Foot'
import actions from './redux/actions'
class App extends Component {
render() {
return (
<div className="App">
<div>
<Head />
<div className="middle container">
<Middle actions={this.props.actions} makes={this.props.makes}/>
</div>
<Foot />
</div>
</div>
);
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
At all points, in the chrome dev tools, I can see that the API call was successful and the state is shown to have makes: Array[62] with 62 objects inside, however if I console log the length of the array in the component that these makes are passed down to as seen below, it says the length is 0, and each index of the array is undefinded.
import React, { Component } from 'react'
class MakeButtons extends Component {
handleClick(event) {
event.preventDefault()
console.log("CLICK")
}
render() {
return(
<div>
{
console.log(this.props.makes.length)
}
</div>
)
}
}
export default MakeButtons
This is essentially what I've been trying to figure out for the past couple hours, so I can use the forEach or map function to return links/buttons for each of the objects in the array, however at the moment this does not work, despite dev tools showing the state to be normal. Any help/explanations would be greatly appreciated!
So you really just need to set up an action/reducer for your init, then you can call it in componentWillMount or componentDidMount because they are only called once upon loading your app.
In the way you are doing it now you have a fetch and an app using the data from the fetch that is not waiting for the async call to finish before it starts the app.
You just want to create your init action, so your action creator would be something like :
import * as services from './services';
function initMyAppDispatcher(type, data, status) {
return {
type,
data,
status
};
}
export function initMyApp(dispatch, makesUrl) {
dispatch(initMyAppDispatcher(actions.INIT_APP, {}, 'PENDING');
return services.myInitCall(makesUrl)
.then((data) =>
dispatch(initMyAppDispatcher(actions.INIT_APP, data, 'SUCCESS'),
(error) =>
dispatch(initMyAppDispatcher(actions.INIT_APP, error, 'FAILURE'),
)
.catch(yourErrorHandling);
}
Services.myInitCall is however you want to implement it, just make sure you export it back as a promise. In your case you can replace that line with fetch(makesUrl) as long as you have access to it there. Then having it set up like this, you can set your reducers like so :
case actions.INIT_APP:
if (action.status) {
switch (action.status) {
case PENDING:
//you can use this to create a "loading" state like a spinner or whatever
return state;
case SUCCESS:
// note: this is immutablejs syntax, use whatever you prefer
return state.set('makes', action.data);
case FAILURE:
return state;
default:
return state;
}
}
return state;
One thing to note is I have dispatch in my action creators because I use mapDispatchToProps in place of mapToProps. So your container looks something like this :
import * as actionCreators from './action-creators';
function mapStateToProps(state) {
return {
makes: state.get('makes')
};
}
function mapDispatchToProps(dispatch, ownProps) {
return {
initMyApp: actionCreators.initMyApp.bind(null, dispatch)
};
}
export default function(component = Component) {
return connect(mapStateToProps, mapDispatchToProps)(component);
}
then in your component componentWillMount or componentDidMount, pass in and call your init function
componentDidMount() {
this.props.initMyApp();
}

Categories