I have a component, which has a form, when a user clicks the submit button and after the validation is successful, an action dispatches (this all happens within the submit event).
Should the component re-render after the action dispatches?
Currently, I don't see this happening, what could be the reason?
Let me add, what's happening here.
1) form is submitted;
2) the validation is run;
3) the action dispatches (it sends the data to backend to save it into the database);
In the reducer, I have defined:
const messages = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ADD_MESSAGE_SUCCESS:
return { ...state, message: action.message, error: null, isMessageLoading: false };
case ADD_MESSAGE_FAILURE:
return { ...state, message: null, error: action.error, isMessageLoading: false };
default:
return state;
}
};
And in the console log, I can clearly see that the state has changed.
But no re-render occurs at this stage.
const mapStateToProps = (state) => {
return {
message: state.messages.message,
isMessageLoading: state.messages.isMessageLoading
}
};
So when the action dispatches, the props gets updated.
This is the code of an action
export const addMessage = (message) => {
return (dispatch) => {
dispatch({ type: ADD_MESSAGE });
return fetch(`http://localhost:8080/api/contacts`, {
credentials: 'include',
method: 'POST',
body: JSON.stringify(message),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
}).then(response => {
return response.json();
}).then((data) => {
dispatch(addMessageSuccess(data));
}).catch(error => {
console.log(`An error occurred while adding contact message: ${error}`);
dispatch(addMessageFailure(error));
});
};
};
Connected components will re-render if the state changes (and the changed state is being selected in mapStateToProps)
In your case, both message or isMessageLoading can trigger the update.
Note that if the message is a string and you dispatch the actions that contain the same message string, your component will not re-render. For example, the current message state is success, and you dispatch another action which also contains message: success:
{
type: ADD_MESSAGE_SUCCESS,
message: 'success'
}
Then your component won't update. That's because there are some optimization in React-Redux's connect
A simple example here: https://jsfiddle.net/njk5teoh/ (I also add error to the component), you can click the submit button several times and see the console log.
If this doesn't solve your problem, you may need to provide a reproducible jsfiddle/codepen or repo.
Related
I am requesting an API using redux and saga
I the request is working fine I am able to get API returned data in the reducer, but I am not able to get that data in the home.js I am new to redux please help me.
in my home.js this is how I am making a call
const fetchSessionData = () => {
dispatch(
fetchSession({
token: token
})
)
}
action.js
export const fetchSession = data => {
return {
type: Action.SESSION_DATA,
payload: {data},
};
};
this is reducer file
export const SessionData = (state = sessionState, action) => {
console.log('inside session reducer', JSON.stringify(action));
switch (action.type) {
case Action.SESSION_FETCH_SUCCESS:
return {
...state,
isLoading: false,
};
case Action.SESSION_FETCH_ERROR:
return {
...state,
sagaerror: action.error,
isLoading: false,
};
case Action.SESSION_DATA:
return {
...state,
isLoading: true,
};
default:
return state;
}
};
and this is the api
export function fetchSessionData(payload) {
return fetch(`${url}/session/`, {
method: 'GET',
headers: {
Authorization: `Bearer ${payload.token}`,
Accept: 'application/json',
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(res => {
return res;
})
.catch(error => {
throw error;
});
}
how can I get the returning data from api in home.js?
Looks like you are not storing back the response from saga.
Please have an action for storing the response into your reducer.you may use
yield put(storeactionname(payload received from api reponse);
in your saga method and in reducer your action.payload should be stored into a state variable
and in your component you can use useSelector like below
const { yourvariableNameInStateInReducer } =
useSelector((state) => state.yourreducername);
As you say you will able to get data to reducer. It's then fine in that section. In the component you need to select stored data from the store. If you are using functional component you can use useSelector hook in react-redux. If you are using class component then you need to use connect function, you need to pass mapStateToProps and mapDispatchToProps arguments to the connect function. Refer https://react-redux.js.org/api/connect
State is not updated immediately after receiving data
Accounts.js like this
class Accounts extends Component {
componentDidMount()
{
this.props.dispatch(fetchAccountsAction())
}
render(){
const accInfo = this.props.accounts // Not getting data immediately
return (
<Details accInfo = {accInfo} />
)
}
}
const mapStateToProps = (state, ownProps) => {
console.log('state',state);
return {
accounts:state.accounts
}
}
export default connect(mapStateToProps)(Accounts)
Action.js like this
const fetchAccountsAction = () => {
return async (dispatch) => {
const res = await fetch(url, {
method: "POST",
headers: {
'Content-type': 'Application/json',
'Authorization': token,
},
body: JSON.stringify(data)
});
const data = await res.json()
if (data) {
dispatch(fetchAccounts(data))
}
}
}
export function fetchAccounts(accounts)
{
console.log('accounts',accounts) // Am getting data here
return {
type: FETCH_ACCOUNTS,
accounts : accounts
}
}
Reducer.js like this
const initialState = {
accounts : [],
error:null
}
export function accountsReducer(state=initialState,action) {
switch(action.type){
case FETCH_ACCOUNTS:
return {
...state,
accounts:action.accounts
}
default:
return state
}
}
When componentDidMount happened props not receiving immediately because there is a delay in API response. Could you please help with the props access after receiving the data from API.
Thank you.
What happens here:
cDM is called, action is dispatched.
If action creator was sync(just a plain action + straight reducer without any async operations) state would be updated
render() happens with previous props(old state)
redux's store.subscribe() makes wrapper(created by connect) to recalculate all that mapStateToProps/mapDispatchToProps
since step #3 returned different values wrapper re-renders your component with new props
render() happens with new props
That fact your action creator is async by its nature switch #2 and #3 with their places. But anyway, your first render will be with old store values.
So you better handle that accordingly(like checking if some object is not undefined anymore or use brand new optional chaining to get safe from "cannot read property ... of null")
I'm new in Redux React and creating web app where app interact with Lumen API framework. When a request go to server and return with error code 400, 404, 500 any status code (error) except 200 it shows console error and processed after that in React.
I tried pass value when get error at axois.catch({dispatch}) but value update in state by viewing redux-dev-tool but didn't get value at props.
As from API I passed as like:
if ($validator->fails()) {
return response()->json(['type'=> 'error','message'=> $validator->errors()->all()],400);
}
And in my action file as like:
export const loginRequest = formValues => async dispatch => {
await user.post('/user/create', { ...formValues, device_id: 12345}).then(response => {
dispatch ({type: LOGIN, payload: response.data});
}).catch(error => {
if(error.response) {
dispatch ({ type: ERRORS, payload: error.response.data.message });
}
});
}
and in reducer:
const INTIAL_STATE = {
isSignedIn: null,
accessToken: null,
loginMessage: null,
errorMessage: null
};
export default (state = INTIAL_STATE, action) => {
switch (action.type){
case ERRORS:
return { ...state, isSignedIn: false, accessToken:null , errorMessage: action.payload };
default:
return state;
}
};
as last in my component file:
const mapStateToProps = state => {
return {error: state.auth.errorMessage};
};
and console log in render method:
console.log(this.props);
I'm getting POST http://localhost:8000/user/create 500 (Internal Server Error) in my console but the errorMessage value updated in state as looked in redux-dev-tool and after error no code run.
Use the redux-saga-routines for dispatching actions, make your work easy with this module.
Here its documentation link https://github.com/afitiskin/redux-saga-routines
I have the following middleware that I use to call similar async calls:
import { callApi } from '../utils/Api';
import generateUUID from '../utils/UUID';
import { assign } from 'lodash';
export const CALL_API = Symbol('Call API');
export default store => next => action => {
const callAsync = action[CALL_API];
if(typeof callAsync === 'undefined') {
return next(action);
}
const { endpoint, types, data, authentication, method, authenticated } = callAsync;
if (!types.REQUEST || !types.SUCCESS || !types.FAILURE) {
throw new Error('types must be an object with REQUEST, SUCCESS and FAILURE');
}
function actionWith(data) {
const finalAction = assign({}, action, data);
delete finalAction[CALL_API];
return finalAction;
}
next(actionWith({ type: types.REQUEST }));
return callApi(endpoint, method, data, authenticated).then(response => {
return next(actionWith({
type: types.SUCCESS,
payload: {
response
}
}))
}).catch(error => {
return next(actionWith({
type: types.FAILURE,
error: true,
payload: {
error: error,
id: generateUUID()
}
}))
});
};
I am then making the following calls in componentWillMount of a component:
componentWillMount() {
this.props.fetchResults();
this.props.fetchTeams();
}
fetchTeams for example will dispatch an action that is handled by the middleware, that looks like this:
export function fetchTeams() {
return (dispatch, getState) => {
return dispatch({
type: 'CALL_API',
[CALL_API]: {
types: TEAMS,
endpoint: '/admin/teams',
method: 'GET',
authenticated: true
}
});
};
}
Both the success actions are dispatched and the new state is returned from the reducer. Both reducers look the same and below is the Teams reducer:
export const initialState = Map({
isFetching: false,
teams: List()
});
export default createReducer(initialState, {
[ActionTypes.TEAMS.REQUEST]: (state, action) => {
return state.merge({isFetching: true});
},
[ActionTypes.TEAMS.SUCCESS]: (state, action) => {
return state.merge({
isFetching: false,
teams: action.payload.response
});
},
[ActionTypes.TEAMS.FAILURE]: (state, action) => {
return state.merge({isFetching: false});
}
});
The component then renders another component that dispatches another action:
render() {
<div>
<Autocomplete items={teams}/>
</div>
}
Autocomplete then dispatches an action in its componentWillMount:
class Autocomplete extends Component{
componentWillMount() {
this.props.dispatch(actions.init({ props: this.exportProps() }));
}
An error happens in the autocomplete reducer that is invoked after the SUCCESS reducers have been invoked for fetchTeams and fetchResults from the original calls in componentWillUpdate of the parent component but for some reason the catch handler in the middleware from the first code snippet is invoked:
return callApi(endpoint, method, data, authenticated).then(response => {
return next(actionWith({
type: types.SUCCESS,
payload: {
response
}
}))
}).catch(error => {
return next(actionWith({
type: types.FAILURE,
error: true,
payload: {
error: error,
id: generateUUID()
}
}))
});
};
I do not understand why the catch handler is being invoked as I would have thought the promise has resolved at this point.
Am not completely sure, it's hard to debug by reading code. The obvious answer is because it's all happening within the same stacktrace of the call to next(actionWith({ type: types.SUCCESS, payload: { response } })).
So in this case:
Middleware: Dispatch fetchTeam success inside Promise.then
Redux update props
React: render new props
React: componentWillMount
React: Dispatch new action
If an error occurs at any point, it will bubble up to the Promise.then, which then makes it execute the Promise.catch callback.
Try calling the autocomplete fetch inside a setTimeout to let current stacktrace finish and run the fetch in the next "event loop".
setTimeout(
() => this.props.dispatch(actions.init({ props: this.exportProps() }))
);
If this works, then its' the fact that the event loop hasn't finished processing when the error occurs and from the middleware success dispatch all the way to the autocomplete rendered are function calls after function calls.
NOTE: You should consider using redux-loop, or redux-saga for asynchronous tasks, if you want to keep using your custom middleware maybe you can get some inspiration from the libraries on how to make your api request async from the initial dispatch.
I have been looking for an elegant (and correct) way to handle logging in for a Redux application. Currently the the user is directed to a login component/container where he see's a form with username and password. Then when he clicks on submit I call an async login function and when the promise is fulfilled then I dispatch a loginSuccess and also a replacePath call. The following is some pseudo code:
submit(e) {
login(username, password)
.then(function (user) {
dispatch(loginSuccess(user));
dispatch(replacePath('/');
});
}
This works but I'm not sure it's best practice. Anyone have any better implementations?
Its generally considered bad practice to call dispatch within a component unless its a top-level container connected to the store.
I'd recommend following the examples that Dan Abramov gives in the docs, most notably the async Reddit post fetching example. Take a look at how he handles the interim of the request with posts.isFetching.
Since I know StackOverflow doesn't like links, here's a simplified example (in ES6):
These are the actions:
// Actions
import fetch from 'isomorphic-fetch';
import * as types from '../constants/actionTypes.js';
var requestAuth = function() {
return {
type: type.REQUEST_AUTH
}
};
var authSuccess = function(response) {
return {
type: type.AUTH_SUCCESS,
response: response
}
};
var authFail = function(response) {
return {
type: type.AUTH_FAIL,
response: response
}
};
var authenticate = function(username, password) {
var fetchOptions = {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({username: username, password: password})
};
var uri = '/api/path/to/your/login/backend';
return dispatch => {
dispatch(requestAuth);
return fetch(uri, fetchOptions)
.then(response => {
if (resopnse.status === 200) {
dispatch(authSuccess(response));
// Do any other login success work here
// like redirecting the user
} else {
dispatch(authFail(response));
}
}
}
};
Next the reducer:
// Reducer
import { combineReducers } from 'redux';
import { REQUEST_AUTH, AUTH_SUCCESS, AUTH_FAIL } from '../actions/login';
function login(state = {
isAuthenticating: false,
isLoggedIn: false,
authenticationToken: '',
authError: null
....., // whatever other state vars you need
.....
}, action) {
switch(action.type) {
case REQUEST_AUTH:
return Object.assign({}, state, {
isAuthenticating: true
});
break;
case AUTH_SUCCESS:
return Object.assign({}, state, {
isAuthenticating: false,
isLoggedIn: true,
authenticationToken: action.response.token
});
break;
case AUTH_FAIL:
return Object.assign({}, state, {
isAuthenticating: false,
authError: action.response.error
});
break;
default:
return state;
}
}
And finally the component method
// Component Method
// authenticate() should be wrapped in bindActionCreators()
// and passed down as a prop
function handleSubmit(username, password) {
if (isValid(username) && isValid(password) {
authenticate(username, password);
}
}
tl;dr Your user types in their credentials which should be part of state (not pictured here). An onClick in the component calls handleSubmit(), which dispatches authenticate(). Authenticate dispatches requestAuth() which updates state to show your user that the request is being processed (a loading spinner displays or something). Once your AJAX call to the backend returns with the authentication results, you dispatch either authSuccess() or authFail() to update state and inform the user whether their request succeeded or not.