I have a main file that creates and destroys instances of a class Child. The Child instances are kept in an array (childArr) within main and I want a way to listen for events from all of those Child instances.
Each Child has a Subject exposed and I want to keep a subscription to all those Subjects that remains up to date as Child instances are added to or removed from the Array.
This stackblitz project is the simplest version I can come up with. As it is it works while I manually add each child subject to the merge() method (lines 22-24). The commented out lines at the bottom (lines 28-30) are what I had hoped to do but that don't seem to work.
I would love someone to explain the difference between the merge on lines 22-24 and the one on line 28-30, why the first works and the second doesn't and hopefully how I can make the second one work.
Any help appreciated.
The behavior of merge operator, when passed an ArrayLike is much like FROM operator where each item in the array is wrapped with a new Observable.
Simply put:
merge([1,2,3]) equals to from([1,2,3)]
Passing merge an array of Observables would results in each item getting wrapped with a new Observable as demonstrated below:
merge([of(1), of(2)]).subscribe(x => {
console.log(isObservable(x)) // true
});
To avoid that consider using the SPREAD operator as in the following example:
merge(...childArr.map(x => x.feed$)).subscribe(e => {
console.log(e);
});
Related
I've reproduced the issue in the following codesandbox:
https://codesandbox.io/s/unruffled-danilo-mbb0e?file=/src/App.js
I have a state array of objects [{name:"Tom"},{name:"Dick"},{name:"Harry"}].
I want to be able to duplicate the object present at a specific index in the array. I've provided a button "Duplicate" to do so in the sandbox.
Follow the following steps in the sandbox to recreate the issue:
Click Duplicate under "Dick" to duplicate the Dick object in the state array
Now click "change" under one of the two Dicks. Notice that my code only changes one "Dick" object in the state array, but the other duplicate one automatically gets changed.
I want to avoid this. I don't want the two Dick objects to remain linked forever. How do I do this?
Your'e shallow copying the array. You need to deep copy.
Replace let copy = cur.slice(); with let copy = cur.map(item => {return {...item}}); and your code should work. basically we need to destructure the inner object to get a new copy of the every object in the array. You can read about this here Object Immutability in JS
I am attempting to use react-spring's useSprings to enable users to reorder the items in a formik FieldArray. The useSprings Draggable List demo (found here) uses useRef to manage the order of items. FieldArray comes with a number of array helper functions for inserting, removing, and moving items.
The issues that I'm having are:
1) Re-ordering existing items using formik's move array helper method successfully changes the array order, but requires an additional click to render the correct order
2) Adding or removing array items using array helper methods produces unexpected results. Mutating the length of the ref doesn't change the length of order.current inside of useGesture
I've also tried using useState instead of useRef and updating the state with useEffect when props change.
Here is a code sandbox I made: https://codesandbox.io/s/usesprings-with-fieldarray-56bex
In the bind function, commenting out order.current = newOrder; and uncommenting // arrayHelpers.move(currIndex, currRow); shows issue #1 that I mentioned above.
I would like to be able to use formik's move, insert, and remove helper functions with react-spring to seamlessly re-order, add, and delete items within a FieldArray.
maybe you can try setting the new order.current after adding the new element
onClick={() =>{
arrayHelpers.insert(items.length, {
name: `Item ${items.length + 1}`
})
order.current = [...order.current, order.current.length];
}
}
this will add the new item at the end of the list.
I encountered at least your #1 issue.
Note that the the setSprings function doesn't re-render anything on its own, and the useSprings is missing a dependencies array to auto-update.
react-springs#9.0.0.beta-23 has a dependencies array, and together with the useSpringsFixed wrapper in the sandbox that is linked here it should force-rerender on changed props.
Hope that helps your issue too.
I want to understand what happens if I don't use keys in dynamically added components. I removed keys and it renders without any issue and just gave warning messages regarding key usage. Would someone please give some example of what the consequences are if we don't use keys?
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity:
Example:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
TL;DR Use unique and constant keys when rendering dynamic children, or expect strange things to happen.
One of the tricky aspects I've found during the few weeks I've been using React.js is to understand the key property you're expected to pass to a component when it's part of an array of children. It's not that you have to specify this property, things will work most of the time apart from getting this warning on the console:
Each child in an array should have a unique "key" prop. Check the render method of undefined.
By reading the linked documentation it can be easy to not see the implications of this affirmation:
When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).
At first it looked to me it was all about performance but, as Paul O’Shannessy pointed, it's actually about identity.
The key here is to understand not everything in the DOM has a representation in React "Virtual DOM" and, because direct manipulations of the DOM (like a user changing an value or a jQuery plugin listening an element) are unnoticed by React, not using unique and constant keys will end up with React recreating the DOM node of a component when the key is not constant (and losing any untracked state in the node) or reusing a DOM node to render another component when the key is not unique (and tying its state to this other component).
Here you have a live demo showing how awful the results are:
http://jsfiddle.net/frosas/S4Dju/
Just add an item, change it, add more items and see what happens.
Also see
Source
Another useful usage of React keys other than creating dynamic elements is reseting elements when their keys change, for example in a project I had an <input/> element of type file and I wanted the element to be initialized to its initial value (no file chosen) each time the component renders, so I did the following:
Parent constructor:
this.state = {
fileInputKey: Date.now()
// other properties
};
The state object also had other properties, I just added this one for the sake of this example
Each time I wanted the input element in the child component be reset I did:
this.setState({fileInputKey: Date.now()});
Parent render:
<Child fileInputKey={this.state.fileInputKey}/>
Child render:
<input key={this.props.fileInputKey} type="file" onChange={this.onSelectFile}/>
Also see this example from React blog:
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
This may well be a very basic problem for anyone familiar with knockout.js, however it is causing me a problem.
I have a situation where I have a model containing an array of items that is dynamically added to and displayed in the view.
So far no problem, I can add entries into the model and the view is updated appropriately.
However. each item in the array itself has an array as a property, this is an array of object, and when I update the properties on these objects the view is not updated.
It is difficult to demonstrate this is a short code snippet so I have created a JsFiddle to show the problem:
https://jsfiddle.net/mikewardle/t0nvwqvL/1/
I have tries making the properties generated by calling
ko.observable()
rather than initializing them directly, but to no avail.
clicking the add button adds items to the array on the model itself.
either of the change... buttons alters the properties of the objects in the inner array.
As Ko2r stated your properties are not declared as observables and therefore updates will not be noticed by knockout.
To fix your changecolors() function you just need to change your linePusher function to create the color as an observable:
var linePusher = function (color, name) {
self.lines.push({ color: ko.observable(color), name: name, current:0 });
};
and then update usages of the color property to box/unbox the observable instead of replacing its value with the standard assignment operator, "="
for (i=0;i<counters.length;i++){
var lines = counters[i].lines();
for (j=0;j<lines.length;j++){
//lines[j].color = color;
lines[j].color(color); //sets the existing observable to the new value
}
}
Unfortunately I can't seem to make sense of your code enough to figure out what the increment() function is supposed to be doing so I can't tell you how to fix it, but hopefully the fixes to changecolors() put you on the right track.
You might want to read up on working with observables
I've posted my code here: http://jsfiddle.net/HYDU6/6/
It's a pretty stripped-down version of what I'm actually working with, but captures the essence of my problem. My view model is like so:
var viewModel = {
objects: {
foo: [
{ text: "Foo's initial" },
],
bar: [
{ text: "Bar's initial" },
]
}
}
I'm using the ko.mapping plugin and my create handler for objects instantiates Obj from objects.foo and then objects.bar, returning the resulting two items in an array. This part works fine; I use
var view = {};
ko.mapping.fromJS(viewModel, mapping, view);
My issue is updating based on new data. (i.e., getting data from the server). I have an object of new data and I attempt
ko.mapping.fromJS(new_model, mapping, view);
I suspect this is incorrect but I have not been able to get it working despite extensive searching. (Trust me, it's been days. ): Anyway, thanks for any help.
EDIT: So I've mostly figured it out - I was depending too heavily on mapping.fromJS and certain things were not being wrapped into observables. I also realized that I didn't need the create(), only the update(), as it is called after create() anyway. If you have a similar problem let me know!
John,
When updating your data using ko.mapping be sure you don't create a new item. Your UI is already bound to the existing items, so you just want to update the values of the existing item properties; not create new ones. For the example you posted, you'll want to adjust your "update" method of your map to insert the new values into the correct ko.observable property, rather than creating a new object in it's place. The ko.mapping "update" method has a few different parameter lists depending on usage, with the third parameter being the target object of the map. You would want to update that object's properties.
obj.target[label].items[0].text(obj.data[label][0].text);
But, that's a bit of a mess. You'll probably want to create a second level of mappings (create / update) to handle "deep" object hierarchies like in your fiddle. For example one map for objects at the "foo/bar" level, and another call to ko.fromJS from within "update" with another map for the child Obj() objects.
After fixing that, you'll run into a couple simple binding errors that you can fix using another "with" binding, or a "foreach" binding for the child arrays.
Overall, you've just run into a couple common pitfalls, but nothing too severe. You can learn a bit more about a few of these pitfalls on my blog here : http://ryanrahlf.com/getting-started-with-knockout-js-3-things-to-know-on-day-one/
I hope this helps!