I built an app and added CRUD functionality and everything works fine except the edit functionality. The problem is when I try to edit its actually hitting the right database and updates the entry but in the react app its just force updates all the entries to particular one entry.
Update Saga :-
function* updateFeedbackSaga(action) {
try {
const updateData = yield call(api.feedback.edit, action.payload);
yield put(actions.updateFeedback(updateData));
console.log(updateData);
} catch (err) {
yield put(actions.updateFeedbackErrors(err.response.data));
}
}
Edit Reducer
import * as actionTypes from "../Actions/types";
const initialState = {
feedbacks: [],
feedback: {},
loading: false
};
export default (state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_FEEDBACK:
return {
...state,
feedbacks: state.feedbacks.map(
feedback =>
feedback.id === action.payload.id ? action.payload : feedback
)
};
default:
return state;
}
};
Actions
//Edit and update Feedback
export const updateFeedbackRequest = newFeedbackData => ({
type: actionTypes.UPDATE_FEEDBACK_REQUEST,
payload: newFeedbackData
});
export const updateFeedback = updatedData => ({
type: actionTypes.UPDATE_FEEDBACK,
payload: updatedData
});
export const updateFeedbackErrors = errors => ({
type: actionTypes.GET_ERRORS,
payload: errors
});
That's how printing
<section className = "feedback">
<div className = "employees__table" >
<h4 className = "semi-heading" > Feedback Table < /h4>
{
FeedbackList feedbacks = {feedbacks} />
}
</div>
</section >
const mapStateToProps = state => ({
feedbackList: selectors.FeedbackSelector(state)
});
HERE ARE THE IMAGES
This is my feedbacklist
If I edit the first item then state is like this
My feedbacklist is repeating edited feedback. I don't know where i am doing wrong.
Here is my database
Here is the working code:
https://codesandbox.io/s/github/montygoldy/employee-review/tree/master/client
login: montyjatt#gmail.com
password: 12345678
do you need to loop over all feedback when you already know the updated I'd?
case actionTypes.UPDATE_FEEDBACK:
return {
...state,
feedbacks[action.payload.id]: action.payload.body
};
This will only update a single item because the ID is part of the key.
The way you have it currently the feedbacks will all be replaced by the single value that matches the ID.
If you're planning on sending multiple id's then you'll want to use the spread operator.
case actionTypes.UPDATE_FEEDBACK:
return {
...state,
feedbacks: {
...state.feedbacks,
...action.payload
}
};
In this case you're spreading out the old feedback items and then using the new payload with the spread operator to overwrite only the ones with matching id's.
Of course this means the action.payload should match your feedback structure.
Ok SO I have found the fix, actually, it's my id reference in the reducer was incorrect.
correct way is
case actionTypes.UPDATE_FEEDBACK:
return {
...state,
feedbacks: state.feedbacks.map(
feedback =>
feedback._id === action.payload._id ? action.payload : feedback
)
};
Related
How can I remove a specific item (by id) from localstorage using react (redux - persist)? handleSubmit is working fine, but handleDelete, is not. I have this:
handleSubmit = event => {
event.preventDefault();
this.props.addWeather(this.state.weatherCity);
this.setState({ weatherCity: "" });
};
handleDelete = (event, id) => {
this.props.deleteWeather(this.state.weatherCity);
this.setState({ weatherCity: "" });
}
const mapStateToProps = state => ({
allWeather: state.allWeather
});
const mapDispatchToProps = dispatch =>
bindActionCreators(WeatherActions, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(WeatherList);
And button in form to call handleDelete:
<form onSubmit={this.handleDelete}><button type="submit" id="add" onClick={this.handleDelete}>Remove City</button></form>
My localstorage:
allWeather: "[{\"id\":0.5927975642362653,\"city\":\"Toronto\"},{\"id\":0.8124764603718682,\"city\":\"Fortaleza\"},{\"id\":0.9699736666575081,\"city\":\"Porto\"},{\"id\":0.852871998478355,\"city\":\"Tokio\"},{\"id\":0.8854642571682461,\"city\":\"New York\"}]"
My reducer:
export default function allWeather(state = [], action) {
switch (action.type) {
case "ADD_WEATHER":
return [...state, { id: Math.random(), city: action.payload.city }];
case "DELETE_ITEM":
return [...state, state.weatherCity.filter((event, id) => id !== action.payload.id)];
default:
return state;
}
}
And actions:
export const deleteWeather = id => ({
type: "DELETE_ITEM",
payload: { id }
});
I appreciate any help.
Your problem is that you are using the spread operator, which copies the content of the current state first. Then you are adding the items that were returned from the filter method. So you aren't deleting but adding. To delete from an array use the filter method only, without the spread operator like that:
return state.filter( (city) => city.id !== action.payload.id )
Also the state is an array, not an object, so this is invalid state.weatherCity.
I am using redux to put products in a cart for a react native project. At the moment it's functional, but I can add duplicate items. I want to prevent that.
What's the best way to modify the reducer that will stop storing duplicates?
My Reducer:
const cartItems = (state = [], action) => {
//action type are the constatns
switch (action.type) {
case ADD_TO_CART:
// TODO: dont add duplicates
return [...state, action.payload];
case REMOVE_TO_CART:
//filter through the single item that matches payload and remove it
return state.filter(cartItem => cartItem !== action.payload);
case CLEAR_TO_CART:
//return empty state which clears cart
return (state = []);
}
//return the state
return state;
};
My action:
export const addToCart = (payload) => {
return {
type: ADD_TO_CART,
payload,
}
}
Use find to check to see if an object with that product ID exists in state. If it does return the state otherwise return the updated state.
const { product_id } = action.payload;
const dupe = state.find(obj => obj.product_id === product_id);
return dupe ? state : [...state, action.payload ];
You can add some code before doing something like:
return {...state, cart: [...state.cart].push(payload)}
. for example:
const lookForCart = state?.cart?.find(crt => crt?.cardId === payload?.cardId)
if (lookForCart) return state
return {...state, cart: [...state.cart].push(payload)}
you must check duplicate first before call action add_cart
case 1: if not has exists => push in array redux store
case 2: if has item => consider change property example increase number quantity product
You should filter out the product if it is in the Store and add the new action.payload
This will ensure that payload quantity, price, total, quantity is updated
Code:
case ADD_TO_CART:
// TODO: dont add duplicates
return [...state.filter(p => p.id !== action.payload.product_id), action.payload];
Using an api for anime called Jikan, I'm trying to display promo thumbnails of new anime shows.
I'm using two api calls, one to get the new anime shows:
export const get_new_anime = () =>
`${base_url}search/anime?q&order_by=score&status=airing&sort=desc`;
and one for getting the videos (containing promos) of anime by getting its id.
export const get_news = (anime_id) => `${base_url}anime/${anime_id}/videos`;
In my home page, here I am mapping the shows, returning a component for each anime:
<Promos>
{new.map((anime, index) => (
<Anime key={anime.mal_id} index={index}></Anime>))}
</Promos>
And for each Anime component, I have a useEffect which uses useDispatch for every new id
const Anime = ({ id, index }) => {
const dispatch = useDispatch();
const loadDetailHandler = () => {
// eslint-disable-line react-hooks/exhaustive-deps
dispatch(loadDetail(id));
useEffect(() => {
loadDetailHandler(id);
}, [id]); // eslint-disable-line react-hooks/exhaustive-deps
const promo = useSelector((state) => state.detail.promo);
const isLoading = useSelector((state) => state.detail.isLoading);
return (
<PromoBox
style={
!isLoading
? { backgroundImage: `url("${promo[index][0].image_url}")` }
: null
}
></PromoBox>);
};
Here is how my promoReducer looks like:
const initState = {
promo: [],
isLoading: true,
};
const promoReducer = (state = initState, action) => {
switch (action.type) {
case "LOADING_PROMO":
return {
...state,
isLoading: true,
};
case "GET_DETAIL":
return {
...state,
promo: [...state.promo, action.payload.promo],
isLoading: false,
};
default:
return { ...state };
}
};
export default promoReducer;
and here is the promoAction:
export const loadPromo = (id) => async (dispatch) => {
dispatch({
type: "LOADING_PROMO",
});
const promoData = await axios.get(get_promos(id));
dispatch({
type: "GET_DETAIL",
payload: {
promo: promoData.data.promo,
},
});
};
While it does return the promo data as the action is dispatched, the problem is that in some instances of dispatching, no data is returned. Here is a screenshot from redux devtools to show what I mean:
and I was trying to get the promos of all the new anime, in which I was expecting to get 50 results of promo data. In devtools, you can see I only got 9 of them. This is followed by an error 429 (too many requests):
How can I resolve this issue? And is there a better way to do this, because this seems like bad practice:
Well it seems that you're limited by the api itself and it's threshold for the number of request per unit of time. There should probably be a request that allows you to pass multiple anime ids to get request in order to avoid requesting details for each anime individually.
My component is set to display a list of books, as card thumbnails. Each item from the list of books is generated by this component.
Each Card has a favorites icon, when clicking it adds the book to favoriteTitles array. By pressing again on the favorites icon it removes it from the list.
const Card = ({ title, history }) => {
const dispatch = useDispatch();
const { favoriteTitles } = useSelector(({ titles }) => titles);
const { id, name, thumbnail } = title;
const [favorite, setFavorite] = useState(favoriteTitles?.some(item => item.titleId === title.id));
const handleFavoriteClick = () => {
const isFavorite = favoriteTitles?.some(item => item.titleId === title.id);
if (isFavorite) {
dispatch(removeFavoriteTitle(title));
setFavorite(false);
} else {
dispatch(addFavoriteTitle(title));
setFavorite(true);
}
};
return (
<CardContainer>
<Thumbnail thumbnail={thumbnail} />
{name}
<FavesIcon isActive={favorite} onClick={handleFavoriteClick} />
</CardContainer>
);
};
The issue with this component is when you press once on FavesIcon to add, and if you changed your mind and want to remove it and press right away again, the favoritesTitles array still has the old value.
Let's suppose our current favorites list looks like this:
const favoritesTitles = [{titleId: 'book-1'}];
After pressing on favorites icon, the list in Redux gets updated:
const favoritesTitles = [{titleId: 'book-1'}, {titleId: 'book-2'}];
And if I press again to remove it, the favoritesTitles array inside the component is still the old array with 1 item in it. But if I look in Redux the list updated and correct.
How component should get the updated Redux value?
Update
I have specific endpoints for each action, where I add or remove from favorites:
GET: /users/{userId}/favorites - response list eg [{titleId: 'book-1'}, {titleId: 'book-2'}]
POST: /users/me/favorites/{titleId} - empty response
DELETE: /users/me/favorites/{titleId} - empty response
For each action when I add or remove items, on success request I dispatch the GET action. Bellow are my actions:
export const getFavoriteTitles = userId =>
apiDefaultAction({
url: GET_FAVORITE_TITLES_URL(userId),
onSuccess: data => {
return {
type: 'GET_FAVORITE_TITLES_SUCCESS',
payload: data,
};
},
});
export const addFavoriteTitle = (userId, id) => (dispatch, getState) => {
return dispatch(
apiDefaultAction({
method: 'POST',
url: SET_FAVORITE_TITLES_URL,
data: {
titleId: id,
},
onSuccess: () => {
dispatch(getFavoriteTitles(userId));
return { type: 'SET_FAVORITE_TITLE_SUCCESS' };
},
})
);
};
My reducers are pretty straight forward, I'm not mutating any arrays. Since only GET request is returning the list of array, I don't do any mutating in my reducers:
case 'GET_FAVORITE_TITLES_SUCCESS':
return {
...state,
favoriteTitles: action.payload,
};
case 'SET_FAVORITE_TITLE_SUCCESS':
return state;
case 'DELETE_FAVORITE_TITLE_SUCCESS':
return state;
It seems that by the time you click FavesIcon second time after adding to favourites, GET: /users/{userId}/favorites request is still pending and favoriteTitles list is not updated yet. That's why the component still contains an old value.
You need to update favoriteTitles list right away after triggering addFavoriteTitle or removeFavoriteTitle actions, without waiting GET_FAVORITE_TITLES_SUCCESS action to be dispatched. This pattern is called 'Optimistic UI':
export const toggleFavorite = itemId => {
return {
type: 'TOGGLE_FAVORITE',
payload: { itemId },
};
}
export const addFavoriteTitle = (userId, id) => (dispatch, getState) => {
dispatch(toggleFavorite(id));
return dispatch(
...
);
};
export const removeFavoriteTitle = (userId, id) => (dispatch, getState) => {
dispatch(toggleFavorite(id));
return dispatch(
...
);
};
And your reducer can look something like this:
case 'TOGGLE_FAVORITE':
return {
...state,
favoriteTitles: state.favoriteTitles.map(item => item.titleId).includes(action.payload.itemId)
? state.favoriteTitles.filter(item => item.titleId !== action.payload.itemId)
: [...state.favoriteTitles, { titleId: action.payload.itemId }],
};
UPD. Please, check out a minimal working sandbox example
I have a modal containing a button that fires a HTTP request, at which point the displayed html will change depending on a successful/error response from the server, where the response changes a state prop that is dealt with in the mapStatesToProps function.
The issue I have now is that I am wanting to reset the modal to its initial state pre-request when I close it.
I had previously done this by using local component state but have since updated the functionality to use the request mapped state props shown above.
I am curious if it possible to reset the state without firing a dispatch to a random URI?
Component.jsx
const mapStatesToProps = ({myState}) => ({
response: myState.response,
success: !!(myState.success),
fail: !!(myState.fail)
});
const mapDispatchToProps = dispatch => ({
doReq: () => {
dispatch(doMyRequest());
}
});
class MyComponent extends Component {
toggleModal = () => // modal toggle code
render() {
const {response, success, fail} = this.props;
<div className="myModal">
// Modal stuff here
{!success && !fail && (
<button onClick="() => toggleModal()">Close modal</button>
)}
{success && !fail && (
<h1>Some success message</h1>
)}
{!success && fail && (
<h1>Some fail message</h1>
)}
</div>
}
}
req-actions.js
export const MY_REQUEST;
export const MY_REQUEST_SUCCESS;
export const MY_REQUEST_ERROR;
export const doMyRequest = () => ({
type: MY_REQUEST,
agent: agent.req.doRequest
})
req-reducer.js
import { deepEqual, deepClone } from '../McUtils';
import {
MY_REQUEST,
MY_REQUEST_ERROR,
MY_REQUEST_SUCCESS
} from "../actions/req-actions";
export default (state = {}, action) => {
let newState = deepClone(state);
switch (action.type) {
case MY_REQUEST:
console.log('SENDING REQUEST');
newState.response = null;
newState.success = false;
newState.fail = false;
break;
case MY_REQUEST_SUCCESS:
console.log('SUCCESS');
newState.response = action.payload;
newState.success = true;
newState.fail = false;
break;
case MY_REQUEST_ERROR:
console.log('FAIL');
newState.response = action.payload;
newState.success = false;
newState.fail = true;
break;
default:
return state;
}
return newState;
}
Just use another action:
case MY_REQUEST_RESET:
return {} // only putting {} in here because this is what you have defined your initialState to be according to your reducer.
Personal preference is to clearly define your initial state like this.
const initialState = {};
export default (state = initialState, action) => {
switch(action.type) {
... your existing handlers
case MY_REQUEST_RESET:
return initialState
}
}
Wiring it up:
const mapDispatchToProps = dispatch => ({
doReq: () => {
dispatch(doMyRequest()),
},
reset: () => {
dispatch(resetMyRequest());
}
});
// types
const MY_REQUEST_RESET = 'MY_REQUEST_RESET';
// action creator (may be referred to as "actions")
const resetMyRequest = () => ({ type: MY_REQUEST_RESET })
EDIT: While I'm here, this is really gross:
let newState = deepClone(state);
and reeks of "I don't really know what I'm doing" and can lead to performance issues. You are deepCloning the state on every action fired through redux, even if the actions aren't one's this reducer is interested in.
If you are changing the state in the reducer, just change the part you are concerned with, don't change "all" of it.
e.g.
export default (state = {}, action) => {
switch (action.type) {
case MY_REQUEST:
console.log('SENDING REQUEST');
return {
success: false,
fail: false,
response: null
}
case MY_REQUEST_SUCCESS:
console.log('SUCCESS');
return {
...state, // this will contain "fail: false" already
success: true,
response: action.payload
};
case MY_REQUEST_ERROR:
console.log('FAIL');
return {
...state, // this will contain "success: false" already
error: true,
response: action.payload
};
default:
return state;
}