Overwriting array of objects instead of updating it - reactjs

Im currently learning redux and trying few options out. Everything looks okay until I want to update one state in the array of objects.
I'm dispatching 5 actions in total now, first 2 setting longitutde and latitude for one part of the state using one reducer, then I set IDs with other reducer and finally when I want to update one of the objects in the array I update one but delete the other somehow.
My file looks like this:
const demoState = {
renderedDrugs: [
{
id: '',
drugId: '',
price: undefined
}
],
apt: {
latA: '',
lonA: ''
}
}
//SET_ID
const setId = (id, drugId) => ({
type: 'SET_ID',
renderedDrugs: {
id,
drugId
}
})
//SET_PRICE
const setPrice = (drugId, price) => ({
type: 'SET_PRICE',
drugId,
price
})
//RENDERED DRUGS REDUCER
const renderedDrugsReducerDefState = [];
const renderedDrugsReducer = (state = renderedDrugsReducerDefState, action) => {
switch (action.type) {
case 'SET_ID':
return [
...state,
action.renderedDrugs
]
case 'SET_PRICE':
return state.map((drug) => {
if (drug.drugId === action.drugId) {
return {
...drug,
...action.price
}
}
})
default:
return state;
}
}
//SET_LAT
const setLat = (latA) => ({
type: 'SET_LAT',
latA
})
//SET_LON
const setLon = (lonA) => ({
type: 'SET_LON',
lonA
})
//APT REDUER
const aptReducerDefState = []
const aptReducer = (state = aptReducerDefState, action) => {
switch (action.type) {
case 'SET_LAT':
return {
...state,
latA: action.latA
}
case 'SET_LON':
return {
...state,
lonA: action.lonA
}
default:
return state;
}
}
//STORE CREATION
const store = createStore(
combineReducers({
renderedDrugs: renderedDrugsReducer,
apt: aptReducer
})
)
store.subscribe(() => {
console.log('store', store.getState());
})
store.dispatch(setLat(88));
store.dispatch(setLon(78));
store.dispatch(setId(uuid(), 3));
store.dispatch(setId(uuid(), 35));
store.dispatch(setPrice(35, {price: 400}));
I assume the SET_PRICE action is at fault, but I tried various configurations and cant figure out the issue so thats why I posted the whole file, if thats unnecessary let me know and I will delete irrelevant bits.
Console log after 4th dispatch:
{renderedDrugs: Array(2), apt: {…}}
apt
:
{latA: 88, lonA: 78}
renderedDrugs
:
Array(2)
0
:
{id: "2a3c4bca-610a-4554-b7e3-695ae6e30ae7", drugId: 3}
1
:
{id: "48df057a-c8f1-4138-8db7-6268f7508ccb", drugId: 35}
length
:
2
__proto__
:
Array(0)
__proto__
:
Object
and aftr 5th
{renderedDrugs: Array(2), apt: {…}}
apt
:
{latA: 88, lonA: 78}
renderedDrugs
:
Array(2)
0
:
undefined
1
:
{id: "48df057a-c8f1-4138-8db7-6268f7508ccb", drugId: 35, price: 400}
length
:
2
__proto__
:
Array(0)
__proto__
:
Object

The .map doesn't return the unchanged objects for the items your're not updating. Adding a return should fix it:
return state.map((drug) => {
if (drug.drugId === action.drugId) {
return {
...drug,
...action.price
}
}
return drug; // this was missing before, so the return was undefined
})

Related

React Redux Error the page is displayed before getting the data

I have a problem. I would like to get some data before loading the DOM but I have been running into this error for hours. I have a 200 response on my query but the error persists. After a reload of the page the display is ok.
// redux
const dispatch = useDispatch();
const customers = useSelector((state) => state.customerReducer);
useEffect( () => {
dispatch(findAllCustomers());
}, [])
{ !isEmpty(Object.values(customers)) && Object.values(customers)[0].map((customer, index) => ...
Uncaught TypeError: Object.values(...)[0].map is not a function ...
Thanks for your help.
[Array(54)]
0: (54) [{…}, {…}, {…}, {…},
0: Array(54)
0: {id: 2,, …}
1: {id: 3, …}
2: {id: 4 , …}
//Actions.js
export const findAllCustomers = () => {
return (dispatch) => {
axios.get('/api/customers')
.then((response) => {
dispatch({
type: FIND_ALL_CUSTOMERS, payload:response.data
})
})
.catch((error) => console.log(error.response))
}
}
//CustomersReducer.js
const INITIAL_STATE = [];
function customerReducer(state = INITIAL_STATE, action)
{
switch (action.type){
case 'FIND_NB_CUSTOMERS' : {
return {
...state,
nbCustomers : action.payload
}
}
case 'FIND_ALL_CUSTOMERS' : {
return {
...state,
customers: action.payload
}
}
default:
return state
}
}
export default customerReducer;
//isEmpty()
export const isEmpty = (value) => {
console.log(value)
return (
value === undefined ||
value === null ||
(typeof value === "object" && Object.keys(value).length ===
0) ||
(typeof value === "string" && value.trim().length === 0)
);
}
Object.values(customers) return an Array and you are trying to access the first index of that array which is probably not an Array anymore.
Change to:
Object.values(customers).map()
The problem is that you are trying to use map() on something that isn't an array. Object.values(customers)[0] is an object. However, since customers is an array, there is no reason to use Object.values() at all. Instead, just map over the array directly with customers.map().
So all together it should be
{ customers && customers.map(...) }

Reducer doesn't update state

I am trying to create a reducer to update a property in object, but i cant be able to update and store the new state information
Reducer
export default function hideCardNumber(state = INITIAL_STATE, action: Action) {
if (action.type === 'HIDE_CARDNUMBER') {
return {
...state,
data: {...state.data, action }}
}
else
return state
}
Action
export const toggleViewNumberCard = (cardId: number, hideCardNumber: boolean) => {
return {
type: 'HIDE_CARDNUMBER',
cardId,
hideCardNumber,
}
}
dispatch to action
function handleToggleViewCardNumber() {
cards.map((card: Card) => {
if (card.cardId === props.activeCard ) {
dispatch(toggleViewNumberCard(
card.cardId,
!card.hideCardNumber,
))
}
})
}
Initial State
export const INITIAL_STATE = {
activeCard: 0,
data: [
{
cardId: 0,
cardName: 'Card',
cardUsername: 'Name',
cardNumber: '1234 1234 1234 1234',
hideCardNumber: false, <-- Trying to replace this property when reducer update
},
]
}
You need to update reducer like this:
const {hideCardNumber, cardId} = action;
return {
...state,
data: state.data.map(item => item.cardId === cardId ? {...item, hideCardNumber} : item )
}
In real-world scenarios, cardID will be a hash. Also to easily maintain the store data when the application grows INITIAL_STATE should be like this.
export const INITIAL_STATE = {
activeCard: 0,
data: {
123456: {
cardId: 123456,
cardName: 'Card',
cardUsername: 'Name',
cardNumber: '1234 1234 1234 1234',
hideCardNumber: false,
},
}
}
Then the Reducer will be like this.
export default function hideCardNumber(state = INITIAL_STATE, action) {
if (action.type === 'HIDE_CARDNUMBER') {
return {
...state,
data: {
...state.data,
[action.cardId]: {
...state.data[action.cardId],
hideCardNumber: action.hideCardNumber
}
}
}
}
else
return state
}
If the activeCard matches one of the IDs in the cards, the code will work perfectly.
first of all it's better to put you data in payload like:
export const toggleViewNumberCard = (cardId: number, hideCardNumber: boolean) => {
return {
type: 'HIDE_CARDNUMBER',
payload : {
cardId,
hideCardNumber,
}
}
}
looks like you have array of cards ,first of all you must have find your current card that you wanna replace using cardid like this:
const index = state.data.findIndex(
(card) => cardId === action.payload.cardId
);
then copy your old array :
const newArray = [...state.data];
then replace that index of newarray with your new hideCardNumber value like this:
newArray[index] = {
...newArray[index],
action.payload.hideCardNumber
};
return {
...state,
data: newArray,
};
i hope it would help you

What happens when a reducer returns 'state' in React?

If I have a contactReducer that looks like this:
import {
GET_CONTACTS,
DELETE_CONTACT,
ADD_CONTACT,
EDIT_CONTACT,
GET_CONTACT,
} from "../actions/types";
// the state the holds the contacts
const initialState = {
contacts: [
{
id: 1,
name: "John Doe",
email: "john#gmail.com",
phone: "555-555-5555",
},
{
id: 2,
name: "Karen Williams",
email: "karen#gmail.com",
phone: "444-444-4444",
},
{
id: 3,
name: "Henry Johnson",
email: "henry#gmail.com",
phone: "333-333-333",
},
],
contact: {},
testProp: {},
};
export default function (state = initialState, action) {
switch (action.type) {
case GET_CONTACTS:
console.log("get contacts");
return {
...state,
};
case DELETE_CONTACT:
return {
...state,
contacts: state.contacts.filter(
(contact) => contact.id !== action.payload
),
};
case ADD_CONTACT:
let newArray = state.contacts.slice(); // get the current contacts array
newArray.unshift(action.payload); //push on the new contact to the beg of array
return {
...state, //take the existing state..
contacts: newArray,
};
case EDIT_CONTACT:
console.log("trying to edit");
return {
...state,
contacts: state.contacts.map((contact) =>
contact.id == action.id ? (contact = action.payload) : contact
),
};
case GET_CONTACT:
const selectedContact = getSingleContactFromId(state, action.payload);
console.log("look here");
console.log(selectedContact);
return {
...state,
contact: selectedContact,
testProp: { test: "test prop" },
};
default:
console.log("testing action in default");
return state;
}
}
function getSingleContactFromId(state, id) {
var contact;
console.log("get single contact");
for (var i = 0; i < state.contacts.length; i++) {
contact = state.contacts[i];
if (contact.id == id) {
return contact;
}
}
}
What is actually happening when the reducer returns? Where does it return to? For example, I send a dispatch to the reducer like this this.props.addContact(newContact);
But, I don't see that I do anything with the returned object anywhere after this. In another file, is where I grab things from the state, so does return really just mean it is updating the state?
Assuming you use combineReducers, the returned state from a specific reducer will now be the updated state of the state chunk represented by this reducer.
Then, any connected component will receive the new state and will re-render.
This is a high-level description obviously.
More information can be found here: https://react-redux.js.org/using-react-redux/connect-mapstate

I'm having trouble looping through an array

I'm using MyJSON to create a store for my data and im having trouble looping through it
i've tried .map(), .forEach but i cannot for the life of me, map over the array of objects.
TypeError: Cannot read property 'map' of undefined
the JSON store looks like this
const Leaderboard = (props) => {
useEffect(() => {
props.fetchScores();
},[]);
const renderList = () => {
props.scores.map(item => {
return <LeaderboardItem name={item.name} data={item.date} />
})
}
return (
<div className="leaderboard">
<h1 className='leaderboard__header'> Leaderboard</h1>
{renderList()}
</div>
);
};
const mapStateToProps = (state) => {
return {
scores: state.scores
};
};
export default connect(mapStateToProps, { fetchScores })(Leaderboard);
I'm able to fetch the data, add it to my reducers. When i console.log my props i get this
(5) [{…}, {…}, {…}, {…}, {…}]
0: {name: "ryan", score: 3, date: 1564079441826, id: 1}
1: {name: "ryan", score: 0, date: 1564080251976, id: 2}
2: {name: "ryan", score: 4, date: 1564080621616, id: 3}
3: {name: "ryan", score: 1, date: 1564088666800, id: 4}
4: {name: "ryan", score: 8, date: 1564088919233, id: 5}
length: 5
__proto__: Array(0)
shouldn't I be able to map over the array and return each object?
10 | },[]);
11 |
12 | const renderList = () => {
> 13 | props.scores.map(item => console.log(item))
| ^ 14 | }
15 |
16 | return (
export default (state = {}, action) => {
switch(action.type) {
case FETCH_SCORES:
return { ...state, ...action.payload }
default:
return state;
};
};
images :
Redux-dev-tools-image1
Redux-dev-tools-image1
console.log
to render a list of items you need to actually return JSX in your map
const renderList = () => {
props.scores.map(item => <div key={item.id}>{item.name}</div>)
}
You should read up on best practices for rendering lists of elements in react.
Edit
Since scores is undefined, you need to make sure that you are referencing things correctly.
Is scores the key defined in combineReducers? aka something like combineReducers({scores: scoresReducer})
Update your reducer to store what you want to store all the time. dont change the datatypes
const defaultState = {
scores: []
}
export default (state = defaultState, action) => {
switch(action.type) {
case FETCH_SCORES:
return { ...state, scores: ...action.payload }
default:
return state;
};
}
this assumes action.payload is an array of scores, adjust accordingly if its not
map creates another array as a transformation of the original array. You are simply console.log'ing over each item which would be a use for forEach and not map.
The return value of a console.log is undefined and probably causing problems while rendering an array of [undefined, undefined, ...]
Can you consider this:
props.scores && props.scores.map(item => {
return <LeaderboardItem name={item.name} data={item.date} />
})

Filter products depend on another ACTION in React-native Redux

I have an app which get all categories and products from the server with Redux ACTIONS. I need to filter products with a category Id. after load data action is complete, i call another action to filter products but i'm a little bit confused.
There is codes of few parts of the app:
ProductsActions:
export const GET_INITIAL_PRODUCTS_DATA = "GET_INITIAL_PRODUCTS_DATA";
export const GET_INITIAL_PRODUCTS_DATA_RESULT = "GET_INITIAL_PRODUCTS_DATA_RESULT";
export const GET_INITIAL_PRODUCTS_DATA_ERROR = "GET_INITIAL_PRODUCTS_DATA_ERROR";
export const FILTER_PRODUCTS_BY_CATEGORY_ID = "FILTER_PRODUCTS_BY_CATEGORY_ID";
export const getInitialProductsData = () => ({
type: GET_INITIAL_PRODUCTS_DATA
});
export const filterProductsByCategoryId = categoryId => ({
type: FILTER_PRODUCTS_BY_CATEGORY_ID,
categoryId
});
ProductsReducers:
import {
GET_INITIAL_PRODUCTS_DATA,
GET_INITIAL_PRODUCTS_DATA_RESULT,
GET_INITIAL_PRODUCTS_DATA_ERROR,
FILTER_PRODUCTS_BY_CATEGORY_ID
} from "../actions/products";
const initialState = {
isFetching: false,
data: {},
error: null
};
const filterProductsByCategoryId = (state, action) => {
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case GET_INITIAL_PRODUCTS_DATA:
return {
...state,
isFetching: true
};
case GET_INITIAL_PRODUCTS_DATA_RESULT:
return {
...state,
isFetching: false,
data: action.result
};
case GET_INITIAL_PRODUCTS_DATA_ERROR:
return {
...state,
isFetching: false,
error: action.error
};
case FILTER_PRODUCTS_BY_CATEGORY_ID:
return {
...state,
data: filterProductsByCategoryId(state, action.categoryId)
};
default:
return state;
}
};
export default reducer;
And there is my code to call filter action:
filterProducts = (title = "A") => {
const _categories = Object.values(this.props.categories);
const selectedCategory = _categories.find(
category => category.title === title
);
this.props.dispatch(filterProductsByCategoryId(selectedCategory.id));
My questions is:
A) Is there is a way to filter my data and display them in UI and refresh them without using ACTIONS way??
B) If A's answer is No!, How can i get my state.data and filter them in FILTER_PRODUCTS_BY_CATEGORY_ID?
Thanks.
You can use the Array.prototype.filter() to return filtered result.
keep in mind that this will return an array and not a single value, which is a good thing if you are using this filter within your reducer. because your reducer's shape is an array and not an object.
Running example:
const myData = [{
name: 'some name',
id: 1
}, {
name: 'some name2',
id: 2
}, {
name: 'some name3',
id: 3
}, {
name: 'some name4',
id: 4
}]
const filterProductsByCategoryId = (state, action) => {
return state.filter(c => c.id === action.categoryId);
};
const result = filterProductsByCategoryId(myData, {categoryId: 2});
console.log(result);
I think it is more appropriate to create a selector for a singular product that will handle this kind of action, this way you will be able to return an object instead of an array with one product in it.
Not to mention the benefits of using reselect to do some memoizations.
For this task you can use the Array.prototype.find():
const myData = [{
name: 'some name',
id: 1
}, {
name: 'some name2',
id: 2
}, {
name: 'some name3',
id: 3
}, {
name: 'some name4',
id: 4
}]
const filterProductsByCategoryId = (state, id) => {
return state.find(c => c.id === id);
};
const result = filterProductsByCategoryId(myData, 2);
console.log(result);

Resources