Following proper standards of keeping this.state immutable
const name = this.refs.name.value;
const names = [ ...this.state.names, name ];
//add new name to names array, and finally
this.setState({ names: names });
So I'm trying to understand what is setState trying to do here. Replace the old names array with the new updated names array?
Well { names: names } is just plain simple javascript object and what setState does is that the state names is being mutated to the new state which is the updated array.
What you are seeing there is the spread operator .... In ruby we have something very similar called splat operator. names becomes the result of concatenating all the elements of this.state.names and name into a new array. setState({names: names}) then updates the internal state of the component with the new names property.
One of its uses it's spreading array to use it's elements as separate arguments for a function call.
// Equivalent
console.log(...[1,2,3])
console.log(1,2,3)
// Usage in function definition
function asdf(qwer, ...uiop) {
console.log('NICE', qwer, uiop)
}
asdf(1,2,3,4);
// a declarative replacement for this procedural code
function asdf2() {
var qwer = arguments[0];
var uiop = Array.prototype.slice.call(arguments, 1, arguments.size);
console.log('UGLY', qwer, uiop);
}
asdf2(1,2,3,4)
Beware JSX is a syxntax extension and may not have all the latest es5 and es6 features.
I guess the answer is there.
The single colon in array sets the array value to the object.
facebook.github.io/react/docs/react-component.html#setstate
Related
In my use case I have an array of characters, each character has multiple builds, and each build has a weapons string, and artifacts string. I'm making a tool to select portions of each string and assign them to a value, e.g. assigning index 3-49 of weapons to a specific weapon.
const [characterIndices, setCharacterIndices] = useState<
{ builds: { weaponIndices: SE[]; artifactSetIndices: SE[] }[] }[]
>([
...characters.map((char) => {
return {
builds: [
...char.builds.map((_build) => {
return {
weaponIndices: [],
artifactSetIndices: [],
};
}),
],
};
}),
]);
The SE type is as follows:
type SE = { start: number; end: number; code: string };
//start and end are the respective start and end of selected text
//code is the specific artifact or weapon
The weaponIndices and artifactSetIndices basically hold the start and end of selected text in a readonly textarea.
I have a function to add a SE to either weaponIndices or artifactSetIndices:
const addSE = (
type: "weaponIndices" | "artifactSetIndices",
{ start, end, code }: SE,
characterIndex: number,
buildIndex: number
) => {
let chars = characterIndices;
chars[characterIndex].builds[buildIndex][type].push({ start, end, code });
setCharacterIndices((_prev) => chars);
console.log(characterIndices[characterIndex].builds[buildIndex][type]);
};
I think that using a console log after using a set function isn't recommended, but it does show what it's intended to the weaponIndices, or artifactSetIndices after an entry is added.
Passing the addSE function alongside characterIndices to a separate component, and using addSE, does print the respective indices after adding an entry, but the component's rendering isn't updated.
It only shows up when I "soft reload" the page, when updating the files during the create-react-app live reload via npm run start.
In case you are confused about what the data types are, I've made a github repo, at https://github.com/ChrisMGeo/ght-indexer/tree/main/src at src/data.json. That JSON file describes what the character data looks like, including the builds, and each build's weapons and artifacts(called artifact_sets in the JSON)
Looks to me you are not updating the state at all.
Here you are just storing the same object reference that you already have in state into a new variable chars.
let chars = characterIndices;
chars now holds reference to a same object as characterIndices.
Here you are mutating that same object
chars[characterIndex].builds[buildIndex][type].push({ start, end, code });
And here you are updating the state to the same object that is already in the state. Notice that no state update here occurs.
setCharacterIndices((_prev) => chars);
Object you have in state is mutated, but you did not "change" the value of the state, thus no component re-render.
What you could maybe do is create a copy of the object, mutate that and update the state. just change chars assignment like this:
let chars = {...characterIndices};
React often compares values using Object.is() only to a single level of nesting (the tested object and its children).
It will not re-render if the parent is found equal, or if all the children are found equal.
React then considers that nothing has changed.
In your implementation, even the first top-level check will immediately fail, since Object.is(before, after) will return true.
You could use an Immutable objects approach to eliminate this concern when setting a new state (either directly through spreading values or with a support library such as Immer).
For example instead of setting the values within the object...
myObj.key = newChildObj
...you would make a new object, which preserves many of the previous values.
myObj === {...myObj, key: newChildObj}
This means that every changed object tree is actually a different object (with only the bits that haven't changed being preserved).
To read more about this see https://javascript.plainenglish.io/the-effect-of-shallow-equality-in-react-85ae0287960c
I have a mongoose object which contains an array of ObjectIds, being used for population from another table. I want to be able to dedupe these. eg I have
[ '61e34f3293d9361bbb5883c7' ,'61e34f3293d9361bbb5883c7', '61e34f3293d9361bbb5883c7' ]
When i print and iterate through these they look like strings.
But they also have an _id property, so I think they're somehow "populated" or at least contain references to the child table.
What's the best way to do this? I tried:
const uniqueTokens = _.uniqBy(tokens, '_id') which doesn't seem to work as _id is some kind of Object.
converting to a string will allow me to dedupe:
const tokens = this.tokens || []
let newTokens: string[] = []
for (let t of tokens) {
const text = t.toString()
// clog.info('t', t, t._id, typeof t._id)
if (!newTokens.includes(text)) {
newTokens.push(text)
}
}
but then these aren't real Objects I can assign back to the original parent object.
// this.tokens = newTokens
await this.save()
I could maybe go through and re-find the objects, but that seems to be digging deeper into the hole!
Seems there must be a better way to handle these type of types...
related searches
How to compare mongoDB ObjectIds & remove duplicates in an array of documents using node.js?
I also tried using lean() on the tokens array to try and convert it back to a simple list of references, in case somehow the 'population' could be undone to help.
I'm down to creating a unique signature field for the referenced items and de-duping based on that.
Is it possible to chain a React hook? If so, how?
A typical application of a hook would look like this:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
b = a.filter(isDairy)
updateInventory(b)
We can also do this, but it's not chained:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
updateInventory(a.filter(isDairy))
What I want is a chained hook in a functional style:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
a.filter(isDairy).updateInventory()
Can a hook can be modified to take state from this?
Proper usage would be:
updateInventory([...a, "cheddar"].quicksort().filter("cheese"))
But if you really want that chaining, look into how to edit the array prototype.
This is really not recommended, as that method will then be available on all arrays.
I think the underlying problem is you're not clear on what's actually happening with method chaining and possibly with hooks. The specific question:
Can a hook can be modified to take state from this?
doesn't really make sense. So let's break down why then come back at the end to how you could approach this.
For method chaining, let's try a simple example using two methods, .filter and .map, that have two important properties:
They actually return arrays (unlike .push, which returns the new length of the array); and
They actually exist on arrays (unlike .quicksort, which exists on neither an array nor the integer you were calling it on).
function isDairy(item) {
return ["cheese", "milk"].includes(item);
}
function getPrice(item) {
return { bread: 0.58, cheese: 0.80, apples: 0.47, milk: 1.01 }[item];
}
const inStock = ["bread", "cheese", "apples"];
inStock
.filter(isDairy)
.map((item) => ({ item, price: getPrice(item) }));
// => [{ item: "cheese", price: 0.8 }]
There's nothing particularly special happening here, each method you're calling returns a new array on which you can also call any method an array has. You could assign the intermediate steps and get the same result:
const filteredStock = stock.filter(isDairy);
// => ["cheese"]
const pricedFilteredStock = filteredStock.map((item) => ({ item, price: getPrice(item) }));
// => [{ item: "cheese", price: 0.8 }]
It is not the case that:
these are standalone functions (like in e.g. Python where you map(callable, iterable)); or
that the item.name syntax is doing anything beyond just accessing a property named name on the item.
If I tried to use the filter method as a standalone function:
filter(isDairy, inStock);
that would be a ReferenceError, or if I defined another function and tried to access it as if it was a prop on an array:
function allUppercase() {
return this.map((item) => item.toUpperCase());
}
inStock.allUppercase();
it would be a TypeError (because isStock.allUppercase is undefined and undefined isn't callable).
Note you could do allUppercase.bind(inStock)() (or the neater allUppercase.call(inStock)), though; JavaScript does have a means of setting this for a function.
When you use the useState hook, you're calling a function that returns an array containing two objects, and destructuring that array to two local variables:
const [thing, setThing] = useState(initialValue);
is equivalent to:
const result = useState(initialValue);
const thing = result[0];
const setThing = result[1];
The thing, setThing naming is just a convention; really, we're accessing those two objects (current value and setter function) by position. They don't have names of their own, you can do const [foo, bar] = useState("baz") (but... don't).
As the setter is a function you might be wondering whether you can use setThing.bind here, but if setThing is written to use this (I didn't look into the implementation, as it's not directly relevant), it's not going to be happy if you change what this is!
So this comes together when you try to do:
const [basket, setBasket] = useState([]);
// ^^^^^^^^^
inStock.filter(...).map(...).setBasket();
// ^^^^^^^^^
As with the example above, this is a TypeError because setBasket doesn't exist on the array returned by .map. The fact that the same "word" setBasket appears twice is totally irrelevant as far as JavaScript is concerned; one is a local variable and the other is a prop on an array, there's no connection between them.
.map(...) returns a new array, one that we didn't already have a reference to, so the only way to make this work is to ensure all arrays have a setBasket method, which means patching the prototype (as covered in adding custom functions into Array.prototype):
Object.defineProperty(Array.prototype, "setBasket", {
value () {
setBasket(this);
},
});
One problem here is that the function setBasket is accessed via a closure, so it needs to happen inside the component where the hook is defined, so it's going to get defined every time the component is rendered (or you're going to useEffect), which is a problem because you can't redefine that method as written...
But let's ignore that because the bigger problem is that every array in your app now has that method, even in contexts where it's not relevant. If you have multiple state hooks, as seems likely in any non-trivial app, your arrays are gaining lots of methods globally that are only for use in small local scopes.
A more feasible approach is to add a generic method that can be used to apply any hook (in fact any function) to an array:
Object.defineProperty(Array.prototype, "andCall", {
value (func) {
return func(this);
},
});
This can be added once, globally, and used to apply whatever hook is relevant:
inStock.filter(...).map(...).andCall(setBasket);
Note that if you're using TypeScript, you'd also have to add the definition to the global array type, e.g.:
declare global {
interface Array<T> {
andCall<S>(func: (arr: Array<T>) => S): S;
}
}
I don't understand something and need explanations please !
I have a datatable and selection of lines generate in my .ts an array of Operation object. here is my object class :
export class Operation {
id: number;
name: string;
}
this is the declaration of array :
selectedOperations: Operation[];
when I log in console before extraction of ids, I have this :
this.selectedOperations = {"selected":[{"id":1,"name":"My name 1"},{"id":3,"name":"My name 3"}]}
and when I want to extract ids with this :
let ids = this.selectedOperations.map(o => o.id);
I have an exception =>
this.selectedOperations.map is not a function
It's not the first time I have this problem and I'd like to understand why. I search some reasons and found differences between Array and object[] ? I think it's not really an array because there is the {"selected": before the array...
Can someone tell me the thing and help me for extract ids ?
thanks a lot !
{"selected":[{"id":1,"name":"My name 1"},{"id":3,"name":"My name 3"}]} => this is of type object, whereas your array declaration looks like this selectedOperations: Operation[];
You either directly assign the array to your variable:
this.selectedOperations = [{"id":1,"name":"My name 1"},{"id":3,"name":"My name 3"}];
Or you can change your variable type to any or object:
selectedOperations: any;
this.selectedOperations = {"selected":[{"id":1,"name":"My name 1"},{"id":3,"name":"My name 3"}]}
const ids = this.selectedOperations.selected.map(o => o.id);
this.selectedOperations.map is not a function error is caused by the initialization, map function is reserved for arrays, therefore it throws an error when you try to use it on an object type variable.
I would recommend the first approach by the way, declaring a variable as any or object is contradicting with the purpose of Typescript.
You need to make some improvements to the code. In order to get the ids, you need to add selected to this.selectedOperations. See below.
let ids = this.selectedOperations.selected.map(o => o.id);
I am trying to update a immutablejs object;
//action.vals = {element: "p", type: "text", content: "test", className: "paragraph-topic"}
return state
.updateIn(['contents'], list => list.push(action.vals)) //<<<<<THis is failing
.set('loading', false)
.set('error', false)
break;`
But instead it is storing 10 an integer .
I am really confused and i am in need of help suggestion.
Here is my full code
Any help is much appreciated! I am going nuts due to lack of proper usage documentation
I've noticed that on the LOAD_DATA reducer, you're setting contents to become an array instead of an immutable List:
case LOAD_DATA:
return state
.set('loading', true)
.set('error', false)
.setIn(['contents'], [])
This would cause quite a few problems. What's happening is that you're using the vanilla JS push function on your update, and that returns the length of the array. So I'm supposing contents has 10 elements?
You just need to change the LOAD_DATA reducer to have this instead:
.setIn(['contents'], fromJS([]))
or
.setIn(['contents'], new List())
If using List(), that has to be explicitly imported as well!
Do some thing like this using ....
.updateIn(['contents'], list => [...list, action.vals])