I have got an initial state in reducer. I get from api products and I should check by key, if product was not repeated, I put it to the state. If was repeated - do nothing. Mistake is that filter does not work and products repeat.
const defaultState = {
productsInMyList: [],
};
//get prodouct
const { productsInMyList } = action.payload;
//check, if it is in state or not
const sameProductsInMyList =
!!state.productsInMyList.includes(
productsInMyList.key
);
const newProductsInMyList = sameProductsInMyList
? state.productsInMyList
: [...state.productsInMyList, productsInMyList];
return {
...state,
productsInMyList: newProductsInMyList,
};
I suspect, based on a comment of yours, that state.productsInMyList is an array of objects and that productsInMyList is also an object. Array.prototype.includes only works with primitives and shallow object equality (i.e. reference equality).
If you are comparing objects in an array and want to know if an array contains some element matching a criteria then you will want to use Array.prototype.some, i.e. does there exist some element in this array meeting this condition.
const sameProductsInMyList = state.productsInMyList.some(
product => product.key === productsInMyList.key
);
I changed includes into find. Thanks all for the help.
const sameProductInMyList = !!state.productsInMyList.find(
(item) => item.key === productsInMyList.key
);
Related
When I remove an array item from my state array, I'm also updating the prices after removing the array item. But prices are not updating. I have tried every thing, but didn't get any solution.
export default function CustomizerState(props) {
const initialTextItem = {
text: "Hello",
neonPrice: 0,
backplatePrice: 0,
neonPower: 0,
totalPrice: 0
}
const [settings, setSettings] = useState({
textItems: [initialTextItem],
libraryItems: [],
accessories: [...],
finalPrice: null
})
const removeItem = (id, itemType = "textItems") => {
const filteredItems = settings[itemType].filter((item) => {
return item.id !== id
})
setSettings((prevState) => (
{...prevState, [itemType]: filteredItems}
))
finalPrice()
}
const finalPrice = () => {
const textItemsPrice = getTotalPrice()
const libraryItemsPrice = getTotalPrice("libraryItems")
const accessoriesPrice = getTotalPrice("accessories", "unitPrice")
console.log(textItemsPrice, libraryItemsPrice, accessoriesPrice)
const finalPrice = textItemsPrice + libraryItemsPrice + parseInt(accessoriesPrice)
setSettings((prevState) => (
{...prevState, finalPrice}
))
}
const getTotalPrice = (itemType = "textItems", priceKey = "totalPrice") => {
let price = 0
settings[itemType].map((item) => (
price = price + (item[priceKey] * item.quantity)
))
return price
}
return (
<CustomizerContext.Provider value={{settings, addTextItem,
removeItem}}>
{props.children}
</CustomizerContext.Provider>
)
}
For now, it is behaving like when I remove any item, it doesn't update the finalPrice object item, but when I remove another item then it updates the prices for previous items. I don't know why it is behaving like this.
Can someone please have a look on my code and tell me what is wrong with it and how can I fix this?
You're calling finalPrice()right after you set your state
that triggers a re-rendering. You have to trigger the change using useEffect hook like this:
useEffect(() => {
finalPrice()
}, [settings]
You should probably consider separating your price from the rest of your settings.
Instead of calling a function right after updating the list, do the calculations before and update the state altogether. The problem with your approach is that when the calculus is being made, the state haven't updated yet, so when the function finalPrice() runs it takes the previous value.
I sincerely recommend you to use a Reducer instead, a single state with so many parameters may be troublesome.
Refer to useReducer, it will make your life easier.
I am new in React, currently taking a course. While doing the exercise, the teacher cloned the same object on it and I don't understand its utility. I commented the line and all is working perfectly. I thought that the teacher made it by mistake, but he did the same thing in another exercise.
Here is the code:
handleLike = (movie) => {
const movies = [...this.state.movies];
const index = movies.indexOf(movie);
movies[index] = { ...movies[index] }; // It is about this line!
movies[index].liked = !movies[index].liked;
this.setState({ movies });
};
What is the use of this line movies[index] = { ...movies[index] }; ?
This is because you don't want to operate on the original objects from the current state as they are references. The React's state is immutable, so you always want to work on new objects when modifying their values.
movies[index] = { ...movies[index] }; creates a new object with all properties of the original one. In the next line (movies[index].liked = !movies[index].liked;) you are modifying the liked value of the new object. Because you've copied the object before, you don't actually mutate the original one but rather work on the new one.
I'll focus on your question about this particular line:
movies[index] = { ...movies[index] };
1) What does this line does?
This line mutates the movies array, and copy-paste the original content from the same index element.
Then, the next line mutates this element directly.
2) Do we need to do this?
No.
This approach is very common on object-oriented programming languages.
But while using React, an immutable approach is more simple and clean.
Here is an immutable solution for the same function:
handleLike = (movie) => {
const movies = [...this.state.movies];
const index = movies.indexOf(movie);
const updatedMovies = movies.map((movie, movieIndex) => {
if (movieIndex === index) {
return { ...movie, liked: !movie.liked };
} else {
return movie;
}
});
this.setState({ movies: updatedMovies });
};
I have the following logic in my List component:
const List = React.memo(() => {
const apples = useSelector(
(state) => Object.values(state.apples),
(currentState, prevState) => currentState.length === prevState.length,
);
return 'whatever';
);
This gives me great results in terms of rendering times -> it renders only once and re-renders ONLY when the length of the "apples" changes, but NOT when the properties of the apples change.
However, I was wondering if adding a memoized selector will result any performance benefit?
To illustrate my example:
const applesSelector = createSelector(
(state) => Object.values(state.apples)
)
const List = React.memo(() => {
const apples = useSelector(
applesSelector,
(currentState, prevState) => currentState.length === prevState.length,
);
return 'whatever';
);
So I struggle to find is there is a performance benefit for the useSelector by using together both a memoizing selector AND an equality comparison function?
First, the example implementation of applesSelector there appears to be buggy. The correct implementation would be:
const applesSelector = createSelector(
state => state.apples,
apples => Object.values(apples)
)
That way, the output selector really only runs when state.apples has changed.
For your actual question: if you're using a memoizing selector, there shouldn't generally be a need to add an equality comparison argument to useSelector, because the work of doing the comparison has already happened internal to the selector function.
Memoization is something that is nice in reselect but the main benefit you get is composable selectors. Instead of answering 2 questions in 1 selector (where are apples and in what shape do I return them) you can answer one question in one selector "where are the apples" in selectAppes and "in what shape do I return them" as selectApplesAsArray that uses selectApples to get the apples. This prevents duplicate implementation.
The apples example is somewhat simple but a react application can have complex business rules in the selectors that you don't want to repeat. Something like selectPolicyPrice may need to re use other selectors to prevent duplication of implementation that would cause hard to fix bugs when the rules of policy pricing change.
Sometimes you may want to memoize array results such as when you do Object.keys, Object.values or Array.prototype.filter.
const { createSelector, defaultMemoize } = Reselect;
const createMemArray = () => {
const memArr = defaultMemoize((...arr) => arr);
return (arr) => memArr(...arr);
};
const selectApples = (state) => state.apples;
const selectApplesAsArray = ((
mem //mem will memoize array
) =>
createSelector([selectApples], (apples) =>
mem(Object.values(apples))//memoize each item in apples
))(createMemArray()); //IIFE createing memoized array
const state = { apples: { a: 1, b: 2 } };
const a = selectApplesAsArray(state),
b = selectApplesAsArray({
...state,
//copied, will not recalculate because of memoize array
apples:{...state.apples},
irrelevantChange: 22,
});
console.log('a is b:', a === b);
console.log('a and b are:', a);
const c = selectApplesAsArray({
...state,
apples: { ...state.apples, b: 'changed' },
});
console.log('c is b:', c === b);
console.log('c is:', c);
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
I wrote some short examples how selectors can be used with redux that may be helpful.
My main goal is to set a state with a array of objects, but I can't do it because this array is mutated with elements that are pushed to it. I understand the problem but I don't know how to solve it
so here is code and I would like to set state with the final actors array. Any ideas?
const moviesToMap = this.state.movies;
moviesToMap.map(movie => {
return this.state.title === movie.title
? movie.characters.map(person => {
const actorUrl = person;
const actorId = parseInt(actorUrl.slice(31, -1));
let peopleStateCopy = this.state.people;
peopleStateCopy.map(persona => {
const slugState = persona.url;
const slugId = parseInt(slugState.slice(31, -1));
if (slugId === actorId) {
actors.push(persona);
return persona.name;
}
});
})
You've got a lot going on here - perhaps you could refactor this?
Where is your actors array initialized? Where is your setState call?
When dealing with arrays in state, you may want to create a copy of your arrays - perform the mutations on the copy - and then set the state to the new array when you are finished.
For instance:
const moviesCopy = [...this.state.movies];
this.setState({ movies: moviesCopy });
Hope this helps get you on the right track.
I'm trying to build a search function to be able to search an Array in state.
var searchString = "search word"
var updatedList = cases.filter((item) => {
return Object.keys(item).some(
key => (item as any)[key]
.toString()
.toLowerCase()
.search(searchString.toLowerCase()) !== -1
);
});
this.setState({
cases: updatedList
})
This updates state.cases and renders the items found in the Array with objects, which is good. But when I'm doing another search, state.cases is of course updated and contains only the items from the search before.
How can I keep the "old" state before the first search and make multiple searches?
The cases in state is used in a component that renders a table with the result.
Maybe don't set the state for the filtered cases ..? Just set the state for the search string and try this
render() {
const { searchString } = this.state;
const { cases } = this.props;
let filteredCases = cases;
if(!!searchString) {
filteredCases = cases.filter((item) => {
return Object.keys(item).some(
key => (item as any)[key]
.toString()
.toLowerCase()
.search(searchString.toLowerCase()) !== -1
);
});
}
return <>{filteredCases}</>
}
So you will just filter out the cases based on your search string.