I am trying call ajax in react using redux-thunk and axios .I want to get data from json file
simple way (on button click call like this)
axios.get('data.json').then((data)=>{
console.log(data);
})
But I want to use redux-thunk.In other words I need subscribe in component which will be dispatch using thunk
can we use thunk here ??
here is my code
https://plnkr.co/edit/bcGI7cHjWVtlaMBil3kj?p=preview
const thunk = ReduxThunk.default;
const abc= (state={},action) => {
console.log('in redux', action.type)
switch(action.type){
case 'GET_DATA':
return dispatch =>{
return axios.get('data.json');
};
default :
return state;
}
}
const {createStore,bindActionCreators ,applyMiddleware } =Redux;
const {Provider,connect} =ReactRedux;
const store = createStore(abc,
applyMiddleware(thunk)
);
class First extends React.Component {
constructor (props){
super(props);
}
getDate(){
this.props.getData();
// axios.get('data.json').then((data)=>{
// console.log(data);
// })
}
render(){
return (
<div>
<button onClick={this.getDate.bind(this)}>GET DATA</button>
</div>
)
}
}
const actions = {
getData: () => {
return {
type: 'GET_DATA',
}
}
};
const AppContainer = connect(
function mapStateToProps(state) {
return {
digit: state
};
},
function mapDispatchToProps(dispatch) {
return bindActionCreators(actions, dispatch);
}
)(First);
ReactDOM.render(
<Provider store={store}>
<AppContainer/>
</Provider>
,document.getElementById('root'))
There is a good tutorial on egghead.io about this ... you might want to check it out.
https://egghead.io/lessons/javascript-redux-dispatching-actions-asynchronously-with-thunks
I have used axios as example here for calling apis, you can use fetch or superagent also.You can try something like.
AXIOS
axios.get('//url')
.then(function (response) {
//dispatch action
})
.catch(function (error) {
// throw error
});
So that was for the API call, now coming to the state. In redux there is one state which handles your app. I would suggest you should go through redux basics which you can find here . So once your api call succeeds you need to update your state with the data.
Action to fetch data
function fetchData(){
return(dispatch,getState) =>{ //using redux-thunk here... do check it out
const url = '//you url'
fetch(url)
.then (response ) => {dispatch(receiveData(response.data)} //data being your api response object/array
.catch( error) => {//throw error}
}
}
Action to update state
function receiveData(data) {
return{
type: 'RECEIVE_DATA',
data
}
}
Reducer
function app(state = {},action) {
switch(action.types){
case 'RECEIVE_DATA':
Object.assign({},...state,{
action.data
}
})
default:
return state
}
}
Related
I am in need of guidance with getting through this error. The code is supposed to get the results from WebAPI while going through actions and services. In the actions is a dispatch where the error is. On my actions page it should call the service for WebAPI and depend on the response dispatch to the reducers for actions. The code does not pass the first dispatch in the jobActions.getjobs()
The error received from this is:
Unhandled Rejection (TypeError): _actions_job_actions__WEBPACK_IMPORTED_MODULE_1__.jobActions.getJobs(...).then is not a function
Page Load
import React from 'react';
import { jobActions } from '../../actions/job.actions';
class LoadTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
this.props.getJobs()
.then((res) => {
this.setState({ data: res.response || [] })
});
}
render() {
return ();
}
const mapDispatchToProps => dispatch => ({ getJobs: () => dispatch(jobActions.getJobs()) });
export default connect(mapDispatchToProps)( LoadTable );
===============================================
Actions
import { jobConstants } from '../constants/job.constants';
import { jobService } from '../services/job.service';
export const jobActions = {
getJobs
};
let user = JSON.parse(localStorage.getItem('user'));
function getJobs() {
return dispatch => {
dispatch(request());
return jobService.getJobs()
.then(
results => {
dispatch(success(user));
return { results };
},
error => {
dispatch(failure(error));
}
);
};
function request() { return { type: jobConstants.JOB_REQUEST }; }
function success(user) { return { type: jobConstants.JOB_SUCCESS, user }; }
function failure(error) { return { type: jobConstants.JOB_FAILURE, error }; }
}
=======================================================
services
export const jobService = {
getJobs
};
const handleResponseToJson = res => res.json();
function getJobs() {
return fetch('http://localhost:53986/api/jobs/getoutput')
.then(handleResponseToJson)
.then(response => {
if (response) {
return { response };
}
}).catch(function (error) {
return Promise.reject(error);
});
}
The result should be table data from the services page, actions page dispatching depending on the stage.
I assume you are using some sort of a middleware, like redux-thunk? If not, then your action creator returns a function, which is not supported by pure redux
I guess you do, because the error says that the action creator returned undefined after it was called
function getJobs() {
console.log("test -1");
return dispatch => {
console.log("test-2");
dispatch(request());
jobService.getJobs() // <==== here comes the promise, that you don't return
// return jobService.getJobs() <== this is the solution
.then(
results => {
console.log("test -3");
dispatch(success(user));
return { results };
},
error => {
dispatch(failure(error));
}
);
};
Update: you also need to map your action in mapDispatchToProps
Page Load
import React from 'react';
import { jobActions } from '../../actions/job.actions';
class LoadTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
this.props.getJobs() // as the name of mapDispatchToProps says, you mapped your action dispatch
// to a getJobs prop, so now you just need call it
.then((res) => {
this.setState({
data: res.response || []
})
}));
}
render() {
return ();
}
const mapStateToProps = state => ({});
const mapDispatchToProps = dispatch => ({
// this function will dispatch your action, but it also mapps it to a new prop - getJobs
getJobs: () => dispatch(jobActions.getJobs())
});
export default connect(mapStateToProps, mapDispatchToProps)( LoadTable );
When I run an action creator to create my thunk, it does not run the dispatch functions.
For testing, I put a button in my root component's render method, my root component is connected thusly (removed extraneous things):
import { loadAndSetCurrentUser } from '../Actions/SedFF';
import { setSysMenuExpand, setNavMenuExpand, setLoginDialogVisibility } from '../Actions/UI';
render() {
return (
<button onClick={() =>
loadAndSetCurrentUser("username#email.com")}>LASCU</button>
)
}
const mapStateToProps = function (state) {
return {
UI: state.UI,
sedff: state.SedFF,
}
}
const mapDispatchToProps = {
loadAndSetCurrentUser,
}
export default withStyles(styles, { withTheme: true })(withRouter(connect(mapStateToProps, mapDispatchToProps)(WebFF)));
My Actions Creators file looks like this:
export function loadAndSetCurrentUser(username) {
console.log("LASCU: " + username);
return (dispatch, getState) => {
console.log("in dispatch");
dispatch(this.requestingUserData(username));
const user = getState().Users[username];
if (user) {
// if already in store, just change the current username
//FUTURE: check date against user mod date in database
dispatch(setCurrentUsername(username));
dispatch(userDataLoadComplete());
} else {
// user not in store, need to check database //TODO:
fetch('https://jsonplaceholder.typicode.com/users?username=Bret')
.then(response => response.json())
// .then(json => delay(3000, json))
.then(json=> dispatch(ingestUserData(json)))
.then(() => dispatch(userDataLoadComplete()));
}
}
}
export function requestingUserData(username) {
console.log("RUD");
return { type: REQUESTING_USER_DATA, username };
}
export function setCurrentUsername(username) {
return { type: SET_CURRENT_USERNAME, username }
}
export function ingestUserData(userData) {
return (dispatch) =>
{
console.log("IUD");
console.log(userData);
dispatch({ type: SET_USER_DATA, userData })
}
}
export function userDataLoadComplete() {
return { type: USER_DATA_LOAD_COMPLETE };
}
And my reducers are bone-stock, looking like this:
export function SedFF(state = initialSedFFState, action) {
let newState = _.cloneDeep(state);
switch (action.type) {
case SET_CURRENT_USERNAME:
newState.currentUsername = action.username
return newState;
case LOAD_USER_DATA:
//TODO:
return newState;
case REQUESTING_USER_DATA:
newState.isFetchingUserData = true;
return newState;
case RECEIVED_USER_DATA:
//TODO:
newState.isFetchingUserData = false;
return newState
case USER_DATA_LOAD_COMPLETE:
//TODO:
newState.isFetchingUserData = false;
return newState
... etc, etc...
default:
return state
}
}
When I hit the LASCU button, I get the following output: LASCU: username#email.com coming from my action creator (noted on the second line of the action creator file above). I do NOT get the in dispatch output on the 4th line of my action creator file. I do NOT get any actions firing.
I'm unsure why the dispatch is not firing.
I will note that on a different component (a sub-component), I can get it to fire the entire thing, but I'm unable to find any differences. The only real difference appear to be that I don't have the router in the export line:
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(SystemMenu));
For what it's worth, my thunk middleware is connected at the App.js (parent of my root component) like so:
const store = createStore(
RootReducer,
initialState,
composeEnhancer(applyMiddleware(thunk))
);
class App extends React.Component {
render() {
return (
<Provider store={store}>
<CssBaseline />
<BrowserRouter>
<WebFF />
</BrowserRouter>
</Provider>
);
}
}
export default withStyles(styles, { withTheme: true })(App);
Thoughts? Help?
Looking at the render method given in the question, the issue appears to be that you're calling the original imported function, not the bound function from props. In other words, changing to this.props.loadAndSetCurrentUser() should work correctly. Note that you shouldn't be importing the store directly into a component file.
For more details, please see the React-Redux usage guide docs page on "Dispatching Actions with mapDispatch".
You still have to dispatch the Thunk action, otherwise it will just return the action and not do anything with it.
render() {
return (
<button onClick={() =>
store.dispatch(loadAndSetCurrentUser("username#email.com"))
}>LASCU</button>
)
}
So, basically what you were doing was similar to:
const action = function (username) { return function () { alert(username); }}
console.log(action('123')); // This will just log `function () { alert(username); }`, without actually running the alert()
So, even though you call action('123'), it will just return a function that still has to be called somehow. In the case of Thunks, the returned function has to be dispatched.
I'm building an App using react-native , following redux concepts , now i got stucked in a problem ,my component is not updating as they should after dispatching an action.
Explaining the plot a bit, I have a React Class "QuestionScreen" and i want to make a call to an API as soon as the page opens but a loader should be rendered while API is doing its work and when its done , questions should appear.so here is my code below
PS: Im new react native and super new to redux concept so a little help with explanation would be nice
QuestionScreen.js
function mapStateToProps(state) {
return {
session: state.questionsReducer.session
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
class QuestionsScreen extends Component {
static navigationOptions = ({navigation}) => ({
title: `${navigation.state.params.title}`,
headerRight: <Button transparent><EntypoIcon name="dots-three-vertical"/></Button>,
headerTintColor: '#0b3484',
});
constructor(props) {
super(props);
this.state = {
params: this.props.navigation.state.params.passProps
};
this.fetchSessionQuestions = this.fetchSessionQuestions.bind(this);
}
fetchSessionQuestions() {
this.props.fetchPracticeQuesions({
URL: BASE_URL + '/api/dashboard/get_chapter_question/',
chapter: this.state.params.chapter_name
});
}
componentDidMount() {
this.fetchSessionQuestions();
}
render() {
const {navigate} = this.props.navigation;
if (this.props.session.isLoading) {
return (
// Loader here
);
}
else {
const questions = this.props.session.questions;
return (
// Some questions logic here
);
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(QuestionsScreen);
questionsReducer.js
import * as types from '../actions/actionTypes';
const initialState = {
session: {
isLoading: true,
currentQuestion: 0,
questions: [],
score: 0,
timeTaken: 0,
attempted: 0
}
};
const newState = JSON.parse(JSON.stringify(initialState));
export default function questionsReducer(state = initialState, action) {
switch (action.type) {
case types.NEXT_QUESTION:
newState.session.currentQuestion = newState.session.currentQuestion + action.payload;
return newState;
case types.FETCH_PRACTICE_QUESTION:
fetch(action.payload.URL, {
method: 'POST',
body: JSON.stringify({
username: 'student',
password: 'pass1234',
chapter: action.payload.chapter
})
}).
then((response) => response.json()).
then((responseJson) => {
newState.session.questions = responseJson;
newState.session.isLoading = false;
});
console.log(newState);
return newState;
default:
return state;
}
}
now the probelem im having is that the props.session in my QuestionScreen remains same, so a loader keeps on spinning, even after dispatching that action, what should i do now ??
and yeah one more thing i checked the states status in console using logger and thunk middleware, the state printed there is as expected , showing me correct value there
You should use middleware as Redux-Thunk to implement async actions. Also you shouldn't use fetch in reducer, you should dispatch 3 events instead on request begin, request success, and request errors. Something like this:
export const callApi = ((fetchUrl) => (dispatch) => {
dispatch(requestBegin());
let body = (method === 'GET') ? undefined : JSON.stringify(data);
return fetch(fetchUrl, {
method,
body,
})
.then(checkStatus)
.then(parseJSON)
.then(function(data) {
dispatch(requestSuccess(data);
}).catch(function(error) {
dispatch(requestError(data));
})
});
Also, you can read about async actions here
I have an action creator that I'm calling in componentWillMount, the return of that action payload is being assigned to state using setState. However, in componentDidMount I cannot access that property as the async call hasn't completed yet. What is the correct way to access this data in compoentDidMount?
//component
class Dashboard extends Component {
componentWillMount() {
this.setState(this.props.getUser());
}
componentDidMount() {
// this.state.user isn't available yet
}
render(){
return(...);
}
}
//action
export function getUser() {
return async function (dispatch) {
const user = await axios.get(`${API_URL}user?token=${token}`);
return dispatch({
type: USER,
payload: user,
});
}
};
}
Axios returns a promise and you have to wait until it resolves. Then dispatch the success action like this,
export function getUser() {
return function (dispatch) {
axios.get(`${API_URL}user?token=${token}`)
.then(user => {
return dispatch(getUserSuccess(user));
}).catch(error => {
throw error;
});
}
};
export function getUserSuccess(user) {
return {type: USER, payload: user};
}
Also note that you need to have mapStateToProps so it brings the user to your component. Then you can access it using this.props.user within your component. It should be like this.
UserPage.propTypes = {
user: PropTypes.object.isRequired
};
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({getUser}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(UserPage);
Finally you may access the user like this.
render() {
const {user} = this.props;
return(
<div>
<div>user.name</div>
</div>
);
}
You need to use componentWillReceiveProps to do that, for example:
componentWillReceiveProps(nextProps) {
if (nextProps.user !== this.state.user) {
this.setState({
user: nextProps.user
});
}
}
now you can use user inside your component.
Here you can find more information.
I'm trying to make an async action using Redux-thunk. I'm still don't really understanding how Async call works with Redux-Thunk but I'm beginning to get a few things. I know that :
I need an Action-Creator
Some actions related to my Action-creator (something like "I'm calling the data", "I'm waiting for then", "Got it", "Oups there is an error")
Not sure about this but I need a Reducer to handle the action dispatched by my Action-Creator.
To start slowly I just want to pass some data into my redux-store-state. So I can check it through the react dev tool.
My Action-Creator and my Actions :
function loadData(dispatch) {
return axios.get(`http://localhost:3000/authors`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
);
}
export function loadData(data) {
return {
type: LOAD_DATA_SUCCESS,
data
}
}
export function failData(data) {
return {
type: LOAD_DATA_FAILURE,
err
}
}
To make sure that my local URL is sending the data correctly with axios I've also done this :
export function fetchData() {
axios.get('http://localhost:3000/authors')
.then(function (response) {
console.log(response);
})
}
My reducer :
const ThunkData = (state = {}, action) => {
switch (action.type) {
case 'LOAD_DATA_SUCCESS':
return Object.assign({}, state, {
data: action.data,
});
case 'LOAD_DATA_FAILURE':
return action.err
default:
return state;
}
};
My client.js where I'm dispatching my Action-Creator with store.dispatch(loadData()); :
const loggerMiddleware = createLogger();
function configureStore(preloadedState) {
return createStore(
todoApp,
preloadedState,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
), window.devToolsExtension ? window.devToolsExtension() : f => f
)
}
const store = configureStore()
store.dispatch(loadData());
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('test')
);
When I launch my app I have a ThunkData object in my store with a data object but undefined. I'm guessing there is something wrong on how my action creator is passing the data and how my reducer is receiving it.
Thanks.
export function loadData(data) {
return {
type: LOAD_DATA_SUCCESS,
data
}
}
export function failData(data) {
return {
type: LOAD_DATA_FAILURE,
err
}
}
These two functions are redundant in your action file. Remove them and add an export to the first loadData function. Rest of the code is fine.