This is my reducer code, please let me know this is the correct format the reducer should be ?
I am a newbie to Redux please help me with this problem. I have gone through the documentation but i didn't get the context of this, if i am wrong please let me know where i am wrong
Below is my reducer code.
export default createReducer(initialState, {
[SELECT_ENTITY]: (state, data) => {
let selectedEntity;
if (data.isSection) {
// Assert the section exists
selectedEntity = state.sections.filter(section => section.id === data.entityId)[0];
} else {
const lectures = state.sections.reduce((acc, section) => {
return (acc.concat(section.lectures));
},[]);
selectedEntity = lectures.filter(lecture => lecture.id === data.entityId)[0];
}
let newState;
let propertyWindowIsActive;
if (state.propertyWindowIsActive) {
if(state.entity.id === data.entityId && state.isSection === data.isSection) {
// propertyWindowIsActive = false;
newState = Object.assign({}, state, {propertyWindowIsActive: false, entity:{}, isSection: null});
} else {
// propertyWindowIsActive = true;
newState = Object.assign({}, state, {entity: selectedEntity, isSection: data.isSection});
}
} else {
// propertyWindowIsActive = true;
newState = Object.assign({}, state, {propertyWindowIsActive: true, entity: selectedEntity, isSection: data.isSection});
}
return (newState);
}
}
Please carefully format your code when posting questions. Even after copying and pasting your code into a text editor and fixing up the alignment, it doesn't appear to be valid JavaScript.
Since you're new to Redux, I suggest you avoid wrappers like "createReducer". Just write your reducers as pure functions. They should always have two parameters state and action and return the new state. Your reducer should have a switch statement based on action.type.
Please read the Redux docs on reducers.
http://redux.js.org/docs/basics/Reducers.html
Related
I have a backend method called LikeExists() to verify if a certain user has liked a certain post.
public async Task<bool> LikeExists(int postId)
{
var post = await _postRepository.GetPostByIdAsync(postId);
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
if (_context.Likes.Where(i => i.PostId == post.Id && i.UserId == user.Id).FirstOrDefault() != null) return true;
return false;
}
The method works fine in Postman, but it does not do the job in Angular. If a user presses a like button I first want to check if this user has already liked this post. If he has, he will unlike it and the like will be deleted from the database. If he hasn't liked it, he will like it and the like will be saved in the database.
likeExists(){
this.postService.likeExists(this.post.id).subscribe((response: boolean) =>{
this.like = response;
});
}
likePost() {
if(this.likeExists){
this.postService.likePost(this.post.id, this.model).subscribe((response: Like) => {
this.likee = response;
console.log(response);
this.toastr.success('Liked');
}, error => {
console.log(error);
this.toastr.error(error.error);
});
} else {
this.postService.deleteLike(this.post.id).subscribe(() => {
this.toastr.success('Unliked');
}, error => {
console.log(error);
})
}
}
The problem is it always enters the if{} clause and never the else{} clause. The method below returns an Observable. I think the problem is that it must return a boolean. How can I make this work?
This is the method in the postService:
likeExists(postId: number) {
return this.http.get(this.baseUrl + 'like/exists/' + postId);
}
Try avoiding nested subscription since it will result in an unreadable and hard to maintain code, use rxjs pipes with operators instead, try something like this:
let id= this.post.id;
let likeExists$ = this.postService.likeExists(id);
likeExists$
.pipe(
switchMap(likeExists => {
if (likeExists) {
// delete like
return this.postService.deleteLike(id);
}
// otherwise addlike
return this.postService.addLike(id);
})
).subscribe(
res=> this.toastr.success('Success'),
err=> this.toastr.error('Failed')
);
or even shorter
let id= this.post.id;
let likeExists$ = this.postService.likeExists(id);
likeExists$
.pipe(switchMap(
liked => liked ? this.postService.deleteLike(id) : this.postService.addLike(id)}))
.subscribe(
res=> this.toastr.success('Success'),
err=> this.toastr.error('Failed')
);
The problem is that this.http.get is asynchronous, which means that likeExists returns before this.like is being set. You need to wait for the value to be returned in your observable. Refactor your code to something along these lines:
likePost() {
// Check to see if like exists and wait for response from server
this.postService.likeExists(this.post.id).subscribe((response: boolean) => {
this.like = response;
if (this.like) {
this.postService.likePost(this.post.id, this.model).subscribe((response: Like) => {
this.likee = response;
console.log(response);
this.toastr.success('Liked');
}, error => {
console.log(error);
this.toastr.error(error.error);
});
} else {
this.postService.deleteLike(this.post.id).subscribe(() => {
this.toastr.success('Unliked');
}, error => {
console.log(error);
})
}
});
}
Also, this is a nice guide to asynchronous concepts in general. And the RxJS docs have a bunch of helpful information to get started.
I have a cart items in my store and i want to check if the user added the same item with the same color and size.
If so i just want to increase the quantity, if not just add it but it creates kinda akward reducer and i wanted to know if this considered as an anti pattern and if so, what can we do to simplify it
export default (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
// Check to see if we already have the same product
// with the same color and size in our cart
const productIndex = state.products
.findIndex(product => product._id === action.product._id
&& product.size === action.product.size
&& product.color === action.product.color);
// if we have it already increase the quantity
if (productIndex !== -1) {
const updatedProduct = {
...state.products[productIndex],
quantity: state.products[productIndex].quantity + action.product.quantity,
};
return {
...state,
products: [
...state.products.slice(0, productIndex),
updatedProduct,
...state.products.slice(productIndex + 1),
],
};
}
// if not just save it
return {
...state,
products: [...state.products, action.product],
};
default:
return state;
}
};
No, it's absolutely not an anti-pattern. Reducers can, and should, do a lot more than just return {...state, ...action.payload}. Now, what logic you put in a reducer is up to you, as well as what abstractions you choose to use in the process.
If you're concerned about writing nested immutable update logic, then I'd suggest looking at the immer immutable update library. I'd also suggest trying out our new redux-starter-kit package, which uses immer internally.
I have a (ngrx) store for an array of Speaker object and for the SelectedSpeaker. The reducer looks like:
export const speakers = (state: any = [], { type, payload }) => {
switch (type) {
case SpeakerActions.TOGGLEFAVORITE:
return state.map(speaker => {
return speaker.id === payload.id ? _.assign({}, speaker, {isFavorite: !speaker.isFavorite}) : speaker;
});
}
}
I left out the unimportant code. The reducer for currentSpeaker looks like:
export const selectedSpeaker = (state: any = [], { type, payload }) => {
switch (type) {
case SelectedSpeakerActions.SELECT:
return payload;
}
}
Now my question, if I dispatch a SpeakerActions.TOGGLEFAVORITE for a speaker and this happens to be the SelectedSpeaker, how do I update the SelectedSpeaker in this case? Note this all works as part of an Angular2 project, for what that worth.
Generally, Redux state should be fully normalized - you shouldn't have some state in two places, since it creates exactly the problem you are seeing.
Probably the best solution in your case is for selectedSpeaker just to contain the id of the selected speaker, not the speaker itself. e.g. something like
export const selectedSpeaker = (state: any = [], { type, payload }) => {
switch (type) {
case SelectedSpeakerActions.SELECT:
return payload.id;
}
}
Obviously, you'll need to lookup the selected speaker where you use it, using the ID. You might also find it easier to have an object (or Map) from id=>speaker in your speaker store, rather than a plain array.
So, I see on an error, redux-promise hands me back error: true, along with the payload, but that is once it hits the reducer... to me, decoupling the request AND error condition is a bit odd, and seems inappropriate. What is an effective way to also deal with error condition when using axios w/ reduc-promise (middleware).. here is the gist of what i have..
in action/
const request = axios(SOME_URL);
return {
type: GET_ME_STUFF,
payload: request
}
in reducer/
const startState = {
whatever: [],
error: false
}
case GET_ME_STUFF:
return {...state, startState, {stuff:action.payload.data, error: action.error? true : false}}
etc... then I can deal with the error.. so, my api call is now split into two seperate areas and that seems wrong.... there must be something I am missing here. I would think in the /actions I can pass in a callback that handles a new action etc.. or something, but not split it.
I've had to go through a similar situation. The challenge is that you likely won't be able to evaluate the results of the promise until it is at the reducer. You could handle your exceptions there but it's not the best pattern. From what I've read reducers are meant only to return appropriate pieces of state based on action.type and do nothing else.
So, enter an additional middleware, redux-thunk. Instead of returning an object, it returns a function, and it can coexist with promise.
It's explained quite well at http://danmaz74.me/2015/08/19/from-flux-to-redux-async-actions-the-easy-way/ [archived here]. Essentially, you can evaluate the promise here and dispatch through the other action creators before the promise result hits the reducers.
In your actions file, add additional action creators that would handle the success and error (and any other) states.
function getStuffSuccess(response) {
return {
type: GET_ME_STUFF_SUCCESS,
payload: response
}
}
function getStuffError(err) {
return {
type: GET_ME_STUFF_ERROR,
payload: err
}
}
export function getStuff() {
return function(dispatch) {
axios.get(SOME_URL)
.then((response) => {
dispatch(getStuffSuccess(response))
})
.catch((err) => {
dispatch(getStuffError(err))
})
}
}
return null
This is roughly to how you might translate your pseudocode to what is explained at the link. This handles evaluating the promise directly in your action creator and firing off the appropriate actions and payloads to your reducers which follows the convention of action -> reducer -> state -> component update cycle. I'm still pretty new to React/Redux myself but I hope this helps.
The accepted answer doesn't make use of redux-promise. Since the question is actually about handling errors using redux-promise I provide another answer.
In the reducer you should inspect the existence of the error attribute on the action object:
// This is the reducer
export default function(previousState = null, action) {
if (action.error) {
action.type = 'HANDLE_XHR_ERROR'; // change the type
}
switch(action.type) {
...
And change the type of the action, triggering a state change for an error handling component that you have set up for this.
You can read a bit more about it here on github.
It looks like you can catch the error where you make the dispatch, then make an separate error dispatch if it happens. It's a bit of a hack but it works.
store.dispatch (function (dispatch) {
dispatch ({
type:'FOO',
payload:axios.get(url)
})
.catch (function(err) {
dispatch ({
type:"FOO" + "_REJECTED",
payload:err
});
});
});
and in the reducer
const reducer = (state=initialState, action) => {
switch (action.type) {
case "FOO_PENDING": {
return {...state, fetching: true};
}
case "FOO_REJECTED": {
return {...state, fetching: false, error: action.payload};
}
case "FOO_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
data: action.payload,
};
}
}
return state;
};
Still using redux-promises you can do something like this which I think is an elegant way to deal with this problem.
First, set a property in the redux state that will hold any ajax errors that may occurred.
ajaxError: {},
Second, setup a reducer to handle ajax errors:
export default function ajaxErrorsReducer(state = initialState.ajaxError, action) {
if (action.error) {
const { response } = action.payload;
return {
status: response.status,
statusText: response.statusText,
message: response.data.message,
stack: response.data.stack,
};
}
return state;
}
Finally, create a very simple react component that will render errors if there are any (I am using the react-s-alert library to show nice alerts):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Alert from 'react-s-alert';
class AjaxErrorsHandler extends Component {
constructor(props, context) {
super(props, context);
this.STATUS_GATE_WAY_TIMEOUT = 504;
this.STATUS_SERVICE_UNAVAILABLE = 503;
}
componentWillReceiveProps(nextProps) {
if (this.props.ajaxError !== nextProps.ajaxError) {
this.showErrors(nextProps.ajaxError);
}
}
showErrors(ajaxError) {
if (!ajaxError.status) {
return;
}
Alert.error(this.getErrorComponent(ajaxError), {
position: 'top-right',
effect: 'jelly',
timeout: 'none',
});
}
getErrorComponent(ajaxError) {
let customMessage;
if (
ajaxError.status === this.STATUS_GATE_WAY_TIMEOUT ||
ajaxError.status === this.STATUS_SERVICE_UNAVAILABLE
) {
customMessage = 'The server is unavailable. It will be restored very shortly';
}
return (
<div>
<h3>{ajaxError.statusText}</h3>
<h5>{customMessage ? customMessage : ajaxError.message}</h5>
</div>
);
}
render() {
return (
<div />
);
}
}
AjaxErrorsHandler.defaultProps = {
ajaxError: {},
};
AjaxErrorsHandler.propTypes = {
ajaxError: PropTypes.object.isRequired,
};
function mapStateToProps(reduxState) {
return {
ajaxError: reduxState.ajaxError,
};
}
export default connect(mapStateToProps, null)(AjaxErrorsHandler);
You can include this component in your App component.
This might not be the best approach but it works for me. I pass the 'this' of my component as var context. Then when i get response back i just execute the methods defined in my components context. In my component i have successHdl and errorHdl. From there i can trigger more redux actions as normal. I checked all the previous answers and seem too daunting for such a trivial task.
export function updateJob(payload, context){
const request = axios.put(UPDATE_SOMETHING, payload).then(function (response) {
context.successHdl(response);
})
.catch(function (error) {
context.errorHdl(error);
});;
return {
type: UPDATE_SOMETHING,
payload: payload,
}
}
Don't use redux-promise. It overcomplicates something that's actually super simple to do yourself.
Instead read the redux docs: http://redux.js.org/docs/advanced/AsyncActions.html
It'll give you a much better understanding of how to handle this kind of interactions and you'll learn how to write something (better than) redux-promise yourself.
What I want is the root reducer combine other reducers, and listen to extra actions. I've find the docs, but I can not get any information.
Here is some pseudo code.
const root1 = combineReducers({
reducer1,
reducer2,
reducer3,
reducer4
});
function root2(state = initState, action) {
switch (action.type) {
case LOAD_DATA:
return _.assign({}, initState, action.data);
default:
return state;
}
}
merge(root1, root2);
The only way I figure out is to drop combineReducers:
function root(state = initState, action) {
switch (action.type) {
case LOAD_DATA:
return _.assign({}, initState, action.data);
case ...: return ...;
case ...: return ...;
case ...: return ...;
default: return state;
}
}
Is there another way to implement this?
Yes, you can use combineReducers() with multiple reducers while also having an action that rebuilds your entire application state. Admittedly, that is a bit of a strange design decision and does not scale very well with more complex apps, but you obviously have a use-case. If you want to do something like that you have two choices.
Option 1: Divide up action
It is totally valid to listen for the same action type within multiple reducer functions. This is the most straightforward approach, although it involves more repetition. You would just break out each piece of state returned by your action into the individual reducer functions it applies to.
For instance, if this was your entire application state
{
foo: {},
bar: {}
}
And your action type that rebuilt the entire application state was LOAD_DATA, you could do this
function foo (state = {}, action) {
switch (action.type) {
case 'LOAD_DATA':
return {...state, action.result.foo}
}
}
function bar (state = {}, action) {
switch (action.type) {
case 'LOAD_DATA':
return {...state, action.result.bar}
}
}
const root = combineReducers({
foo,
bar
});
With that, both foo and bar in your state would always get rebuilt with the corresponding data coming from the same action.
Option 2: Build Custom combineReducers()
There is nothing stopping you from building your own version of combineReducers(). If you watch this video on building a combineReducers() function from scratch, you'll see that the logic in place is not that complicated. You would just have to listen for the specific action type and return the entire state from that action if it matched. Here's a version of that I built by looking at the current source for combineReducers() and then working the 2 util functions into that function
function combineReducers(reducers) {
var fn = (val) => typeof val === 'function';
var finalReducers = Object.keys(reducers).reduce((result, key) => {
if (fn(reducers[key])) {
result[key] = reducers[key]
}
return result
}, {});
return function combination(state = {}, action) {
if (action.type === 'LOAD_DATA') {
return completeStateReducer(action)
} else {
var hasChanged = false
var fn = (reducer, key) => {
var previousStateForKey = state[key]
var nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
return nextStateForKey
}
var finalState = Object.keys(finalReducers).reduce((result, key) => {
result[key] = fn(finalReducers[key], key)
return result
}, {})
return hasChanged ? finalState : state
}
}
}
function completeStateReducer(action) {
return action.result;
}
Outside of merging those util functions back in, the only thing I really added was the bit about listening for the LOAD_DATA action type and then calling completeStateReducer() when that happens instead of combining the other reducer functions. Of course, this assumes that your LOAD_DATA action actually returns your entire state, but even if it doesn't, this should point you in the right direction of building out your own solution.
First, combineReducers is merely a utility function that simplifies the common use case of "this reducer function should handle updates to this subset of data". It's not required.
Second, that looks like pretty much the exact use case for https://github.com/acdlite/reduce-reducers. There's an example here: https://github.com/reactjs/redux/issues/749#issuecomment-164327121
export default reduceReducers(
combineReducers({
router: routerReducer,
customers,
stats,
dates,
filters,
ui
}),
// cross-cutting concerns because here `state` is the whole state tree
(state, action) => {
switch (action.type) {
case 'SOME_ACTION':
const customers = state.customers;
const filters = state.filters;
// ... do stuff
}
}
);
Also, I give an example of using reduceReducers in the "Structuring Reducers" section of the Redux docs: http://redux.js.org/docs/recipes/reducers/BeyondCombineReducers.html .