React Native: Can't use this.setState() to set a variable inside a 2D array, but this.state.x= works - react-native

I have a 2D array of objects with key/value pairs as a state variable, and I'm trying to use the recommended way of setting/changing state variables, which is to use this.setState({x:y}) instead of directly setting it using this.state.x = y and then forceUpdate(). However, when I try to do that, it gives me an "unexpected token" error.
I basically want to flip a variable from one state to the other, so I'm usng a ternary operator. This code works
toggleBookmark(category, index) {
this.state.menuItems[category][index].bmIcon = (this.state.menuItems[category][index].bmIcon === "bookmark-o") ? "bookmark" : "bookmark-o";
}
This code, which I'd expect to do the same thing, gives an error
toggleBookmark(category, index) {
this.setState({menuItems[category][index].bmIcon: (this.state.menuItems[category][index].bmIcon === "bookmark-o") ? "bookmark" : "bookmark-o"});
}
I thought it might be the ternary operator, so I put the value into a variable and tried setting the state variable with that, but it still gives the same error.
toggleBookmark(category, index) {
var iconText = (this.state.menuItems[category][index].bmIcon === "bookmark-o") ? "bookmark" : "bookmark-o";
this.setState({menuItems[category][index].bmIcon: iconText});
}
Am I doing something wrong? Is what I want to do possible with setState()?

In Javascript, you cannot use an expression as a key for an object when creating that object inline.
The problem here is that you have done {menuItems[category][index].bmIcon: iconText} which will throw a syntax error.
If you want a quick way to solve this, you may create the object first, then assign the value to that key like this:
var state = {};
state[menuItems[category][index].bmIcon] = iconText;
this.setState(state);
It's worth noting however that ES6 Provides a sugar for doing this, and there is another answer here that might provide more insight
How do I create a dynamic key to be added to a JavaScript object variable
Update:
I now see what you meant, I had previously assumed that menuItems already defined, but what you want to do is change the value of a key inside a nested object that is in this.state
This is something that React is not really built to do, you should keep your state relatively simple, and make separate React components for each menu item, then have them manage their own state. I would strongly recommend this approach because it will keep your code clean and robust. Don't be afraid to make more components!
However if you do want to keep all this nested state in one component (not advised), then you should first make a copy of the object you want to setState on.
var newMenuItems = _.clone(this.state.menuItems);
var iconText = (this.state.menuItems[category][index].bmIcon === "bookmark-o") ? "bookmark" : "bookmark-o";
newMenuItems[category][index].bmIcon = iconText;
this.setState({ menuItems: newMenuItems });
OR
var iconText = (this.state.menuItems[category][index].bmIcon === "bookmark-o") ? "bookmark" : "bookmark-o";
this.state.menuItems[category][index].bmIcon = iconText;
this.forceUpdate();
(First method preferred, but it requires you have something like underscore or lodash installed )

I have the data chat:
chat: {
id: 'ss3k5e6j1-6shhd6-sdasd3d3-23d5-gh67',
agentName: 'egaliciar',
agentAvatar: 'http://i.imgur.com/DY6gND0.png',
messages: [
{
id: 1,
lines: [
'Me pueden ayudar?',
'Tengo problemas con mis boletos',
'Hola buen dia...',
],
time: '17:20',
},
{
id: 2,
lines: ['¿Me podria regalar su nombres', 'Con gusto...'],
time: '17:22',
date: '23/ene/2012',
},
],
},
};
and when i do
const oldLines =Object.assign({}, this.state.chat);
oldLines.messages[0].lines.push('newValue');
My state Changed..... without this.setState({});
I Made a Clone;
var clone = JSON.parse(JSON.stringify(this.state.chat));
clone.messages[0].lines.push('new Value');
and the State maintain their state;
thus, the complete solution is for me:
var clone = JSON.parse(JSON.stringify(this.state.chat));
clone.messages[0].lines.push(questionAreaMessage); //the state maintains
this.setState({chat:clone}); //here the State change!!!!

Related

Event only firing as inline JS statement

I have the following code in a Nuxtjs app in SSR mode.
<Component
:is="author.linkUrl ? 'a' : 'div'"
v-bind="!author.linkUrl && { href: author.linkUrl, target: '_blank' }"
#click="author.linkUrl ? handleAnalytics() : null"
>
The click event in case it's an a tag, will only fire if it's written as handleAnalytics(), but handleAnalytics will not work.
Don't get me wrong the code is working, but I don't understand why.
With classical event binding (#click="handleAnalytics), Vue will auto bind it for you because it sees it's a function.
But when provided a ternary condition, it's not auto binded but wrapped into a anonymous function instead. So you have to call it with parenthesis otherwise you're just returning the function without executing it.
To be clearer, you can write it this way: #click="() => author.linkUrl ? handleAnalytics() : null"
Note: when having a dynamic tag component, I'd suggest to use the render function instead.
This is an advanced technique, but this way you won't bind things to an element that doesn't need it (without having the kind of hack to return null).
Example:
export default {
props: {
author: { type: Object, required: true },
},
render (h: CreateElement) {
const renderLink = () => {
return h('a', {
attrs: {
href: author.linkUrl,
target: '_blank',
},
on: {
click: this.handleAnalytics
},
)
}
const renderDiv = () => {
return h('div')
}
return this.author.linkUrl ? renderLink() : renderDiv()
}
}
Documention: Vue2, Vue3
In javascript functions are a reference to an object. Just like in any other language you need to store this reference in memory.
Here are a few examples that might help you understand on why its not working:
function handleAnalytics() { return 'bar' };
const resultFromFunction = handleAnalytics();
const referenceFn = handleAnalytics;
resultFromFunction will have bar as it's value, while referenceFn will have the reference to the function handleAnalytics allowing you to do things like:
if (someCondition) {
referenceFn();
}
A more practical example:
function callEuropeanUnionServers() { ... }
function callAmericanServers() { ... }
// Where would the user like for his data to be stored
const callAPI = user.preferesDataIn === 'europe'
? callEuropeanUnionServers
: callEuropeanUnionServers;
// do some logic
// ...
// In this state you won't care which servers the data is stored.
// You will only care that you need to make a request to store the user data.
callAPI();
In your example what happens is that you are doing:
#click="author.linkUrl ? handleAnalytics() : null"
What happens in pseudo code is:
Check the author has a linkUrl
If yes, then EXECUTE handleAnalytics first and then the result of it pass to handler #click
If not, simply pass null
Why it works when you use handleAnalytics and not handleAnalytics()?
Check the author has a linkUrl
If yes, then pass the REFERENCE handleAnalytics to handler #click
If not, simply pass null
Summary
When using handleAnalytics you are passing a reference to #click. When using handleAnalytics() you are passing the result returned from handleAnalytics to #click handler.

Trying to change my immutable array in React Native

I have a function that runs every time your location changes and I'm trying to set a value in my array when a certain if statement is found true. All I seem to be doing is removing everything from my variable except the value that I am changing. Bad explanation so here is some code...
The data starts like this:
this.state = { selectedItem: [] }
And will change to something like this during normal app use:
selectedItem: [{address: 'Somewhere', latitude: -37.826835, longitude: 144.992030, found: false }]
Here is where I am trying to change the data (This will always run after some data is added):
const newSelectedItem = () => {
let copyB = {...this.state.selectedItem};
copyB.found = true;
return copyB;
};
this.setState({selectedItem: newSelectedItem});
When I try to run:
{this.state.selectedItem.address}
I see the initial value which would be the address "Somewhere" but when my function runs based on location change it disappears. What have I actually done to my data in my above function?
Have I just made it selectedItem: [{found:true}] or something dumb like that?
You were doing fine except for one single thing , in this function :
const newSelectedItem = () => {
let copyB = {...this.state.selectedItem};
copyB.found = true;
return copyB;
};
this.setState({selectedItem: newSelectedItem});
here copyB is now an object , but yours selected Item was an array. So the problem is now selectedItem is now an object when you do setState with newSelctedItem.
SO copyB.found = true; wouldnt evaluate anything rather, try copyB[0].found = true; so there the value will be accessed and return true accordingly.
And when you try to access the state , replace {this.state.selectedItem.address}
with {this.state.selectedItem[0].address} ,
Hope i helps. feel free to ask any doubts.
You can play around with this pen codepen
Well, this is pretty weird since you still get the initial value. There are some problems in your code:
You want to store your variables in a array: It's fine, but the problem comes from the way you retrieve and set your value. Since selectedItem, your {this.state.selectedItem.address} because this is an object destructuring. To do that, you have to destruct your array first, e.g: item = selectedItem[0] or using map, etc... After that, you can try: item.address.
Another problem is from your newSelectedItem. Since let copyB = {...this.state.selectedItem}; will destruct your selectedItem, take all its properties and set to newSelectedItem, it will make your selectedItem become an object, not an array anymore.
If your selectedItem stores only 1 object, so don't use array. This selectedItem will become:
selectedItem: {
address: 'Somewhere',
latitude: -37.826835,
longitude: 144.992030,
found: false
}
It looks like a JSON object, hence you can do: selectedItem.address
In case you still don't get it, place a little debug or a console.log("selectedItem", this.state.selectedItem) to see what happend, and you will find out.
I think in the end I was mapping a function to the data and not the data itself. This was my eventual solution...
const newMyWaypoinys = this.state.myWaypoints.map(a => {
let copyA = {...a};
if (copyA.address === wp.address) {
if (copyA.address === this.state.selectedItem.address) {
this.setState(prevState => ({
selectedItem: {
...prevState.selectedItem,
found: true
}
}))
}
copyA.found = true;
}
return copyA;
});
this.setState({
myWaypoints: newMyWaypoinys,
});

Vuex mutation inside array of objects after backend update saves

I have an array of objects. When my api executes the update method and saves, I'm am broadcasting an event through laravel-echo-server and attempting to mutate state with the update object. I'm trying for real-time updates. Everything but the actual mutation is going according to plan. Here is the beginning of it:
updateExample (state, example) {
state.examples.map(e => {
if (e.id === example.id) {
// entirely replace the old object with the new
}
return false
})
},
What is an ideal way to do this? I could also pop the old object out of the array and push a new one, but that seems wonky.
Nevermind, figured it out:
updateExample (state, example) {
let index = state.examples.findIndex(e => e.id === example.id)
if (index !== -1) {
state.examples[index] = new Example(example)
return
}
state.examples.push(new Example(example))
}
Thanks for looking at it!

React-Native + Redux: Random number of form fields

I am a newbie to react-native, redux and saga and have run into a use case that I have not been able to find a solution for. I understand how to map state to properties and pass around the state between action, reducer and saga. This makes sense to me so far. This is where things seem to get dicey. I have a form that requires a variable number of form fields at any given time depending upon what is returned from the database.
As an example, let's say I have a structure like this:
{
name: ‘’,
vehicleMake: ‘’,
vehicleModel: ‘’,
carLotCity: ‘’,
carLotState: ‘’,
carLotZipCode: ‘’,
localPartsManufacturers: [{name: ‘’, address: ‘’, zipCode}]
}
Everything from name to carLotZipCode would only require one text field, however, the localPartsManufacturers array could represent any number of object that each would need their own set of text fields per each object. How would I account for this with redux as far as mapping the fields to the state and mapping the state to the properties? I am confused about how to begin with this scenario. I understand how to project mapping when the fields are fixed.
I would keep the data as it is coming from the backend. That way you'll avoid normalizing it. I think we just have to be smarter when rendering the fields. Here's what I'm suggesting:
function onTextFieldChange(name, index) {
// either name = `name`, `vehicleMake`, ...
// or
// name = `localPartsManufacturers` and `index` = 0
}
function createTextField(name, index) {
return <input
type='text'
name={ name }
onChange={ () => onTextFieldChange(name, index) } />;
}
function Form({ fields }) {
return (
<div>
{
Object.keys(fields).reduce((allFields, fieldName) => {
const field = fields[fieldName];
if (Array.isArray(field)) {
allFields = allFields.concat(field.map(createTextField));
} else {
allFields.push(createTextField(fieldName));
}
return allFields;
}, [])
}
</div>
);
}
Form receives all the data as you have it in the store. Then we check if the field is an array. If it is an array we loop over the fields inside and generate inputs same as the other properties createTextField. The tricky part here is how to update the data in the store. Notice that we are passing an index when the text field data is changed. In the reducer we have to write something like:
case FIELD_UPDATED:
const { name, index, value } = event.payload;
if (typeof index !== 'undefined') {
state[name][index] = value;
} else {
state[name] = value;
}
return state;
There is nothing preventing you from keeping a list, map, set or any other object in Redux.
The only thing remaining then, is how you map the state to your props, and how you use them. Instead of mapping a single element from the collection to a prop, you map the entire collection to a single prop, and then iterate over the collection in your render method.
In the action you can pass a new collection back, which is comprised of the form fields making up the parts list. Then, your reducer will replace the collection itself.
Or, upon changing an element in the part collection, you can send an action with its id, find it in the collection in the reducer and replace the element that was changed / add the new one / remove the deleted one.

how to push values in array and pass these values to Select tag

I want to push values in array and pass these values as options of select tag. I did following,
used plugin
import DropDown, {
Select,
Option,
OptionList,
} from 'react-native-selectme';
assigned state as
this.state = {company:[]};
pushing in this array as
for(let i in data.companyRecord)
company.push(data.companyRecord[i].companyname);
and assigning to select tag as
<Select
width={250}
ref="SELECT1"
optionListRef={this._getOptionList.bind(this)}
defaultValue="Select a Company ..."
onSelect={this._company.bind(this)} asyncOptions={this.state.company}>
</Select>
But it is not working. It is showing that
undefined is not an object('evaluating children.length').
Please help me solving this issue.
You can't edit the state like that.
const tempNames = [];
for(let i in data.companyRecord)
tempNames.push(data.companyRecord[i].companyname);
this.setState({ company: tempNames });
Now your state will have the correct values.
But there might still be some problem, because your error might suggest that this.state.company is undefined, however you correctly assigned this.state.company to an empty array before.
This could be due to this.state is undefined. Are you defining your selector in a own created function? And not in your class own render method? In that case you need to bind this to your method.
renderSelector() {
return (<Select
width={250}
ref="SELECT1"
optionListRef={this._getOptionList.bind(this)}
defaultValue="Select a Company ..."
onSelect={this._company.bind(this)} asyncOptions={this.state.company}>
</Select>);
}
In your constructor you need to bind "this" to that method. Like this:
this.renderSelector = this.renderSelector.bind(this);