Redux reducer not being called - javascript

Using redux-api-middleware I'm encountering a problem wherein one and only one of my reducer functions is not triggering.
I would like to handle failed api requests in a similar way to how I handle the successful api requests. Yet as far as I can tell, the SEARCH_FAILURE FSA never gets handled, though it is treated (AFAICT) identically to the SEARCH_SUCCESS FSA. It does seem to be created and dispatched, based on what I see in the devtools.
I have this
import { CALL_API } from 'redux-api-middleware'
import { handleActions } from 'redux-actions'
const searchReducer = handleActions({
//... other handlers elided
SEARCH_SUCCESS: (state = defaultState, action) => {
return {
...state,
search_results: ({...action.payload}),
api: {
requestPending: false,
searchPending: false
},
}
},
SEARCH_FAILURE: function(state = defaultState, action) {
console.log("Handling SEARCH_FAILURE given state, action: ", state, action)
return {
...state,
search_results: {Total: 0},
api: {
requestPending: false,
error: action.payload
},
errors: [action.payload, ...state.errors]
}
},
})
the SEARCH_SUCCESS FSA gets handled by searchReducer, but when the server gives a 400 response, the SEARCH_FAILURE handler never gets called--at least I don't see the log output I would expect, and the state sure doesn't end up looking right. I do see a SEARCH_FAILURE entry in the redux devtools panel, however.
Serving to confuse me further, here is the declaration I have at the moment for creating the RSAA
export function doSearch( selected_filters, page ){
let qs = SearchPage.constructQueryString(selected_filters, page)
return {
[CALL_API]: {
endpoint: `/api/songs/search?${qs}`,
method: 'GET',
types: [
{type: SEARCH_REQUEST},
{type: SEARCH_SUCCESS},
{
type: SEARCH_FAILURE,
payload: (action, state, res) => {
if (400 === res.status)
{
console.log(`${SEARCH_FAILURE} payload: `, action, state, res)
}
return res
}
},
],
headers: { 'Content-Type': 'application/json' },
credentials: 'include'
}
}
}
the payload function is being called and logging more or less what I'd expect. So what am I messing up here? As far as I can discern from multiple readings of the docs for redux-api-middleware, this setup should yield the behavior I want, but it does not. The successes succeed, but the failures fail...

I ran into a similar issue when I was defining one of my types with an object rather than the just the action as you're doing for SEARCH_FAILURE. I ended up fixing by updating my package.json with:
"redux-api-middleware": "^1.0.0-beta3",
I think the problem is that the docs describe the version 1.0.0 API, but when you:
npm install redux-api-middleware --save
You end up getting an earlier version of the package.

Related

Can't connect custom action with the custom reducer react-admin

8.4 of react-admin. I've been trying to implement a custom action that connects with the custom reducer but so far nothing has worked.
I've Implemented this part of the guide in the official documentation for the action side https://marmelab.com/react-admin/doc/3.8/Actions.html#querying-the-api-with-fetch and this for the reducer https://marmelab.com/react-admin/doc/3.8/Admin.html#customreducers. The problem stems from that I can only use useUpdate method which sends update request, instead of a get without connecting to the reducer and there is no clear explanation of how I can chain those two things together. I also tried using an older way of dispatching actions, but still didn't work. Please help I've been trying this for 2 weeks now. Nothing gets updates and the redux store stays the same.
component
const { data, loading, error } = useQueryWithStore({
type: 'getList',
resource: 'goals',
action: "GET_USER_GOALS",
payload: { pagination: { page: 1, perPage: 10 }, sort: { field: "a-z", order: "ABC" }, filter: {} }
});
reducer
export default (previousState = 0, { type, payload }) => {
console.log(type)
if (type === 'GET_USER_GOALS') {
return payload.rate;
}
return previousState;
}
I even wrote a custom action
but it says that "Cannot read property 'update' of undefined" which isn't supported in the newer version I guess.
import { UPDATE } from 'react-admin';
export const UPDATE_PAGE = 'GET_USER_GOALS';
export const setGoals = (id, data) => {
return {
type: UPDATE_PAGE,
payload: { id, data: { ...data, is_updated: true } },
meta: { fetch: UPDATE, resource: 'goals' },
}
};
admin
<Admin
locale="en"
customReducers={{ userGoals: userGaolsReducer }}
loginPage={LoginPage}
authProvider={authProvider}
dataProvider={testProvider}
i18nProvider={i18nProvider}
history={history}
dashboard={Dashboard}
customSagas={[userGoalsSaga]}
>
I had to include it in the store.js as well
const reducer = combineReducers({
admin: adminReducer,
router: connectRouter(history),
userDashboardSettings: userGaolsReducer
});

reduce redux-thunk boilersplate code

I'm using redux-thunk middleware, it handles but async action well but I have too many boilerplate to write, how can I reduce it?
export function userReducer(state = {
signup_loading: false,
signup_failed: '',
signup_error: '',
login_loading: false,
login_failed: '',
login_error: ''
}, { payload, payload: { data, error } }) {
switch (action.type) {
case SIGNUP_REQUEST:
return {
signup_loading: true
}
case SIGNUP_SUCCESS:
return {
signup_loading: false,
data
}
case SIGNUP_FAILED:
return {
signup_loading: false,
signup_error: error
}
case LOGIN_REQUEST:
return {
login_loading: true
}
case LOGIN_SUCESS:
return {
login_loading: false,
data
}
case LOGIN_FAILED:
return {
login_loading: false,
login_error: error
}
default: return state
}
}
There are similar things that I want to do for each http call in the entire app:
before request I want to show loader for every http call
hide the loader after it’s finished
if error stop the loader and show the error msg
no way I want to copy paste above code across all the reducer
You can keep seperate actions like, SHOW_LOADER, HIDE_LOADER for handling the loader. These actions can be dispatched when you make a web api call, or when an api call return. These actions can be dispatched from anywhere in your application to handle the loader states. You will have to write that only in one reducer which can be called from anywhere.

AsyncStorage behaviour

I bumped into a weird behaviour of AsyncStorage that I couldn't wrap my head around and would appreciate anyone who can explain to me what's happening behind the scene (i.e. fail cases and why)
Here's a code I'm working on:
componentDidMount() {
let _this = this;
AsyncStorage.getItem('token', (err, data) => {
setTimeout(() => {
if(data !== null){
this.setState({isReady: true, isLoggedIn: true});
store.dispatch({type: t.LOGGED_IN, token: data});
}
else{
this.setState({isReady: true, isLoggedIn: false})
store.dispatch({type: t.LOGGED_OUT});
}
}, 3000)
console.log(err);
});
}
As you can see, I'm passing a callback function to getItem() as per the documentation, which basically tells me if the user has logged in before and hasn't logged out since(i.e. the token still persists in the device/app somewhere).
This code succeeded the first time, retrieving the old token I stored via reducer:
export default function authReducer(state = initialState, action)
{
switch (action.type) {
case t.LOGGED_IN:{
AsyncStorage.setItem('token', action.token);
return Object.assign({}, state, { isLoggedIn: true, token: action.token });
}
case t.LOGGED_OUT:{
AsyncStorage.removeItem('token');
return Object.assign({}, state, {isLoggedIn: false, token: null});
}
default:
return state;
}
}
However, on the second time I reloaded the app, the AsyncStorage will always fail to retrieve the data even after I tried logging in again and again.
I tried variations of AsyncStorage calls as well, i.e. using await, .then plus .catch, but they all lead to the same result.
My questions are:
On fail cases, I was under the impression that getItem() will still call the callback function I passed since there's an error on the param list. However, my console.log was never run in the above case. Am I expecting something I shouldn't be here?
Why would it only keep failing from second time around? Is there a case where calling setItem() on the same key more than once without ever deleting it will cause the storage to fail? (I know for sure that the first try was a success because I printed the retrieved token from async storage)
Does this have anything to do with the fact that I'm loading my app from Expo and initialized the app with CRNA? will this somehow make the asyncStorage persistence quality different?
thanks in advance! :)
EDIT: Upon further inspection, it seems stopping the packager and running it again seems to allow the app to once again succeed in retrieving the old token, but if I refresh the app again, after editing the code, getItem() will fail again. Is this a thing with Expo and persistent storage then?
Reducers must be free from side-effects, since it is a pure function.
As mentioned in the docs
They must be pure functions—functions that return the exact same output for given inputs. They should also be free of side-effects.
Side Effects - when a procedure changes a variable from outside its scope
A better approach would be to use redux-saga, that is like a separate thread for your side effects model.
So the problem is fixed now, thanks to a more experienced programmer friend of mine.
Turns out my mistake is that I put the AsyncStorage.setItem() call in reducer, which he said is deterministic in nature.
I moved the call to the actions.js of that class right before dispatch, and it works!
So instead of
export default function authReducer(state = initialState, action)
{
switch (action.type) {
case t.LOGGED_IN:{
AsyncStorage.setItem('token', action.token);
return Object.assign({}, state, { isLoggedIn: true, token: action.token });
}
case t.LOGGED_OUT:{
AsyncStorage.removeItem('token');
return Object.assign({}, state, {isLoggedIn: false, token: null});
}
default:
return state;
}
}
I did
export default function authReducer(state = initialState, action)
{
switch (action.type) {
case t.LOGGED_IN:{
return Object.assign({}, state, { isLoggedIn: true, token: action.token });
}
case t.LOGGED_OUT:{
return Object.assign({}, state, {isLoggedIn: false, token: null});
}
default:
return state;
}
}
Plus this
export function login(data, successCB, errorCB) {
return (dispatch) => {
api.login(data, function (success, data, error) {
if (success && data.exists) {
AsyncStorage.setItem('token', data.token); //NOTE THIS
dispatch({type: t.LOGGED_IN, token: data.token});
successCB(data);
}else if (error) errorCB(error)
});
};
}
export function signOut(successCB, errorCB){
return (dispatch) => {
AsyncStorage.removeItem('token'); //NOTE THIS
dispatch({type: t.LOGGED_OUT});
successCB();
}
}
But my question still persists (pardon the pun), "Why does that simple modification works? did I understand the mechanism of reducers and dispatchers wrong here?"
Also, what does being deterministic means and have to do with incompatibilities with Async calls?
If anyone can explain the concept to me that would be awesome! :D

Reusing the same reducer & epic for multiple Ajax calls like an api factory?

is using the same reducer to update different parts of state an anti pattern?
Like my data reducer has a GET_DATA_DONE action, updates state.data and then in another instance you use fetch something else and call GET_DATA_DONE to update state.somethingElse?
Or would you do something like GET_SOMETHING_DATA_DONE & so on.. multiple diff actions doing the same thing? (Hardly DRY)
reducers.js
export const reducer = (state, action) => {
switch (action.type) {
case actions.GET_DATA_REQUESTED:
return { ...state, isLoading: true };
case actions.GET_DATA_DONE:
return { ...state, isLoading: false, data: action.payload };
case actions.GET_DATA_FAILED:
return { ...state, isLoading: false, isError: true }
default:
return state;
}
};
actions.js
export function getDataRequested() {
return {
type: 'GET_DATA_REQUESTED'
};
}
export function getDataDone(data) {
return {
type: 'GET_DATA_DONE',
payload: data
};
}
export function getDataFailed(error) {
return {
type: 'GET_DATA_FAILED',
payload: error
};
};
export function getDataEpic(action$) {
return action$.ofType(GET_DATA_REQUESTED)
.mergeMap(action =>
ajax.getJSON(action.url)
.map(response => getDataDone(response))
.catch(error => getDataFailed(error))
);
}
What't the best way to structure the app such that , getDataEpic acts like a api factory and the data returned from getDataDone(response) can be passed to another reducer to update a part of state based on the action, for example a cities action using getDataDone reducer dispatches another action which updates state.cities with the response?
Edit: I've made an app with rx-observable & redux calling 3 different api's but I ended up with a lot of duplicate code and just not happy with the solution, so i want to build a properly architectured app
Hope I was clear enough.
Thanks a lot!
I don't think it's an anti-pattern if deals with state of it's own children, but if used too much, it could certainly get you in trouble. Having it modify totally unrelated state is a definite anti-pattern. According to the docs the first line hits the nail on the head.
For any meaningful application, putting all your update logic into a single reducer function is quickly going to become unmaintainable.
We're not talking about 'all our data' but we are talking about all the data from a single API call.
It's not too complicated in regards to setting a loading flag but in a real world app would get significantly more complicated for a reducer that is both setting a loading flag, error flag and 'data'. And that is assuming we even know what the data being requested is.
In your example, if the intention is to create an API factory through reducers, we have to assume an API could return any number of different data structures, right now it could be a string or an int but what if it's a deeply nested Object? How would you access this data and differentiate it between another piece of data?
Let's say we have an app with this data structure for just the errors:
{
"errors": {
"byId": {
"1": {
"code": 500,
"message": "There was an internal server error"
},
"2": {
"code": 400,
"message": "There was another error."
},
"3": {
"code": 999,
"message": "Wow! weird error."
},
},
"all": ["1", "2", "3"]
}
}
I might have a byId reducer that returns a computed key with another reducer as the value.
byId = (state={}, action) => {
if (action.type === 'ADD_ERROR') {
...state,
[action.id]:error_reducer(state[action.id], action)
} else {
return state
}
}
error_reducer might look like
errorReducer = (state={}, action) => {
if (action.type === 'ADD_ERROR') {
code: action.code,
message: action.message
} else {
return state
}
}
I think it makes more sense to have errorReducer handle both code and message because we know that they are both mutually inclusive pieces of data where as each error is mutually exclusive (different ids) and so require their own reducer.
Another major advantage of this when dealing with real-world applications is that when the data is separated one action can update state across MANY different areas of our app. When reducers handle multiple pieces of state these pieces of tied state become harder to update.
There are many different patterns you can employ with your reducers and none of them are wrong however I have found this pattern to work very well for me and i've used it successfully in a quite complex production app.
Having said all that, one possible approach for your AJAX function is to write a generic action that accepts an object that contains your dispatches.
By using a library like redux-thunk you can execute multiple dispatches to update different parts of your state with different pieces of data. I won't explain redux-thunk here as I think it's beyond the scope of the question.
an example object might look like this:
{
getDataRequested: function () {
return {
type: 'GET_DATA_REQUESTED'
};
},
getDataFailed: function (error) {
return {
type: 'GET_DATA_FAILED',
payload: error
};
},
getDataDone: function (data) {
return {
type: 'GET_DATA_DONE',
payload: data
};
}
}
then you can pass this object of callbacks to your main AJAX function along with your API endpoint, REST request type etc.
I hope this helps.

React - TypeError: this.props.courses.map is not a function

I have created a react-redux application. Currently what it does is load courses from the server(api), and displays them to the course component. This works perfectly. I'm trying to add a feature where you can create a course by posting it to the server, the server would then true an a success object. However, when i post to the server i get the following error(see below). I think this is due to my connect statement listening for the load courses action. Clearly its thinking it should be getting a list of something, instead of a success object. I have tried a few thing for it to listen for both courses and the success response, but to save you the time of reading all the strange thing i have done, i could not get it to work. Dose anyone know how to fix this issue ?
error
TypeError: this.props.courses.map is not a function
course.component.js
onSave(){
// this.props.createCourse(this.state.course);
this.props.actions.createCourse(this.state.course);
}
render() {
return (
<div>
<div className="row">
<h2>Couses</h2>
{this.props.courses.map(this.courseRow)}
<input
type="text"
onChange={this.onTitleChange}
value={this.state.course.title} />
<input
type="submit"
onClick={this.onSave}
value="Save" />
</div>
</div>
);
}
}
// Error occurs here
function mapStateToProps(state, ownProps) {
return {
courses: state.courses
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(courseActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Course);
course.actions.js
export function loadCourse(response) {
return {
type: REQUEST_POSTS,
response
};
}
export function fetchCourses() {
return dispatch => {
return fetch('http://localhost:3000/api/test')
.then(data => data.json())
.then(data => {
dispatch(loadCourse(data));
}).catch(error => {
throw (error);
});
};
}
export function createCourse(response) {
return dispatch => {
return fetch('http://localhost:3000/api/json', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
response: response
})
})
.then(data => data.json())
.then(data => {
dispatch(loadCourse(data));
}).catch(error => {
throw (error);
});
};
}
course.reducer.js
export default function courseReducer(state = [], action) {
switch (action.type) {
case 'REQUEST_POSTS':
return action.response;
default:
return state;
}
}
server.js
router.get('/test', function(req, res, next) {
res.json(courses);
});
router.post('/json', function(req, res, next) {
console.log(req.body);
res.json({response: 200});
});
i have tried added a response to the state, and listening for it in the map state to props, but still for some reason react is trying to map response to courses. Do i need a second connect method?
function mapStateToProps(state, ownProps) {
return {
courses: state.courses,
resposne: state.resposne
};
}
As you can see from the pictures response is getting mapped as courses and not as response.
Picture
Assumptions:
state.courses is initially an empty array - from course.reducer.js
You don't call fetchCourses() action the first time you are rendering your view
Even if you call fetchCourses() there is no problem as long as courses in server.js is an array (the array in the response replaces the initial state.courses)
Flow:
Now I assume the first render is successful and React displays the <input type="text"> and submit button. Now when you enter the title and click on the submit button, the onSave() method triggers the createCourse() action with parameter that is more or less similar to { title: 'something' }.
Then you serialize the above mentioned param and send to the server (in course.actions.js -> createCourse()) which in turn returns a response that looks like {response: 200} (in server.js). Response field is an integer and not an array! Going further you call loadCourses() with the object {response: 200} which triggers the courseReducer in course.reducer.js
The courseReducer() replaces state.courses (which is [] acc. to assumption) with an integer. And this state update triggers a re-render and you end up calling map() on an integer and not on an array, thus resulting in TypeError: this.props.courses.map is not a function.
Possible Solution:
Return a valid response from serve.js (i.e. return the course object the endpoint is called with), or
Update your reducer to add the new course object into the existing state.courses array, like, return [...state, action.response]
Update:
Based on OP's comment, if what you want to do is send the new course object to the server, validate it and send success (or error) and based on response add the same course object to the previous list of courses, then you can simply call loadData() with the same course object you called createCourse() with and (as mentioned above) inside your reducer, instead of replacing or mutating the old array create a new array and append the course object to it, in es6 you can do something like, return [...state, course].
Update 2:
I suggest you go through Redux's Doc. Quoting from Redux Actions' Doc
Actions are payloads of information that send data from your application to your store. They are the only source of information for the store.
The createCourse() action is called with a payload which is more-or-less like,
{title: 'Thing you entered in Text Field'}, then you call your server with an AJAX-request and pass the payload to the server, which then validates the payload and sends a success (or error) response based on your logic. The server response looks like, {response: 200}. This is end of the createCourse()action. Now you dispatch() loadCourses() action from within createCorse(), with the response you received from the server, which is not what you want (based on your comments). So, instead try dispatch()ing the action like this (try renaming response param, it's a bit confusing)
//.....
.then(data => {
dispatch(loadCourse(response)); // the same payload you called createCourse with
})
//.....
Now, loadCourse() is a very basic action and it simply forwards the arguments, which Redux uses to call your reducer. Now, in case you followed the previous discussion and updates how you call loadCourse(), then the return from loadCourse() looks like
{
type: REQUEST_POSTS,
response: {
title: 'Thing you entered in Text Field',
}
}
which is then passed onto your reducer, specifically your courseReducer().
Again quoting from Redux Reducers' Doc
Actions describe the fact that something happened, but don't specify how the application's state changes in response. This is the job of reducers.
The reducer must define the logic on how the action should impact the data inside the store.
In your courseReducer(), you simply returns the response field inside the action object and [expect] Redux to auto-magically mutate your state! Unfortunately this is not what happens :(
Whatever you return from the reducer, completely replaces whatever thing/object was there before, like, if your state looks like this
{ courses: [{...}, {...}, {...}] }
and you return something like this from your reducer
{ title: 'Thing you entered in Text Field'}
then redux will update the state to look like
{ courses: { title: 'Thing you entered in Text Field'} }
state.courses is no longer an Array!
Solution:
Change your reducer to something like this
export default function courseReducer(state = [], action) {
switch (action.type) {
case 'REQUEST_POSTS':
return [...state, action.response]
default:
return state
}
}
Side Note: This is may be confusing at times, so just for the sake of record, state inside courseReducer() is not the complete state but a property on the state that the reducer manages. You can read more about this here
--Edit after reading a comment of you in a different answer, I've scraped my previous answer--
What you're currently doing with your actions and reducers, is that you're calling loadCourse when you fetched the initial courses. And when you created a new course, you call loadCourse too.
In your reducer you're directly returning the response of your API call. So when you fetch all the courses, you get a whole list of all your courses. But if you create a new one you currently receive an object saying response: 200. Objects don't have the map function, which explains your error.
I would suggest to use res.status(200).json() on your API and switching the response status in your front-end (or using then and catch if you can validate the response status, axios has this functionality (validateStatus)).
Next I would create a separate action-type for creating posts and dispatch that whenever it's successful.
I would change your reducer to something like
let initialState = {
courses: [],
createdCourse: {},
}
export default (state = initialState, action) => {
switch (action.type) {
case 'REQUEST_POSTS':
return {
...state,
courses: action.response
}
case 'CREATE_COURSE_SUCCESS':
return {
...state,
createdCourse: action.response,
}
default: return state;
}
}
I wouldn't mind looking into your project and giving you some feedback on how to improve some things (ES6'ing, best practices, general stuff)
Based on the questions & answers so far, it looks like you need to do something like this:
1) Add a new action and dispatch this from your createCourse function
export function courseAdded(course, response) {
return {
type: 'COURSE_ADDED',
course
response
};
}
export function createCourse(course) {
return dispatch => {
return fetch('http://localhost:3000/api/json', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
course
})
})
.then(response => response.json())
.then(response => {
dispatch(courseAdded(course, response));
}).catch(error => {
throw (error);
});
};
}
2) Change your reducers to handle both fetching courses and adding a new course (we're using combineReducers to handle this here)
import { combineReducers } from "redux";
function response(state = null, action) {
switch (action.type) {
case 'COURSE_ADDED':
return action.response;
default:
return state;
}
}
function courses(state = [], action) {
switch (action.type) {
case 'COURSE_ADDED':
return [...state, action.course];
case 'REQUEST_POSTS':
return action.response;
default:
return state;
}
}
export default combineReducers({
courses,
response
});
3) Hook up to the new response state in your connect component
function mapStateToProps(state, ownProps) {
return {
courses: state.courses,
response: state.response
};
}
4) Do something with this new response prop in your component if you want to show it e.g.
// this is assuming response is a string
<span className="create-course-response">
Create Course Response - {this.props.response}
</span>
UPDATE
I've added support for adding the new course to the end of the existing course list, as well as handling the response. How you shape the state is completely up to you and it can be re-jigged accordingly.
In order for this code to work, you will need to add support for the spread operator. If you are using babel it can be done like this. Creating a new object is important to ensure that you don't mutate the existing state. It will also mean react-redux knows the state has changed. Spread operator isn't essential and this can be done with Object.assign, but that syntax is ugly IMO.

Categories