Cannot add item to JS Map - javascript

I'm simply trying to add an item to a Map (like dictionary) in Javascript. I'm using React Native.
import React, { Component } from 'react';
import {
AppRegistry,
View,
Button,
} from 'react-native';
export class Stack extends Component {
constructor(props){
super(props)
this.state = {
statelist: new Map(),
}
}
add_func_list() {
let plist = new Map()
plist.set(1, 'ddd');
alert(JSON.stringify(plist))
}
add_state_list(){
let statelist = this.state.statelist
statelist.set(2, 'ggg');
this.setState({statelist})
}
render() {
return (
<View>
<Button title="add to func list" onPress={this.add_func_list} />
<Button title="add to state list" onPress={this.add_state_list} />
</View>
);
}
}
export default function App(props) {
return (
<Stack />
);
};
AppRegistry.registerComponent('default', () => App);
If you press the first button I'm adding to a local variable in a function. Nothing gets added, the alert shows {}
The second button should add to a state variable, but I'm getting an error:
"TypeError: undefined is not an object (evaluating 'this.state.statelist')
Any ideas?

The second error is due to code error:
onPress={this.add_state_list.bind(this)}
or convert the method to a arrow function.
Regarding the alert you can't Stringify it. its data is in prototype.
Map is an iteratable.
you can do :
alert(plist.size);
or
alert(plist.get('2'));

add_func_list() {
let plist = new Map()
plist.set(1, 'ddd');
alert(JSON.stringify(plist.get(1))
}

Thanks Mukesh and Vivek. I'm new to RN so I wasn't aware of the restriction in printing a map. I actually have a bind of the method in my constructor, but failed to copy it into the example. Unfortunately the consequence was that it generated the same error as I have in my real system and I got fooled. Anyway, you answered the questions as I posted them, for which I'm thankful.
What turned out to be the real problem was a consequence of inheritance, and as I'm learning now inheritance of state doesn't work like other inheritance.
import React, { Component } from 'react';
import {
AppRegistry,
Button,
View,
} from 'react-native';
export class Stack extends Component {
constructor(props){
super(props)
this.state = {
statelist: new Map(), // get's overwritten and destroyed by child state assgnment
}
//this.statelist = new Map(), <-- this way it works fine if child sets state
this.add_state_list = this.add_state_list.bind(this)
}
add_state_list(){
this.statelist.set(2, 'ggg');
alert(JSON.stringify(this.statelist.get(2)))
}
}
export class StackHandler extends Stack {
constructor(props){
super(props)
// if I remove this.state assignment below, it works
this.state = {
count:0 // destroys parent's state
};
}
render() {
return (
<View>
<Button title="add to state list" onPress={this.add_state_list} />
</View>
);
}
}
export default function App(props) {
return (
<StackHandler/>
);
};
AppRegistry.registerComponent('default', () => App);
This code corresponds better to my problem. Class StackHandler and class Stack both have state variables. Stackhandler inherits Stack. When Stackhandler sets its state variable, it destroys the state of its parent. For us with OOP background this seems counter-intuitive. Either way, I now know not to do this and can work around it. If anyone has a good explanation, I'm all ears. In any case I'm unstuck now!

Related

ReactJS error when passing a function as a props

Recently I started to learn ReactJS with help of tutorials and I've run into an error I can't find solution for by myself. One of the first projects I decided to do is To-do list and when trying to pass "handleChange" function as a prop to my component I get this error TypeError: Cannot read property 'handleChange' of undefined.
Here is my full code of App class so you can see what I'm trying to do:
import React from 'react';
import './App.css';
import Content from "./Content"
import ToDoItems from "./ToDoItems"
class App extends React.Component {
constructor() {
super()
this.state = {
items: ToDoItems
}
this.handleChange = this.handleChange.bind(this)
}
handleChange() {
console.log("Change!")
}
render() {
const items = this.state.items.map(function(item){
return (<Content key={item.id} todo={item.item} checked={item.completed} handleChange={this.handleChange}/>)
})
return (
<div>
{items}
</div>
)
}
}
export default App;
I'm getting my data from file called ToDoItems and trying to pass them as props into Content component. Everything is working fine until I try to pass the function handleChange().
I must be doing something wrong. Any help would be appreciated.
The problem is here,
const items = this.state.items.map(function(item){
return (<Content key={item.id} todo={item.item} checked={item.completed} handleChange={this.handleChange}/>)
})
When ur using non-array function it binds this and others stuff to its own protoype. Meaning ur this.handleChanges this actually refers to function, instead of class
Try using this,
const items = this.state.items.map((item) => {
// ^^^^^^^^^^^
return (<Content key={item.id} todo={item.item} checked={item.completed} handleChange={this.handleChange}/>)
})
as Arrow functions don't bind this or any other stuff. so this should now refer to the class

Testing this.props.navigation

import React from 'react';
import ListElements from '../Components/ListElements';
class HospitalsScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
Hospitals: ['Hospital de Base','Hospital Regional de Taguatinga','Hospital da Ceilandia','ClĂ­nica']
};
}
onPressItem() {
this.props.navigation.navigate('sectors');
}
render() {
return (
<ListElements
list = {this.state.Hospitals}
title='Hospitais'
onPress={this.onPressItem.bind(this)}
/>
);
}
}
export default HospitalsScreen;
I'm having some trouble testing this screen in the _onPress function.
I did not find anything that solved the problem.
In Javascript the value of this inside a function depends upon how that function is invoked.
Learn how the this binding works in JavaScript here
There are a couple of ways to achieve it. Use them and see if you get this as undefined in the function or not.
One is to add this.onPressItem = this.onPressItem.bind(this); in the constructor.
Another is arrow functions
onPressItem= (event) =>
{this.props.navigation.navigate('sectors');
}
And then there is onPress={this.onPressItem.bind(this)}.
You can find more details here
Also please go through this article Don't Use Bind When Passing Props

React Ref Api's

So I am following video tutorials by Max on Udemy and in one of the lectures he is trying to explain Ref Api's in react 16.3
So here is what he did, Inside on of the container class (not App.js) he created a property known as this.lastref = React.createRef(); and then created a ref tag in return JSX code which looks like this ref={this.lastref} (This is the parent component)
Now in child component he created a method which looks like this
myFocus () {
this.lastref.current.focus()
}
and then in parent component, he again did something like this in componentDidMount lifecycle
componentDidMount() {
this.lastref.current.myFocus()
}
Now here are two questions which I have.
[Question Part]
First: How can he use this.lastref in child component? Is this because of the uni-directional (or one directional) flow from Parent to child (this.lastPersonRef is referred from ref={this.lastPersonRef} ?
Second: myFocus I believe happens to be static method so shouldn't he initiate it before using it?
[Code Example]
Here is what Parent Component should look like -> [person.js]
import React, { Component } from 'react';
import Person from './persons/person-s';
class Cpersons extends Component {
this.lastref = React.createRef()
componentDidMount() {
this.lastref.current.myFocus()
}
render (
return {
<Person
key={el.id}
click={this.props.cpdelete.bind(index)}
ref={this.lastref}
name={el.name}
age={el.age}
changed={(event) => this.props.cpchanged(event, el.id)} />
});
}
}
export default Cpersons
and this should be my child component -> [person-s.js]
import React, { Component } from 'react';
class Cppersons extends Component {
myFocus () {
this.lastref.current.focus()
}
render() {
//something
return (
<div> Something </div>
)
}
}
export default Cppersons;
ref has changed a lot in the React world and documentation regarding it is wildy different. I suggest you use the callback method.
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.otherComponentRef = null; // Will be set after the first render
}
render() {
return [
<OtherComponent ref={el => this.otherComponentRef = el} />,
<ChildComponent reference={this.otherComponentRef} />
];
}
}
First: How can he use this.lastref in child component? Is this because
of the uni-directional (or one directional) flow from Parent to child
(this.lastPersonRef is referred from ref={this.lastPersonRef} ?
when a ref is created inside a class component like this,
this.myRef = React.CreateRef();
this.myRef is assigned a null value. Later when the component is mounted, React assigns this.myRef an object with the current property making this.myRef.current an object containing either:
the dom element that the ref is attached to, or
the component that the ref is attached
In your code, lastref is attached to the Person component like so,
<Person ref={this.lastref} .../>
which at
componentDidMount() {
this.lastref.current.myFocus()
}
React assigns the Person instance (component) to this.lastref.current, like this
// ~ with a bit of React magic under the hood
this.lastref.current = new Person();
Since myFocus is a method on the instance, it can be called by this.lastref.current.myFocus()
I encourage you to read more about React Ref and its expected behavior from React docs. If you find yourself stuck, you can read about how class inheritance work in Javascript which gives more insight to what is going on behind the scenes.
Second: myFocus I believe happens to be static method so shouldn't he
initiate it before using it?
it's really just the syntax being used from a different Javascript specification
class P {
constructor(props) {
super();
this.myRef = props.myRef
}
myFocus() {
console.log(this.myRef)
}
}
is equivalent to
class P {
myFocus() {
console.log(this.props.myRef)
}
}
in the eyes of the babel-loader from Babel which transpiles the Javascript in a typical React application created with create-react-app. myFocus will be a method of the Instance when it is instantiated in both cases.

changes in state array does not change children through props

Recently, I have been working with a dynamic control of input text boxes, each with their own id's. However, I noticed that if I stored an array of elements and each element was an object with different variables and such, any change in on of those variables in the object would not alert React to update the children components(which receive the state through props). I am trying to control the input according to the documentation, but am confused as to why changes in the parent's state of a normal non array and non object variable will be recognized by the children as a change in props and yet not for normal state arrays. Here is my parent code:
import React, {Component} from 'react';
import Music from './music'
import axios from 'axios';
import update from 'immutability-helper';
class Selection {
constructor(){
this.music = '';
this.beginning = '';
this.the_end = '';
}
setTitle=(title)=>{
this.music = title;
}
setStart=(start)=>{
this.beginning = start;
}
setEnd=(end)=>{
this.the_end = end;
}
}
class Practice extends React.Component{
constructor(props){
super(props);
this.state = {
selections: Array(0),
test: 0,
}
this.removeSong = this.removeSong.bind(this);
}
removeSong(index){
var newArray = this.state.selections.slice();
newArray.splice(index, 1);
this.setState({selections: newArray, test: this.state.test+=1});
}
addAnotherSong=()=>{
var newArray = this.state.selections.slice();
newArray.push(new Selection());
this.setState({ selections: newArray, test: this.state.test+=1});
}
render(){
return(
<div>
<button onClick={() => this.props.practice()}>Log Practice Session</button>
<h1>{this.props.time}</h1>
<form >
Description: <input type="form" placeholder="How did it go?" name="fname"/><br/>
</form>
<button onClick={()=>this.addAnotherSong()}>Add Another Piece</button>
<button onClick={()=>this.setState({test: this.state.test})}>Will it now Update?</button>
{
this.state.selections.map((child, index) => (
this.state.selections[index].music,
<Music key={index} number={index} subtract={this.removeSong}
Title={this.state.test} Start={this.state.selections[index].beginning}
End={this.state.selections[index].the_end} changeTitle={this.state.selections[index].setTitle}
changeStart={this.state.selections[index].setStart} changeEnd={this.state.selections[index].setEnd}
/>
))
}
</div>
);
}
}
export default Practice;
As you can see, I have an array in the state with objects constructed from the Selection class. Here are the children that won't update unless the change happens in the non array type "this.state.test" prop.
import React, {Component} from 'react';
import InputBox from './input';
class Music extends React.Component{
constructor(props){
super(props);
}
shouldComponentUpdate(newProps){
if(this.props !== newProps){
return true;
} else{
console.log("wassup");
return false;
}
}
render(){
return(
<div>{this.props.number}
<InputBox cValue={this.props.Title} identity={this.props.number} updateInput={this.props.changeTitle} />
<InputBox cValue={this.props.Start} identity={this.props.number} updateInput={this.props.changeStart} />
<InputBox cValue={this.props.End} identity={this.props.number} updateInput={this.props.changeEnd} />
<button onClick={()=> this.props.subtract(this.props.number)}>DELETE{this.props.number}</button>
{this.props.Title}
</div>
)
}
}
export default Music;
Lastly, here is the children of that child.
import React,{Component} from 'react';
class InputBox extends React.Component{
constructor(props){
super(props);
this.state = { value: '' }
this.handleChange = this.handleChange.bind(this);
}
handleChange(event){
this.setState({value: event.target.value});
this.props.updateInput(this.state.value, this.props.identity);
console.log("test" + this.props.cValue);
}
shouldComponentUpdate(newProps){
if(this.props !== newProps){
console.log("Updating Input Component");
return true;
} else {
console.log("yo");
return false;
}
}
render(){
return(
<input type="text"onChange={this.handleChange} value={this.props.cValue}/>
)
}
}
export default InputBox;
If this is a bad question, please let me know. But, any help is always appreciated. Thankyou
You're treating your state as a mutable object - states in React should never be mutated, and a state change should always be a brand new object.
Take, for example, we have just one Selection in state.selections:
[ { music: 'music', beginning: 'beginning', the_end: 'ending' } ]
If the Music component ends up calling setStart('newBeginning'), then we end up with this in our state:
[ { music: 'music', beginning: 'newBeginning', the_end: 'ending' } ]
This looks like everything went well, but React won't actually pick up this change by default because state.selections is still referring to the same array.
To fix it, you're going to have to figure out how to update your array reference while you modify individual items. You've already done this in removeSong and addAnotherSong by calling slice on your current state. Slice returns a new array, so no issues there.
I'd recommend having methods to modify individual Selections in Practice, as opposed to each Selection modifying itself.
Something like this could work:
class Practice extends React.Component{
updateSelectionBeginning(index, newBeginning){
var newArray = this.state.selections.slice();
newArray[index].beginning = newBeginning;
this.setState({selections: newArray});
}
}
We'd then pass it down to our children via props, and they'd call updateSelectionBeginning(FOO, BAR). That lets us update the state, while also keeping it immutable.

If I need to get properties for a component from an API should I do that before the component loads?

Say I have a comp that is inside of a Scene (react-native-router-flux). It lets people choose their favorite fruits.
import React, {Component} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {MKCheckbox} from 'react-native-material-kit';
var styles = StyleSheet.create({});
export default class PickAFruit extends Component {
render() {
console.log(this.props.fruits);
return (
<View>
{
this.props.fruits.map((x)=> {
return (
<View key={x.key}>
<Text>{x.key}</Text>
<MKCheckbox checked={this.props.checked} key={x.key} onCheckedChange={(e) => {
this.props.update(e, '' + x.key)
}}/>
</View>
)
})
}
</View>
)
}
}
In the parent comp I'm loading the list of fruits from an API in the didMount:
componentDidMount() {
ApiInst.getFruits().then((fruits) => {
console.log(fruits);
console.log(this.props.fruits);
this.props.fruits = fruits;
});
}
I'm also setting a default fruits array in the parent class. It seems like the properties won't load via the API though, the list of fruit is always the "unknown" value, never the new values. Do I need to load the list of fruits before the Profile scene is loaded? When is the correct time to set properties for a component if they will come from an API?
setState seems like the easy answer but these settings don't "feel" like state, they feel like properties that would be injected at build-time (i.e. when the component is built, not the app). Is this a distinction without a real difference?
You can't modify props. Props are passed from parent to child component, and only the parent can change them.
Use setState instead:
this.setState({fruits: fruits});
And access them from state:
<PickAFruit fruits={this.state.fruits} />
You may also want to set a default state in the component constructor:
constructor(props) {
super(this);
this.state = {fruits: null};
}
this.props.fruits = fruits;
won't effect child component, and to be honest - I'm not sure it will work at all. If you don't want to use flux architecture I think the best solution is to update parent's state on componentDidMount() and pass it as props to child component:
componentDidMount() {
ApiInst.getFruits().then((fruits) => {
this.setState({fruits: fruits});
});
}
render() {
return (
<PickAFruit fruits={this.state.fruits} />
);
}
Every state change will invokre render() method, so after API call PickAFruit component will be rerendered, with fruits passed as a props.

Categories