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
...
}
}
Related
First, I made a small application on the React.js. Using the fetch method, I take the API
And these are the main files of my application:
Index.js:(action)
export const SHOW_AIRPLANES = "SHOW_AIRPLANES";
export function showAirplanes() {
return (dispatch, getState) => {
fetch("https://api.iev.aero/api/flights/25-08-2019").then(response => {
dispatch({ type: SHOW_AIRPLANES, payload: response.data });
});
};
}
airplanes.js:(reducer)
import { SHOW_AIRPLANES } from '../actions'
const initialState = {
list: []
}
export function showAirplanes(state = initialState, action) {
switch (action.type) {
case SHOW_AIRPLANES:
return Object.assign({}, state, {list: action.payload})
default:
return state
}
}
index.js(reducer):
import { combineReducers } from "redux";
import { showAirplanes } from "./airplanes";
const rootReducer = combineReducers({
user: showAirplanes
});
export default rootReducer;
First, you should use the createStore function like so:
const initialData = {}; // whatever you want as initial data
const store = createStore(reducers, initialData, applyMiddleware(thunk));
Then pass it to your provider
<Provider store={store}>
{...}
</Provider
next, when you map your reducers inside the combineReducers function, each key in this object represents a piece of your state. So when you do user: showAirplanes it means that you intend to use it in the mapStateToProps with state.user.list so I think you meant to call it airplane: showAirplanes.
Then, your reducer name is not informative enough, I would suggest to change it to airplanesReducer.
Next issue, the call to fetch returns a response that has JSON that must be resolved.
Change this:
fetch("https://api.iev.aero/api/flights/25-08-2019").then(response => {
dispatch({ type: SHOW_AIRPLANES, payload: response.data });
});
To this:
fetch("https://api.iev.aero/api/flights/25-08-2019")
.then(res => res.json())
.then(response => {
dispatch({ type: SHOW_AIRPLANES, payload: response.body.departure });
});
Note that I've changed the value that you need to resolve from the response as well.
Inside your App.js component you need to create a constructor and bind the renderAirplaneList function to this
// Inside the App class
constructor(props) {
super(props);
this.renderAirplaneList = this.renderAirplaneList.bind(this);
}
And finally (I hope I didn't miss anything else), you map your state in the App.js component to { airplanes: state.airplanes.list} so the name of the prop you expect inside your component is props.airplanes.
renderAirplaneList() {
if (!this.props.airplanes.length) {
return null;
}
const arr = this.props.airplanes || [];
return arr.map(airplane => {
return (
<tr key={airplane.id}>
<td>{airplane.ID}</td>
<td>{airplane.term}</td>
<td>{airplane.actual}</td>
<td>{airplane["airportToID.city_en"]}</td>
</tr>
);
});
}
Make sure you go over the documentation of React and Redux, they have all the information you need.
Good luck.
aren't you suppose to send some parameters to this call?
this.props.showAirplanes()
it seems that it has 2 parameters: state and action, although state seems to have already it's default value
I'm creating a React Native application and using redux and redux-thunk to implement my API requests. I would like to know how I can wait for my action to be dispatched and make sure that my state has been updated in an async thunk logic. If I understand correctly, await will wait for the end of the thunk but the action is not dispatched yet. Although, as you can see in my usage, I need the state to be modified to proceed the rest of the code accordingly.
actions/user.js
export const tryLogin = (
email: string,
password: string,
sessionToken: string = ''
): Function => async (dispatch: Function) => {
const logUser = () => ({ type: LOG_USER })
const logUserSuccess = (data: any, infos: any) => ({
type: LOG_USER_SUCCESS,
data,
infos,
})
const logUserError = (signinErrorMsg: string) => ({
type: LOG_USER_ERROR,
signinErrorMsg,
})
dispatch(logUser())
try {
{ /* Some API requests via axios */ }
dispatch(logUserSuccess(responseJson, infos))
return true
} catch (error) {
{ /* Error handling code */ }
dispatch(logUserError(error.response.data.error))
return false
}
reducers/user.js
case LOG_USER:
return {
...state,
isLoggingIn: true,
}
case LOG_USER_SUCCESS:
return {
...state,
isLoggingIn: false,
data: action.data,
infos: action.infos,
error: false,
signinErrorMsg: '',
}
case LOG_USER_ERROR:
return {
...state,
isLoggingIn: false,
error: true,
signinErrorMsg: action.signinErrorMsg,
}
RegisterScreen.js
if (await trySignup(
emailValue,
firstNameValue,
lastNameValue,
passwordValue,
birthdateValue,
genderValue
)
) {
if (userReducer.data) {
navigation.navigate('Secured')
}
In Redux,
When an Action is dispatched to the store, it will update the state of the UI automatically with new props.
Instead of watching the dispatched action, You can add a flag in the reducer signUpSuccess similar to isLoggingIn flag and listen to the changes in componentDidUpdate lifecycle method.
trySignup can be called separately (like on an event, formSubmit, button click, etc.)
RegisterScreen.js
class RegisterScreen extends React.Component{
...
componentDidUpdate(prevProps) {
if (prevProps.signUpSuccess !== this.props.signUpSuccess){
if (this.props.signUpSuccess) {
navigation.navigate('Secured')
}
}
}
...
}
const mapStateToProps = (state) => ({
signUpSuccess: state.userReducer.signUpSuccess,
});
export default connect(mapStateToProps)(RegisterScreen);
If I understand correctly, await will wait for the end of the thunk
but the action is not dispatched yet.
Render can be update if any changes happens in props, so extract props in your render method and update UX as per change in props.
I would suggest use React native debugger to check your actions
and current saved state.
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')
);
}
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));
}
}
I'm trying to wrap my head around redux, react-redux and redux-form.
I have setup a store and added the reducer from redux-form. My form component looks like this:
LoginForm
import React, {Component, PropTypes} from 'react'
import { reduxForm } from 'redux-form'
import { login } from '../../actions/authActions'
const fields = ['username', 'password'];
class LoginForm extends Component {
onSubmit (formData, dispatch) {
dispatch(login(formData))
}
render() {
const {
fields: { username, password },
handleSubmit,
submitting
} = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit)}>
<input type="username" placeholder="Username / Email address" {...username} />
<input type="password" placeholder="Password" {...password} />
<input type="submit" disabled={submitting} value="Login" />
</form>
)
}
}
LoginForm.propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired
}
export default reduxForm({
form: 'login',
fields
})(LoginForm)
This works as expected, in redux DevTools I can see how the store is updated on form input and on submitting the form the login action creator dispatches the login actions.
I added the redux-thunk middleware to the store and setup the action creator(s) for logging in as described in the redux docs for Async Actions:
authActions.js
import ApiClient from '../apiClient'
const apiClient = new ApiClient()
export const LOGIN_REQUEST = 'LOGIN_REQUEST'
function requestLogin(credentials) {
return {
type: LOGIN_REQUEST,
credentials
}
}
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
function loginSuccess(authToken) {
return {
type: LOGIN_SUCCESS,
authToken
}
}
export const LOGIN_FAILURE = 'LOGIN_FAILURE'
function loginFailure(error) {
return {
type: LOGIN_FAILURE,
error
}
}
// thunk action creator returns a function
export function login(credentials) {
return dispatch => {
// update app state: requesting login
dispatch(requestLogin(credentials))
// try to log in
apiClient.login(credentials)
.then(authToken => dispatch(loginSuccess(authToken)))
.catch(error => dispatch(loginFailure(error)))
}
}
Again, in redux DevTools I can see that this works as expected. When dispatch(login(formData)) is called in onSubmit in the LoginForm, first the LOGIN_REQUEST action is dispatched, followed by LOGIN_SUCCESS or LOGIN_FAILURE. LOGIN_REQUEST will add a property state.auth.pending = true to the store, LOGIN_SUCCESS and LOGIN_FAILURE will remove this property. (I know this might me something to use reselect for, but for now I want to keep it simple.
Now, in the redux-form docs I read that I can return a promise from onSubmit to update the form state (submitting, error). But I'm not sure what's the correct way to do this. dispatch(login(formData)) returns undefined.
I could exchange the state.auth.pending flag in the store with a variable like state.auth.status with the values requested, success and failure (and again, I could probably use reselect or something alike for this).
I could then subscribe to the store in onSubmit and handle changes to state.auth.status like this:
// ...
class LoginForm extends Component {
constructor (props) {
super(props)
this.onSubmit = this.onSubmit.bind(this)
}
onSubmit (formData, dispatch) {
const { store } = this.context
return new Promise((resolve, reject) => {
const unsubscribe = store.subscribe(() => {
const state = store.getState()
const status = state.auth.status
if (status === 'success' || status === 'failure') {
unsubscribe()
status === 'success' ? resolve() : reject(state.auth.error)
}
})
dispatch(login(formData))
}).bind(this)
}
// ...
}
// ...
LoginForm.contextTypes = {
store: PropTypes.object.isRequired
}
// ...
However, this solution doesn't feel good and I'm not sure if it will always work as expected when the app grows and more actions might be dispatched from other sources.
Another solution I have seen is moving the api call (which returns a promise) to onSubmit, but I would like to keep it seperated from the React component.
Any advice on this?
dispatch(login(formData)) returns undefined
Based on the docs for redux-thunk:
Any return value from the inner function will be available as the return value of dispatch itself.
So, you'd want something like
// thunk action creator returns a function
export function login(credentials) {
return dispatch => {
// update app state: requesting login
dispatch(requestLogin(credentials))
// try to log in
apiClient.login(credentials)
.then(authToken => dispatch(loginSuccess(authToken)))
.catch(error => dispatch(loginFailure(error)))
return promiseOfSomeSort;
}
}