I'm learning react and redux and can't seem to understand why a simple array in my state is not functioning right but if I add just another variable which is not even being used then every thing works fine. This is my store:
const store = createStore(
reducer,
{items:[],
a:100
}
This is the mapping:
const mapStateToProps = (state) => {
return {
list:state.items,
a:state.a
};
};
This is what my reducer return:
return {a:state.a-1000,
items:state.items}
The variable 'a' is not being used but for some reason if I remove it from the above code blocks then the application does not function correctly. In the reducer's return statement even if I change state.a-1000 with state.a it stops functioning correctly. I can't seem to understand what is happening. The state works fine when there is just a variable in it that is not an array but when there is just an array inside the state for some reason it is requiring another variable.
redux state needs to be immutable. items:state.items mutates the state which wont be picked up by redux.
try {...state, items} (where items is the new list) or try using Immutable.JS
further info here - https://redux.js.org/faq/immutable-data
Related
Am using react recoil for my state management and have a moderately nested object serving as state for this particular component.
When trying to update the state with setState (the Recoil version, I'm suspecting vanilla React useState would behave similar), I grab the old version of the object, and try mutating it a bit before returning the updated state. But reassigning values on this object fails completely silently, I see no warnings, but assigning a new value has no effect. Example:
import { useRecoilState} from "recoil";
...
const [state, setState] = useRecoilState(sampleState);
const someCallback = () => {
setState((oldState) => {
const newState = { ...oldState };
console.log(newState.dog.paw); // false
newState.dog.paw = true
//stopping the debugger here and trying to reassign in the devtools has equally no effect
console.log(newState.dog.paw); // false
return newState ;
});
};
when reassigning during a paused debugger session in chrome dev tools this is happening:
newState.dog.paw = true
true
newState.dog.paw
false
I am aware that React's documentation says that mutating the state object directly is not advisable but from that to explaining what is happening above is a gap I don't know how to fill.
I actually also know how to work around the problem, the solution is to spread reassign the parent object like so:
newState.dog = {...newState.dog, paw:true}
Having said all that, I'd be grateful for some deeper insight about what is happening up there and why is this phenomenon behaving so weirdly in the interpreter/devtools -> the reassignment seems to go through but it doesn't have any effects.
I have a observable store that is being based via props to components that need it.
Console logging from components does show store as expected, but only if I stick to the whole store. Once I start chaining into it I get undefined.
Base store
export let TutorStore = observable({
Tutor: {},
Queue: [],
QLength: null
});
Component logging
checkBtn = () => {
console.log(this.props.tutorStore);
};
Result TutorStore in Console as expected
All the correct tutorStore objects are there, with the right data, as expected.
But if I try to chain into a object there are no values attached to it, not behaving as expected.
checkBtn = () => {
console.log(this.props.tutorStore.Tutor);
};
Result TutorStore.Tutor no values
I've tried messing around with mobx's toJS method, but it seems unreliable at best.
Considering just assigning the appropriate object to components state, but that defeats the purpose of having a store.
The data is there, so how do I access it?
I have a simple cart function that, when a user clicks to increase or decrease the quantity of an item in a shopping cart, calls a useState function to update the cart quantity in state.
const [cart, setCart] = useState([]);
const onUpdateItemQuantity = (cartItem, quantityChange) => {
const newCart = [...cart];
const shouldRemoveFromCart = quantityChange === -1 && cartItem.count === 1;
...
if (shouldRemoveFromCart) {
newCart.splice(cartIndex, 1);
} else {
...
}
setCart(newCart); //the useState function is called
}
so in jest, I have a function that tests when a user sets a cart item's quantity to zero, but it does not yet remove the item from the cart, I'm assuming because it has not yet received the results of setCart(newCart):
test('on decrement item from 1 to 0, remove from cart', () => {
const [cartItemToDecrement] = result.current.cartItems;
const productToDecrement = result.current.products.find(
p => p.id === cartItemToDecrement.id
);
act(() => {
result.current.decrementItem(cartItemToDecrement);
});
act(() => {
result.current.decrementItem(cartItemToDecrement);
});
...
expect(result.current.cartItems).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: cartItemToDecrement.id,
count: cartItemToDecrement.count - 2,
inventory: productToDecrement.inventory + 2
})
])
);
});
});
This test passes, because the cart now contains an item whose quantity has dropped to zero. But really it shouldn't, because from the splice operation in onUpdateItemQuantity our cartItems array should now not include the object at all. How do I verify in my jest test that this removal is happening (the react code works properly).
I do not really understand the relation between the test and your onUpdateItemQuantity very much because the context you provided is not sufficient to me.
But from your question, there are 2 clues which may help to save your time.
You may know setCart from useState is not synchronous, so that if you try to access cart from useState at the same frame, it shouldn't reflect the change even though you ensure setCart called. useEffect is a good solution. You can find the doc of useEffect [https://reactjs.org/docs/hooks-effect.html][1], any change should be reflected in useEffect, perhaps you can put your test there.
Add an extra variable myCart to store cart and a function setMyCart. Instead of calling setCart in your code, call setMyCart. setMyCart is like,
function setMyCart(newCart)
{
myCart = newCart;
setCart(myCart); // this is for triggering React re-render
}
then use myCart which can reflect the change immediately for testing.
The only purpose of the additional code in the 2nd point, is when we still rely on the re-render mechanism of React, we use our own Model myCart for our particular logic rather than cart state from React which is not only for View but also used for our logic on an inappropriate occasion.
Testing for state changes is a challenge, because it doesn't produce any specific output (except whatever impacts your render).
The key is to narrow the testing scope to your responsibilities, versus testing React. React is responsible for making sure that useState updates the state properly. Your responsibility is to make sure you are using the data properly and sending the correct data back. So instead of testing useState, test how your component responds to the state it is given, and make sure you are setting the correct state.
The answer to to mock useState. Give it an implementation that returns the initial value passed to it, with a mock function that lets you read what data is being saved.
More detailed answer here:
https://dev.to/theactualgivens/testing-react-hook-state-changes-2oga
HTH!
If you need to test every state update using useState(), you can do so by implementing a useEffect function that listens specifically for that state change, in your case, cart:
useEffect(() => {
test(...)
), [cart]);
And so you can do the test inside the useEffect() function.
That will ensure that you have the correct value of cart and it will be called every time cart is updated.
It's totally unclear how onUpdateItemQuantity is called. And even what it does exactly. Never the less, you claim that toEqual(...[{...,count:cartItemToDecrement.count - 2,...}]...) passes. That probably means that onUpdateItemQuantity is called inside of decrementItem, inside of onUpdateItemQuantity there is something like newCart[cartIndex].count--, and if toEqual() passes, that means that setState() successfully updates the state, and after act() you have the actualized state. And if test's name is correct and initial value of cartItemToDecrement.count === 1 that should mean that cartItemToDecrement.count - 2 === -1 and you never go inside of if (shouldRemoveFromCart). So maybe the issue is not with the test, but with the code.
I'm pretty new to Redux and i'm encountering some problems with it.
I'm creating a list of items in a component, sending it to redux state and then i want to read from that redux state and display the items in a different list component.
The creation part works as i can console.log and getState() without problems (i am seeing the changes in Redux State).
My problem is that my component state does not change, nor does it re-render.
And now some code ->
this.state = {
initialItems: this.props.SharepointItems,
}
And at the end
const mapStateToProps = (state) => {
return {
SharepointItems: state.listItems,
}
}
export default connect(mapStateToProps)(SharePointList);
I even tried something like this in my componentDidMount() ->
store.subscribe(() => {
this.setState({ initialItems: this.props.SharepointItems });
console.log("updating state");
});
From what i've read i shouldnt need to update the state manually while using redux, or am i wrong?
EDIT: Since my list doesnt throw an error if i console.log i can see that the array is empty (which is what i defined in the Redux state). Is there something that i should be doing to get the new state ? Seems like its getting the immutable state or something like that (the empty array).
Edit2: Found the problem (or part of it). It appears as my state is 1 event behind. So redux contains the array with 4 items, the component state is empty. If i do a dispatch from the browser i get 5 items (as expected) in redux, but 4 in state (it finally shows not empty).
Also my code was a bit bugged (i was passing the entire array instead of items in the array).
I've changed it to
result.map((item) => {
store.dispatch(addListItem(item));
});
And it started rendering. The problem is it displays items from 0 to 2 (4 in array), but the last one is left behind. Once again if i do another dispatch from the browser i get item 3 rendered, but 4 (the last one added) is only in redux state and does not update the list state.
Also...is it a good idea to do it like this? My list might have 1000 items in the future and i'm not sure that dispatch is a good solution (i need to make an API call to get the items first, which is why i'm using dispatch to populate redux).
Updated with reducer ->
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_LISTITEM:
return { ...state, listItems: [...state.listItems, action.payload] };
case ADD_SPOTOKEN:
return { ...state, spoToken: action.payload };
default:
return state;
}
};
export default rootReducer;
Found something else thats a bit weird. I am also using React Router. As i said my list displays only 3 of the 4 items in my Redux State array. If i navigate to a different page and then back to the list page it actually renders all 4 of the items.
I believe your problem stems from the fact that you have "forked" SharepointItems off of props and set it to the component's local this.state. Unless you really need to, I'd recommend not doing that. Just use this.props. Dan Abramov (author of Redux) recommends this as a general principle too.
By forking props onto state, you create two sources of truth regarding the state of SharepointItems, namely, this.state.initialItems and this.props.SharepointItems. It then becomes your responsibility to keep this.state and this.props in sync by implementing componentDidUpdate (that is why you're not seeing it update). You can avoid all the extra work by just using the data that flows in from props.
The connect function will re-render your component with new props whenever you update the redux store. So in your render method, just refer to this.props.SharepointItems and not this.state.initialItems. Then you should be good to go, that is, assuming you've implemented your reducer(s) and store configuration properly.
I actually fixed this by mistake. I was planning on leaving it for the end and find a workaround for it, but i somehow fixed it.
I was importing the list component (which had the redux state props) in a Page (react-router). The page wasn't connected since it wasn't ready yet. Apparently after connecting the page (parent component which holds the list) everything works fine and i can see all 4 of my items (instead of seeing 3 without having the parent connected).
I wonder if this is intended...
I am new to react-redux and I was surprised to see an example where a function, in this case being getVisiblieTodos, is called inside mapStateToProps. This function should be called in a reducer since it changes state? Is the code breaking "good form" for the sake of brevity? Is it okay to do this in general?
I am looking at code from this link
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
In redux we want the store to hold the minimal data needed for the app. Everything that is derived from the base data, should be computed on the fly, to prevent cloning pieces of the store, and the need to recompute all derived data when something changes in the store.
Since the visible todos list is not part of the store, but computed using the list of todos, and the visibilityFilter, the getVisibleTodos() doesn't change the store's state. It produces the derived computed data from the those two properties.
A function that is used to get data from the store, and compute derived data is known as a selector. Using selectors, the derived data is not part of the store, and computed when needed. In addition, we can use memoized selectors, to save the computation overhead.
You may see getVisibleTodos as a reducer because it includes "switch .. case" block or/and because it has 2 arguments . However, it is not a rule.
A redux reducer ( by definition) changes store state according to dispatched action , and that's why it takes two arguments ( store state + dispatched action ) and it returns new state for the store without mutation.
getVisibleTodos here is a helper function which filter an array according to string (filter).
Also , filter is not a redux-action, it is just string that decides todos to be rendered.
I may agree with you it is something weird , and if we can see the whole application (reducers, actions,... ) we can decide if it is best practices or not .
todos in this component is a calculated property based on the state of the reducer, and it is not changing any state.
It's okay to transform properties comming from recuders that are used only by one component (they are called selectors). Imagine that you use todos in other components, you will not want to make changes in one component like filtering and seeing that in the other components. If this is the case, it's fine to do it.
Also, it is a good property of your reducer to store only the needed data. More state is more complexity in the app, and more overhead to calculate new states.
It seems to me that a function should do what its name says, nothing less, nothing more.
mapStateToProps() should just do that, ie "map", and should normally not call other functions.