react ajax call is undefined at first - javascript

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

Related

React-Redux: Action Dispatch on button click not working

On click of Button my Action is not getting dispatched. Below are all the files viz - action, reducer, root reducer, configSTore, Index and Component.
Please help me why my action is not getting dispatched on button click
Actions.js
import axios from 'axios';
const requestURL='JSONUrl';
let responseData = '';
export function fetchRequests() {
axios.get(requestURL)
.then((res) => {
responseData = res;
});
return responseData;
}
export const fetchDataAction = () => {
return {
type: 'FETCH_DATA',
data: fetchRequests()
};
}
export function fetchDataSuccessAction(err) {
return {
type: 'FETCH_DATA_SUCCESS',
err
};
}
export function fetchDataErrorAction(err) {
return {
type: 'FETCH_DATA_ERROR',
err
};
}
Reducer.js
export function fetchDataReducer(state = [], action) {
switch(action.type) {
case 'FETCH_DATA':
return action.data;
default: return state;
}
}
export function fetchDataSuccessReducer(state = [], action) {
switch(action.type) {
case 'FETCH_DATA_SUCCESS':
return action.err;
default: return state;
}
}
export function fetchDataErrorReducer(state = [], action) {
switch(action.type) {
case 'FETCH_DATA_ERROR':
return action.err;
default: return state;
}
}
RootReducer
import {combineReducers} from 'redux';
import { fetchDataAction, fetchDataSuccessAction, fetchDataErrorAction}
from '../actions/fetchData';
export default combineReducers({
fetchDataAction,
fetchDataSuccessAction,
fetchDataErrorAction
});
configStore.js
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers/rootReducer";
import thunk from 'redux-thunk';
export default function configureStore() {
const enhance = applyMiddleware(thunk);
return createStore(
rootReducer,
enhance
);
}
INdex.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import reportWebVitals from './reportWebVitals';
import Header from './App';
import configureStore from './store/configureSTore';
import {CounterApp} from './counter';
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={configureStore()}>
<Header favcol="yellow"/>
<CounterApp />
</Provider>,
document.getElementById('root')
);
reportWebVitals();
My Component File
import React, { useState } from 'react';
import { fetchDataAction, fetchRequests } from './actions/fetchData';
import { connect } from 'react-redux';
export class CounterApp extends React.Component {
constructor(props) {
super(props);
}
btnClick = () => {
return this.props.fetchDataAction;
}
render() {
return (
<div className="App">
<h1>Hello</h1>
<div>
<h1>Fetch Data click below</h1>
<button onClick={() => this.props.fetchDataAction}>
Fetch Data
</button>
{this.props.datafetchedFromApi}
</div>
</div>
)
}
}
const mapStateToProps = state => ({
datafetchedFromApi: state.data
});
const mapDispatchToProps = (dispatch) => ({
fetchDataAction: () => dispatch(fetchDataAction())
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterApp);
On click of Button my Action is not getting dispatched. Below are all the files viz - action, reducer, root reducer, configSTore, Index and Component.
Please help me why my action is not getting dispatched on button click
The answer got too long, so first part is if you wanna fully understand why things went south. If you just want your problem solved so you can finally go pee, the second part is for you :).
First Part (Basically what asynchronous programming in JavaScript is, so any questions to what are asynchronous tasks in JS can be referred to this answer.)
Okay a couple of problems detected here. As others have pointed out make sure all the paths for imports are correct. Now assuming they are all correct, here's what you need to do to solve your problem.
First let's take a look at the top of your component file:
import React, { useState } from 'react';
import { fetchDataAction, fetchRequests } from './actions/fetchData';
import { connect } from 'react-redux';
...
And then the block where you have called fetchDataAction:
...
<button onClick={() => this.props.fetchDataAction}>
Fetch Data
</button>
...
Here what you have done is this.props.fetchDataAction, I don't see you passing fetchDataAction as a prop to this component, so it's most probably undefined that's why you get an error TypeError: this.props.fetchDataAction is not a function because of course undefined is not a function.
This was the first mistake I noticed.
Now, moving on to the second one. I'm gonna start with this
Dispatching actions in redux is synchronous.
So you cannot do something like the following:
export default function SomeComponent(props) {
const fetchAction = () => {
let payload;
//wait for 5 seconds for the async task(the setTimeout below is an async task) to populate the payload with data.
setTimeout(() => payload = "This is my data", 5000);
//then return the action object
return {
type: 'Data',
payload,
};
}
const handleClick = () => {
dispatch(fetchAction());
}
return (<>
<Button onClick={handleClick} />
</>);
}
The above will not throw any errors, but will certainly not do what I want it to do. The above will dispatch the following action object:
{
type: 'Data',
payload: undefined
}
which ofcourse is not what I want the payload to be.
And that's exactly what you're doing. Take a look at your fetchDataAction and fetchRequests functions:
...
let responseData = '';
export function fetchRequests() {
axios.get(requestURL)
.then((res) => {
responseData = res;
});
return responseData;
}
export const fetchDataAction = () => {
return {
type: 'FETCH_DATA',
data: fetchRequests()
};
}
...
Now I'll compare with the example I've given above:
Here your responseData is analogous to my payload
Your fetchRequests function is analogous to my setTimeout
Looks familiar? I'm sure by now it does. Plain simple answer as to why it doesn't work is that you're performing an async task, in your case you're making a network request with axios.get(requestUrl)...
Network requests are async(now if you don't know what async things are, check out https://javascript.info/callbacks which gives you idea about what those are. Also check out a video on it by TheNetNinja https://www.youtube.com/watch?v=ZcQyJ-gxke0 ), in simple words, \network requests take some time to get finished(just like setTimeout).
So the axios.get request takes some time to get the response back from the server. Now the other tasks(below it) won't wait for that request to get completed, instead js will execute those tasks immediately without waiting for the response.
I know this answer is getting too long. But I want you to understand, because trust me I have made the same mistakes before :).
So in your fetchRequests function:
export function fetchRequests() {
axios.get(requestURL) --- (1)
.then((res) => {
responseData = res; --- (2)
});
return responseData; --- (3)
}
In line 1, you start an async task. Remember the function inside then block will execute only after sometime. So the responseData is still undefined. Instead of line (2), line (3) will execute first, cause as I told you earlier, js won't wait for the response from the server(the technical wording is 'the thread doesn't get blocked by network request'.) So basically you're returning undefined from this function.
Also see this video by JavaBrains. He uses an excellent analogy to understand async tasks in js, and you might also learn about what the event loop is and about single threaded-ness of javascript.
https://www.youtube.com/watch?v=EI7sN1dDwcY
Now the Second part (I really wanna go pee):
Replace(I've pointed out where I have made changes)
Your component file with this.
import React, { useState } from 'react';
import { fetchDataAction, fetchRequests } from './actions/fetchData';
import { connect } from 'react-redux';
export class CounterApp extends React.Component {
constructor(props) {
super(props);
}
btnClick = () => {
return this.props.fetchDataAction;
}
render() {
return (
<div className="App">
<h1>Hello</h1>
<div>
<h1>Fetch Data click below</h1>
<button onClick={() => fetchDataAction()}> //this is the only change here
Fetch Data
</button>
{this.props.datafetchedFromApi}
</div>
</div>
)
}
}
const mapStateToProps = state => ({
datafetchedFromApi: state.data
});
const mapDispatchToProps = (dispatch) => ({
fetchDataAction: () => dispatch(fetchDataAction())
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterApp);
And then in your action.js file:
import axios from 'axios';
const requestURL='JSONUrl';
let responseData = '';
export function fetchRequests() {
return axios.get(requestURL) //change is here
//removed return responseData and the 'then' block
}
export const fetchDataAction = () => {
return dispatch => { //almost whole of this function is changed and that's it
fetchRequests().then((res) => {
responseData = res;
dispatch({
type: 'FETCH_DATA',
data: responseData
})
});
};
}
export function fetchDataSuccessAction(err) {
return {
type: 'FETCH_DATA_SUCCESS',
err
};
}
export function fetchDataErrorAction(err) {
return {
type: 'FETCH_DATA_ERROR',
err
};
}
Now it should work. Tell me more if it doesn't. Remember this answer is assuming that you have all your functions imported properly into your files. I'll be making edits if this doesn't answer your question.
So the answer was to use 'thunk' - an official "async function middleware" for redux.
Also see this to learn more about handling 'async actions' and also using redux thunk:
https://redux.js.org/tutorials/fundamentals/part-6-async-logic
https://www.npmjs.com/package/redux-thunk
Try this
<button onClick={() => this.props.fetchDataAction()}>
Also if you need to dispatch the url from your components you can do this way
<button onClick={() => this.props.fetchDataAction(url)}>
const mapDispatchToProps = (dispatch) => {
return {
fetchDataAction: (url) => dispatch(fetchRequests(url))
};
};
And in Action.js
export function fetchRequests(url) {
axios.get(url)
.then((res) => {
responseData = res;
});
return responseData;
}
<button onClick={() => this.props.fetchDataAction()}>
<button onClick={() => this.props.fetchDataAction}>
Here you are trying to return the function definition alone ,not call the fetchDataAction onclick use onClick={() => this.props.fetchDataAction()} or pass a separate handler for the onclick as good practice.
For the other issue you have mentioned TypeError: this.props.fetchDataAction is not a function is because of the curly brackets used while importing fetchRequests
Remove the curly brackets
import fetchRequests from "./actions/fetchData";
This should resolve your issue

Redux: Asynchronous action creator not being called

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

How can I refactor this ASYNC call in my react component to make it more readable?

I want my component to fetch an array of objects from the server. Each object is a message with author, body and date. I then want to render these messages in my react component.
My react component currently fetches data from the server before mounting. It will then store this message list in the redux state.|
I'm sure there's a better way of writing this code.
1. Can I place the fetch request in either the Action or Reducer file?
2. Can I write a function in the component to make the async call?
import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from '../actions/actions_index.js';
class MessageList extends Component {
constructor(props) {
super(props)
}
componentWillMount() {
fetch('https://wagon-chat.herokuapp.com/general/messages')
.then(response => response.json(),
error => console.log('An error occured receiving messages', error))
.then((data) => {
this.props.fetchMessages(data.messages);
});
}
render() {
return (
<div className="message-list">
{this.props.messageList.map( (message, index) => { return <Message key={index} message={message}/> })}
</div>
)
}
}
function mapStateToProps(state) {
return {
messageList: state.messageList
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{ fetchMessages: fetchMessages },
dispatch
)
}
export default connect(mapStateToProps, mapDispatchToProps)(MessageList);
Can I place the fetch request in either the Action or Reducer file?
The fetch request should be placed in action creator. Where the retrieved data will be dispatched to reducer later to manipulate the data, and lastly update the store to show on UI. Here's simple flow for most of react-redux app.
UI -> Action creator (calling request, saga etc..) -> reducer -> store -> UI
Can I write a function in the component to make the async call?
Yes, this should be called action creator, and you can see actions.js below for more reference.
I think you can safely follow this sample pattern where most tutorials out there apply. I'm assuming all files listed here are in the same directory.
constant.js
const MESSAGE_FETCH__SUCCESS = 'MESSAGE/FETCH__SUCCESS'
const MESSAGE_FETCH__ERROR = 'MESSAGE/FETCH__ERROR'
export {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
}
actions.js
import {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
} from './constant';
const fetchMessageError = () => ({
type: MESSAGE_FETCH__ERROR
})
const fetchMessageSuccess = data => ({
type: MESSAGE_FETCH__SUCCESS,
payload: data
})
const fetchMessages = () => {
const data = fetch(...);
// if error
if (data.error)
fetchMessageError();
else fetchMessageSuccess(data.data);
}
export {
fetchMessages
}
reducers.js
import {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
} from './constant';
const INIT_STATE = {
messageList: []
}
export default function( state = INIT_STATE, action ) {
switch(action.type) {
case MESSAGE_FETCH__SUCCESS:
return {
...state,
messageList: action.payload
}
case MESSAGE_FETCH__ERROR:
// Do whatever you want here for an error case
return {
...state
}
default:
return state;
}
}
index.js
Please read the comment I noted
import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from './actions';
class MessageList extends Component {
/* If you don't do anything in the constructor, it's okay to remove calling `constructor(props)`
*/
//constructor(props) {
// super(props)
//}
// I usually put this async call in `componentDidMount` method
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div className="message-list">
{
/* Each message should have an unique id so they can be used
for `key` index. Do not use `index` as an value to `key`.
See this useful link for more reference: https://stackoverflow.com/questions/28329382/understanding-unique-keys-for-array-children-in-react-js
*/
this.props.messageList.map( message => <Message key={message.id} message={message}/> )
}
</div>
)
}
}
function mapStateToProps(state) {
return {
messageList: state.messageList
}
}
export default connect(mapStateToProps, {
fetchMessages
})(MessageList);
You could use redux-thunk in an action called getMessages.
So:
(The double arrow func, is to return an action, see redux-thunk)
const getMessages = ()=>(dispatch, getState)=>{
fetch('https://wagon-chat.herokuapp.com/general/messages')
.then(response => response.json(),
error => dispatch(['error', error]))
.then((data) => {
dispatch(data);
})
}
Then you've successfully reduced your component to:
componentWillMount(){
this.props.getMessages()
}
I think #Duc_Hong answered the question.
And in my opinion, I suggest using the side-effect middle-ware to make AJAX call more structured, so that we could handle more complicated scenarios (e.g. cancel the ajax request, multiple request in the same time) and make it more testable.
Here's the code snippet using Redux Saga
// Actions.js
const FOO_FETCH_START = 'FOO\FETCH_START'
function action(type, payload={}) {
return {type, payload};
}
export const startFetch = () => action{FOO_FETCH_START, payload);
// reducer.js
export const foo = (state = {status: 'loading'}, action) => {
switch (action.type) {
case FOO_FETCH_STARTED: {
return _.assign({}, state, {status: 'start fetching', foo: null});
}
case FOO_FETCH_SUCCESS: {
return _.assign({}, state, {status: 'success', foo: action.data});
}
......
}
};
Can I place the fetch request in either the Action or Reducer file?
// Saga.js, I put the ajax call (fetch, axios whatever you want) here.
export function* fetchFoo() {
const response = yield call(fetch, url);
yield put({type: FOO_FETCH_SUCCESS, reponse.data});
}
// This function will be used in `rootSaga()`, it's a listener for the action FOO_FETCH_START
export function* fooSagas() {
yield takeEvery(FOO_FETCH_START, fetchFoo);
}
Can I write a function in the component to make the async call?
// React component, I trigger the fetch by an action creation in componentDidMount
class Foo extends React.Component {
componentDidMount() {
this.props.startFetch();
}
render() {
<div>
{this.props.foo.data ? this.props.foo.data : 'Loading....'}
<div>
}
}
const mapStateToProps = (state) => ({foo: state.foo});
const mapDispatchToProps = { startFetch }
export default connect(mapStateToProps, mapDispatchToProps) (Foo);
//client.js, link up saga, redux, and React Component
const render = App => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
combinedReducers,
initialState,
composeEnhancers(applyMiddleware(sagaMiddleware))
);
store.runSaga(rootSaga);
return ReactDOM.hydrate(
<ReduxProvider store={store}>
<BrowserRouter><AppContainer><App/></AppContainer></BrowserRouter>
</ReduxProvider>,
document.getElementById('root')
);
}

ReactJs Redux Data Fetch

I just started to work on React Js and Redux-Thunk. Currently, I am trying to fetch data from a url using redux-thunk. I got data successfully but the issue is that it renders undefined data twice, then it gives me the desired data in props.
Here is my code.
In Actions
index.js
function getData() {
return {
type: 'FETCH'
}
}
function getSuccess(data) {
return {
type: 'FETCH_SUCCESS',
payload: data
}
}
function getFailed(err) {
return {
type: 'FAILED',
payload: err
}
}
export function fetchData() {
const thunk = async function thunk(dispatch) {
try {
dispatch(getData());
const body = await fetch("http://jsonplaceholder.typicode.com/users")
const res = await body.json();
console.log("Thunk", res);
dispatch(getSuccess(res));
}
catch(err) {
dispatch(getFailed(err));
}
}
return thunk;
}
In Reducers fetch.js
const initialState = {
state: []
}
export default function(state=initialState , actions) {
switch(actions.type) {
case 'FETCH':
return {
...state
}
case 'FETCH_SUCCESS':
return {
...state,
data: actions.payload
}
case 'FAILED':
return {
...state
}
default:
return state;
}
}
Reducers Index.js
import fetch from './fetch';
import { combineReducers } from 'redux';
const rootReducer = combineReducers ({
fetch
});
export default rootReducer;
App.js
import React, { Component } from 'react';
import './App.css';
import Main from './component/Main';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
console.log("STore", store.getState());
class App extends Component {
render() {
return (
<Provider store={store}>
<Main/>
</Provider>
);
}
}
export default App;
Main.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
class Main extends Component {
componentWillMount() {
const { fetchData } = this.props
fetchData();
}
render() {
let mydata = this.props.data.data;
console.log("Data .....<>", mydata);
return(
<div>
Main
</div>
);
}
}
const mapStateToProps =(state)=> {
return {
data: state.fetch
}
}
export default connect(mapStateToProps, {fetchData: actions.fetchData})(Main);
Output Screen Shot
Please let me know what i am doing wrong. Any help will be appreciated.
Thanks
This behavior is correct. You're doing everything in the normal way, except calling async operations in componentWillMount method instead of componentDidMount.
Read more here about it.
You need to know, that when you are using componentDidMount - you are handle this in a safe way due to commit phase in component lifecycle and it means that your request will be guaranteed trigger once instead of possible several times, which can be in render phase.
See here the visual diagram to understand this more.
Regarding several renderings - it can be explained easily.
First time, when your component is mounting you are calling asynchronous operation and it needs more time to load data than for component mounting. Thats why you are accessing data property of your initialState (which is empty array), and getting undefined.
When you response is ready and actions is being dispatched, redux trigger re-render of connected components with new state.
If you want to make your async-await syntax works you should make lifecycle with async keyword as well and await your request inside.
NOTE: It's safe, but in several cases it might cause unexpected bugs, so keep in mind. Nevertheless, I don't recommend to use it in a such way. Read about this topic in the another thread at SO.
I advice you to create some isLoading boolean status in your reducer and keep track whether data is loaded or not.
Good luck! Hope it will helps!
There is nothing wrong with your code, but there is one unnecessary action.
Why do you see two times undefined and then you see your data?
First one is coming from the initial render. You are making an async dispatching in your componentWillMount so render does not wait for it, then try to log your data. At that time it is undefined.
Your fetchData dispatches two actions: getData and getSuccess. You see second undefined because of getData action since it returns current state which props.data.data undefined at that time again.
You see the data since getSuccess updates your state now.
If you want to test this comment out getData in your fetchData function, just use getSuccess and change your console.log in the render like that:
mydata && console.log("Data .....<>", mydata);
I think getData action is unnecessary here, it does not do anything beside returning the current state. Also, try to use componentDidMount instead of componentWillMount since it will be deprecated in version 17.

response undefined using then for async redux api call?

How to properly redirect user using the response of the api call in redux? I need the resp after axios's then but I got undefined, although I've returned the thunk in my action
//jobForm.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createJob } from '~/actions/jobAction'
import { getUserId } from '~/utils'
import moment from 'moment'
#connect(state=>state.job,{createJob})
class Form extends Component {
handleSubmitForm = () => {
this.props.createJob({formData})
.then(resp => console.log(resp)) //undefined?)
}
//etc..
}
export default Form
//action
export function createJob(params) {
return dispatch=>{
dispatch({type: CREATING_JOB})
return axios.post(`/job/create`, {...params})
.then(res=>{
if(res.status===200 && res.data.status===1){
dispatch({
type: CREATE_JOB,
payload: res.data.data
})
}
})
.catch(res => {
dispatch(errorMsg(res.data.msg))
})
}
}
I can pass my payload to reducer but I need a response's id to redirect the user to a created job page.
You're not returning anything after processing the API call, which is why the promise resolves to "undefined". For the promise to resolve with data, you'll need to return the id after dispatching the action. See below.
export function createJob(params) {
return dispatch=>{
dispatch({type: CREATING_JOB})
return axios.post(`/job/create`, {...params})
.then(res=>{
if(res.status===200 && res.data.status===1){
dispatch({
type: CREATE_JOB,
payload: res.data.data
});
// RETURN ID AFTER DISPATCHING ACTION
return res.data.data
}
})
.catch(res => {
dispatch(errorMsg(res.data.msg))
})
}
}
An alternative approach, that is arguably more inline with the flux one-way data flow paradigm would be to perform the redirect based on a change in the redux state rather than completion of the action.
You could use componentWillReceiveProps to determine if the new job has been created, if so, redirect
componentWillReceiveProps(nextProps) {
// use nextProps to determine if the new job has been added
// to the job state
// ...
const isNewJobAdded = nextProps.job.includes(...)
if (isNewJobAdded) {
// perform redirect
...
}
}

Categories