What is the issue with state being an array in react? - javascript

Started learning react but made my state as an array.
my state was not getting properly update when i did something like
this.setState(state => [newItem, ...this.state])
above statement was converting an array to integer indexed object
I was getting a warning
index.js:2178 Warning: App.state: must be set to an object or null
seems like there is some check in react-dom code like this
if (state && (typeof state !== 'object' || isArray(state))) {
link
why is this happening
what are the issues i can face if i use the state as an array or something else than object or null

setState accepts objects, plain and simple. If you call this.setState(['a','b']) it will convert your array to an array-like object. Why? Because React allows that elsewhere in your code you should be able to call this.setState({data: 'something'}) without having that fail. For reference this.state would now look like this:
{
0: 'a',
1: 'b',
data: 'something'
}
Why did you get a warning? Because React performed this conversion, from array to array-like object, behind the scenes, and wants to let you know that things have changed. For example, after setting state to an array, you won't be able to call this.state.map ... or any other array methods.
If you need to store an array in state, set it to an object property: this.setState({arrayData: ['a', 'b']}). In general it's a good practice to wrap your data in object properties, because you will certainly be lifting state up as you develop your application.

Related

When should I use a custom equality function in useSelector()?

I'm trying to understand when it is necessary to use a custom equality function in useSelector().
I have a selector that returns a simple object as:
const data = useSelector(state => state.data) // => { a: "foo", b: "bar }
I though that since data is an object, I needed to use a custom equality function to avoid unnecessary re-render, as it is stated in the doc:
With useSelector(), returning a new object every time will always force a re-render by default.
But then I noticed that my component was only re-rendering when data changed. I assumed that maybe the default equality function works with objects, but to be sure I tried to use a custom function:
const data = useSelector(state => state.data, (a, b) => a === b));
If data didn't change, it will always return true.
In Javascript, if I try to compare two objects with === it will return false because the references are different. But here with useSelector(), since it returns true it means that the references are the same.
Now, I'm not sure if I misunderstood the doc, but I wonder when I should use a custom equality function such as the isEqual of Lodash?
I'm using Redux Toolkit, if it makes any difference. Here is a CodeSandox if you want to test.
You should rarely need to write a custom equality function for useSelector.
Most of your useSelector calls should either be a simple const item = useSelector(state => state.slice.item), or be pass in an existing memoized selector function like const someData = useSelector(selectSomeData).
If you need to pass in a custom equality function, the most common one would be the shallowEqual function exported from React-Redux itself.
If you think you need to use a deep equality check like _.isEqual, you should probably pause and reconsider why you think you need a deep equality check, because that should normally be unnecessary in a Redux app.
Additionally, I think there's a bit of a misunderstanding here. Yes, state.data is an object, but it is a reference to an object in memory. When your reducer updates, RTK+Immer will automatically create a new object reference, and thus oldObject === newObject will be false because two different objects are never === to each other. That's why useSelector already uses a === comparison by default - immutable updates means that comparison is really fast and simple to do.

How to update/delete ref value in VueJS 3 composition api?

I'm using a ref value to be able to only execute a click event if the ref value is changing.
For example, if I want to update/del the array inside let myRef = ref([]);, do I just drill inside the proxy and do the operations like that :
selectedElements.value.push(3);
which returns
Proxy {0: 3}
or what is the correct way to update/del the ref.value ?
export default {
setup() {
let myRef = ref([]);
return {
myRef
};
},
};
You've got it under control. This is how I manage state.
In setup, I list all of my refs or reactive objects. I'll use computed properties if I feel like it to do various crosschecks on my refs or state, e.g. do I have records, how many, etc.
I'll write small functions that will handle state changes/mutations in a local component. If I need to use a watcher on state (ref or reactive object), then I'll do that and use it to call a method/action. You can only update ref inner values by accessing the ref object's single property .value which points to its inner value. I'm sure you know this, but the ref is a wrapper. IN your setup method, you'll got to unwrap a ref to change its inner value, but this isn't true with a reactive object - wherein you can just set that value as per a normal Vue2 data value.
So, yes, in your example, you can mutate your myRef array however you need by accessing its value property. Again, a good way to do this would be to define a method that does the thing that you need done, adding to the array. And, use a computed property to check for whatever it is that is your conditional that is allowing or denying your click event.
const datas=ref({
id:0,
name:'test',
age:20
})
delete datas.value.id
console.log(datas.value) //{name:'test',age:20}

Why setState changes also the props which i added to the state before

Scenario:
I have two components: X and Y. Component Y is the child of component X.
inside X:
<Y data={FormObject} />
Inside the constructor of component Y, I add the prop data (FormObject) to the state of Y which works well. Now after that, if I change the state it affects the props. How? When I close component Y and reopen it I see that the last state is showing up. FormObject isn't like how it was at the beginning. This is because objects are reference types right?
Is there a solution for this? The only solution i found is to do somthing like this:
Inside constructor of Y:
const { data } = JSON.parse(JSON.stringify(this.props);
Does it seem like JSON.parse() or JSON.stringify() changed the reference by making a copy with a different memory number?
Is there a better way of doing this instead of the mentioned solution?
If your FormObject has no child object or array, you can fix that by using object destructor like below:
const data = { ...this.props.data }; // it's a kind of shallow copy
But if it has some object or array in it, it won't prevent mutations.
So you need to deep copy using JSON.pares(JSON.stringify(data)) or Recursive method.
ok if you have to have new copy of the props in your state you can use some library for cloning the deeply nested object, you can use deepClone method from lodash.
I would also recommend taking a look at this from react documentation
hope it helps.

mobx extendObservable doesn't work as expected

I am trying to use extendObservable to add more properties on observable state but it doesn't work. Below is my code
var store = mobx.observable({
property: {}
});
mobx.autorun(function () {
console.log("render:"+store.property.a);
});
store.property = {a:1};
extendObservable(store.property, {a:2});
store.property.a=3;
The output is:
render:undefined
render:1
I initialised a store with a property object. I want to add a as a observable state under property but I didn't get the autorun executed after using extendObservable method. I expected the value 3 got printed but it didn't. What's wrong with my code? Is it the correct way to use extendObservable method?
You can read why this doesn't work in the Common pitfalls & best practices section of the documentation:
MobX observable objects do not detect or react to property assignments
that weren't declared observable before. So MobX observable objects
act as records with predefined keys. You can use
extendObservable(target, props) to introduce new observable
properties to an object. However object iterators like for .. in or
Object.keys() won't react to this automatically. If you need a
dynamically keyed object, for example to store users by id, create
observable _map_s using
observable.map.
So instead of using extendObservable on an observable object, you could just add a new key to an observable map.
Example
var store = mobx.observable({
property: mobx.observable.map({})
});
mobx.autorun(function () {
console.log('render:' + store.property.get('a'));
});
store.property.set('a', 2);
setTimeout(function () {
store.property.set('a', 3);
}, 2000);
Please read this page https://mobx.js.org/best/react.html very minutely. There are some laws onto the usage of dynamic properties added using extendObservable. The one which you are "observing" (pun intended) is documented on that page in the section Incorrect: using not yet existing observable object properties. The right way to do this is also mentioned in the subsequent section.
Basically, your tracking functions must use the get method of the observable to ensure it gets invoked again on state mutations. The fixed code is -
mobx.autorun(function () {
console.log("render:"+store.property.get("a"));
});
extendObservable(store.property, {a:2});
store.property.set("a", 3);
So you need to use get/set with extendObservable extended objects.

Why do Redux examples pass empty object as first Object.assign() argument?

In the todoMVC example of the redux project, the reducer for dealing with todos have these lines:
export default function todos(state = initialState, action){
...
case EDIT_TODO:
return state.map(todo =>
todo.id === action.id ?
Object.assign({}, todo, { text: action.text }) :
todo
)
}
This portion of the code deals with updating a particular todo item. My question here is that since state.map() will always return a new array. Then, is it still necessary to do:
Object.assign({}, todo, { text: action.text})
Can it just be:
Object.assign(todo, { text: action.text})
UPDATE
I understand the difference between Object.assign({}, blah...) vs Object.assign(obj, blah...). Let me rephrase my question:
Redux wants reducers to return a new state instead of mutating existing state. I get it. In my example, I have an array of objects. I want to swap the order of the first two elements. Check out the jsbin example here.
Since Array.map always returns a new array, the reference to the returned array is gurranteed to be a new one. Check.
However, the elements within the returned array are not all new. The reference to the first two elements are new. However, the third item is not. It is the same third item as in the old array.
So my question is for the third element, should I use:
Object.assign({}, third_element) or simply return the third_elment
Does Redux want a new array with new references to each object inside it (even though the values stored within those new objects are identical to the old objects) or just a new array with only the updated elements being new?
This is the nature of how Object.assign works. It returns the target object as the return value of its operation. So, in the first syntax:
Object.assign({}, todo, { text: action.text})
you are creating a brand new object with the enumerable properties of todo and { text: action.text}.
If you do this:
Object.assign(todo, { text: action.text})
Then todo itself is the target object, so that will be mutated and that will be what is returned from Object.assign.
The first method is making the map operation completely immutable by creating a new array of entirely new objects.
Here is a jsbin that illustrates what I mean. Note that in the first example, the original object in the array has changed, meaning state has been mutated. In the second, the original object stays the same:
https://jsbin.com/boxeserave/edit?html,js,console
Tweet from Dan Abramov (creator of Redux):
Common Redux misconception: you need to deeply clone the state. Reality: if something inside doesn’t change, keep its reference the same!
https://twitter.com/dan_abramov/status/688087202312491008
I want to add to sma's great answer and explain why a new object needs to be returned from Object.assign.
With the way redux works, if a reducer returns the same reference it received in the state parameter, redux will assume that the state hasn't changed and won't update the views. In order to tell redux that you're changing the state, you must return a new reference. That's why you'll always see a new empty object {} as the first parameter of Object.assign in reducers.

Categories

Resources