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.
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 new to react. I tried to separate component and action function. but I cannot get return value from separate action function. Is it possible to return a value (e.g Object {}) from dispatch function
I put the brief code as below :
LoginComponent.js
class Login extends React.Component {
constructor(props){
super(props)
this.state = {
username : '',
password : ''
}
}
submit = (e) => {
/* console.logging "Some response"*/
console.log(this.props.doLogin(this.state))
}
render(){
return (
<form onSubmit={this.submit}>/* some login element */</form>
)
}
}
export default connect(null, {LoginAction})(Login);
LoginAction.js
export function doLogin(state){
return dispatch => {
return axios.post('login', state).then(res =>{
return "Some response";
})
}
}
but It doesn't return any value
Thankyou.
Contrary the the above answer, you actually can return whatever you want from a thunk. Redux-thunk will pass it through.
In your case, where your thunk is returning a Promise<string>, that means that in your component this.props.doLogin(this.state) will also evaluate to a Promise<string>.
So instead of trying to log the Promise, instead try switching that log code over to this.props.doLogin(this.state).then(result => console.log(result);
You can use callback function
this.props.doLogin((this.state),(result)=>{
console.log(result)
})
export function doLogin(state,callback){
return dispatch => {
return axios.post('login', state).then(res =>{
callback(res);
})
}
}
Returning the function is not an option when you are using redux-thunk. it will run the callback and dispatch whatever you pass as an action object.
So as you want to make the api call ans see whether it is a success or failure . You need to dispatch and action on success. save it in redux state. and access the data in your component
export function doLogin(state){
return dispatch => {
axios.post('login', state).then(res =>{
dispatch({
data: "Some response",
type: "API_SUCCESS"
})
})
.catch(err) {
dispatch({
data: err,
type: "API_FAILURE"
})
}
}
And then access those values in your component like this
mapStateToProps = (state) => ({
data: state.yourreducer.data,
})
define mapDispatchToProps if you need dispatcch binded functions
export default(mapStateToProps, mapDispatchToProps)(YourComponent)
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
}
}
After a bit of trial and error I finally manage to get my action creator working properly and passing the data I wanted into my redux store. Until now I've been dispatching it "manually" like this store.dispatch(fetchTest()); but It would be great if could use these data into a component.
So here is my action creator :
export const fetchTest = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
return axios.get('http://localhost:3000/authors')
.then(data => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching:false,
data: data
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
Here is my reducer :
const initialState = {data:null,isFetching: false,error:null};
export const ThunkData = (state = initialState, action)=>{
switch (action.type) {
case 'FETCH_DATA_REQUEST':
case 'FETCH_DATA_FAILURE':
return { ...state, isFetching: action.isFetching, error: action.error };
case 'FETCH_DATA_SUCCESS':
return Object.assign({}, state, {data: action.data, isFetching: action.isFetching,
error: null });
default:return state;
}
};
So far everything is working properly when using store.dispatch(fetchTest());.
Based on this example I tried to build the following component :
class asyncL extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchTest(this.props.thunkData)
// got an error here : "fetchTest is not a function"
}
render() {
if (this.props.isFetching) {
return console.log("fetching!")
}else if (this.props.error) {
return <div>ERROR {this.props.error}</div>
}else {
return <p>{ this.props.data }</p>
}
}
}
const mapStateToProps = (state) => {
return {
isFetching: state.ThunkData.isFetching,
data: state.ThunkData.data.data,
error: state.ThunkData.error,
};
};
const AsyncList = connect(mapStateToProps)(asyncL);
export default AsyncList
It doesn't work, I have an error on the componentWillMount() and probably somewhere else.
Also my data structure is kind of weird. To actually get to the data array I have to do state.ThunkData.data.data. The first data object is full of useless stuff like request, headers, etc...
So how should I write this component so I can at least passed the Async data into a console.log.
Thanks.
You need to mapDispatchToProps as well.
import { fetchTest } from './myFetchActionFileHere';
import { bindActionCreators } from 'redux';
function mapDispatchToProps(dispatch) {
return {
fetchTest: bindActionCreators(fetchTest, dispatch)
};
}
const AsyncList = connect(mapStateToProps, mapDispatchToProps)(asyncL);
export default AsyncList
documentation link: http://redux.js.org/docs/api/bindActionCreators.html