This is probably going to be something really simple. But I am having hard time figuring out what is wrong with how my react component is written. Here is the component code.
import React, { Component, PropTypes } from 'react';
import styles from './Menu.css';
import SubMenu from './SubMenu';
import classNames from 'classnames/bind';
let cx = classNames.bind(styles);
export default class Menu extends Component{
static propTypes ={
menuData:PropTypes.array.isRequired
};
constructor(props){
super(props);
this.state = {menuOpenedLabel:""};
};
menuClick(label){
this.state.menuOpenedLabel = label;
};
render(){
let menus = this.props.menuData.map(function(menuItem){
let handleClick = this.menuClick.bind(this,menuItem.label);
return (<li key={menuItem.label}>
<a onClick={handleClick}>{menuItem.label}</a>
<SubMenu subMenu={menuItem.submenu} isVisible={this.state.menuOpenedLabel === menuItem.label}/>
</li>);
});
return (<nav>
<ul className={styles.menu}>{(menus)}</ul>
</nav>);
}
}
This is the error that I get in chrome.
Uncaught TypeError: Cannot read property 'menuClick' of undefined
At first I thought it was because of using this inside of the map function, but apparently that this code is correct. Based on this link.
https://facebook.github.io/react/tips/expose-component-functions.html
Any thoughts?
Ok I figured it out! there is a second parameter to map(), that controls what the value of this is.
let menus = this.props.menuData.map(function(menuItem){
let handleClick = this.menuClick.bind(this,menuItem.label);
return (<li key={menuItem.label}>
<a onClick={handleClick}>{menuItem.label}</a>
<SubMenu subMenu={menuItem.submenu} isVisible={this.state.menuOpenedLabel === menuItem.label}/>
</li>);
}, this);
Your component is fine, but map stuff can be confusing to look at sometimes. I find this a useful chunk of code, maybe it will help you even though you already figured it out:) Just a way of organizing a map and you get the index, too.
render() {
const { stuffs } = this.state;
return (
<div>
{stuffs.map((stuff, i) => {
return(
<Components key={i} funStuff={stuff.fun} />
);
})}
</div>
)
}
Related
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
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!
i am making a website under react with reactstrap, i have a section that contains charts and a button whose function is to replace said charts with another chart containing more details. however i am struggling to make a concrete code.
i have tried placing the charts in a separate component and have it's content switch through the use of a handleclick function on the button that changes the state of the section (using 'onclick')
i am really not confident in my code's clarity, so i tried reproducing what i did in a simpler matter within fiddle
class hello extends React.Component {
render() {
return (
<h2>hello</h2>
);
}
}
class bye extends React.Component {
render() {
return (
<h2>goodbye</h2>
);
}
}
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<div>
<div>
{this.state.components[hello]}
</div>
<button onClick={this.handleClick}>
switch
{this.setState({components:[<bye />]})}
</button>
</div>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
the div in the "toggle" component is supposed to switch between the components "hello" and "bye"
in effect the current section that is supposed to be displayed ("hello") will be replaced by the other section ("bye") uppon clicking the button under them.
thanks in advance.
If you simply want to toggle between the two components with the button click, you can use conditional rendering.
Change your render method to this:
render(){
return (
<div>
{this.state.isToggleOn?<Hello />:<Bye />}
<button onClick={this.handleClick}>Switch</button>
</div>
}
Also keep your Component's name first character capitalized or react might complain. And using Class based Components is outdated. Hooks are the hot thing right now. So try to use more Functional Components.
Note: My answer assumes you are using babel presets for transpiling jsx and es6 syntax. If not, check out #Colin's answer. It also uses hooks.
why not import all partial views and conditionally render them based on the condition
{condition & <View1/>
There's a few mistakes in your code. Here's an example which does what you want using conditional rendering:
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Hello = () => {
return <h2>hello</h2>;
};
const Bye = () => {
return <h2>bye</h2>;
};
const App = () => {
const [toggled, setToggled] = useState(true);
const handleClick = () => {
setToggled(!toggled);
};
const render = () => {
if (toggled) {
return <Hello />;
}
return <Bye />;
};
return (
<div>
<button onClick={handleClick}>toggle</button>
{render()}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
There are many ways to do it:
Using conditional operator:
{ this.state.isToggleOn?<Hello/>:<Bye/> }
Using if condition:
render() {
let chart;
if(this.state.isToggleOn) {
chart = <Hello/>;
} else {
chart = <Bye/>;
}
return ( <div> { chart } </div>);
}
3 You can use switch case also for conditional rendering. Here it is not well suited as condition is true or false.
I've been following this tutorial for ReactJS and have been trying now to convert the simplistic Todo App (just checks off and on items) to React Native. I've been using expo to try it live on my phone and everything.
It all went good, but now I'm trying to add something. Whenever I click the checkbox I want to remove the component related to that item.
My idea was:
Since I'm rendering the TodoItem components from an array of todos,
and whenever I click a checkbox it updates the array as a whole
(looking for a certain id and updating it's completed variable). I can
run through the array and whenever the id is different I return the
todo. This way I returned every todo but the one with matching id to
be rendered.
import React, { Component } from 'react';
import { Alert,Image,StyleSheet, Text,Button, View } from 'react-native';
import TodoItem from './TodoItem'
import todosData from "./todosData"
export default class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
const updatedTodos = this.state.todos.map( todo => {
if(todo.id !== id) {
return todo
}
})
return {
todos:updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map( item =>
<TodoItem
key={item.id}
item={item}
handleChange = {this.handleChange}
/>
)
return (
<View style={styles.container}>
{todoItems}
</View>
);
}
}
This gives an error: ' TypeError:undefined is not an object (evaluating 'item.id')', giving at App.js:42:18
I'll also add the code referring to the TodoItem:
import React, { Component } from 'react';
import { Alert,Image,StyleSheet, Text,Button, View } from 'react-native';
import { CheckBox } from 'react-native-elements'
function TodoItem(props) {
return (
<View>
<CheckBox
checked={props.item.completed}
onPress={() => props.handleChange(props.item.id)}
/>
<Text>{props.item.text}</Text>
</View>
);
}
export default TodoItem
I don't understand why this won't work. It feels like I'm deleting the component while still using it (for it to give a undefined), but I don't see where. Since I'm simple updating a list of todos.
How can I do the thing I want?
PS: I seem unable to properly format the first segment of code. I apologize for that!
Try this:
handleChange(id) {
const { todos } = this.state
// filter out the deleted one
const filtered = todos.filter(x => x.id !== id)
this.setState({ todos: filtered })
}
We don't want to alter the state directly, but since .filter() creates a new array, without touching the given array, it is fine to use it. if it was another operation, you'd do something like this:
// create a copy
const newSomethings = [...this.state.somethings]
// do whatever with newSomethings
this.setState({ somethings: newSomethings })
import React, { Component } from 'react';
class newsList extends React.Component {
render(){
return(
<div>
{JSON.stringify(this.props.arr)}
</div>
)
}
}
export default newsList;
In the above code, arr is an object coming from another component. I can display the data using JSON.stringify(this.props.arr.result). But as soon as I change it with JSON.stringify(this.props.arr.result.id), I am getting an error says TypeError: this.props.arr.result is undefined. I cannot understand what I am doing wrong here?
I'm almost positive that, at some point in time, your this.props.arr is undefined, but then eventually gets assigned a value. Your initial render will receive a null or undefined, but if you try and go one step further into a key that doesn't exist, you will throw that error. You can use a boolean to control what gets initially rendered.
Instead of this line of code:
{JSON.stringify(this.props.arr)}
try this:
{this.props.arr ? JSON.stringify(this.props.arr) : null}
edit: is your issue with this.props.arr.result.id? If so, use this instead
{this.props.arr.result ? JSON.stringify(this.props.arr.result.id) : null}
Is this.props.arr Array?
If it is, the render function should be
render(){
var ids = this.props.arr.map(e=>(<div>e.result.id</div>))
return(
<div>
{ids}
</div>
)
}
Try out this code instead:
class NewsList extends React.Component {
constructor(props) {
super(props);
this.props = props;
}
render() {
return <div>{this.props.arr}</div>;
}
}
A React.Component's constructor always receives props as it's first parameter.