I'm building a sidebar menu skeleton using ReactJs and need to understand the way to call a function inside ReactJs render() function.
The code is below:
import React from 'react';
var menuData = require("./data/admin.menu.json");
class SidebarMenu extends React.Component {
constructor(props) {
super(props);
this.state = { expanded: true };
this.buildItem = this.buildItem.bind(this);
};
buildItem(title, ref, icon) {
return (
<div className={"item" + this.props.key}>
<a href={ref}>{title}<i className={"fa " + icon} /></a>
</div>
);
};
render() {
return (
<div>
{
menuData.forEach(function (item) {
this.buildItem(item.title, item.ref, item.icon);
if (item.hasOwnProperty("submenu")) {
item.submenu.forEach(function (subitem) {
this.buildItem(subitem.title, subitem.ref, subitem.icon);
});
}
})
}
</div>
);
};
}
export default SidebarMenu;
The given code shows the following error:
Uncaught TypeError: Cannot read property 'buildItem' of undefined
How to properly call a function that will render data inside the ReactJs function ?
The this referenced when you try to call this.buildItem() refers to the anonymous function's context, not your React component.
By using Arrow Functions instead of functions defined using the function keyword inside the render() method, you can use this to reference the React component and its methods as desired.
Alternatively, you can use (function () { ... }).bind(this) to achieve the same result. But this is more tedious and the use of arrow functions is preferred.
Below is one solution, using fat arrow, AKA arrow functions:
import React from 'react';
var menuData = require("./data/admin.menu.json");
class SidebarMenu extends React.Component {
constructor(props)
{
super(props);
this.state = { expanded: true };
this.buildItem = this.buildItem.bind(this);
};
buildItem(title, ref, icon) {
return (
<div className={"item" + this.props.key}>
<a href={ref}>{title}<i className={"fa " + item.icon}/></a>
</div>
);
};
render() {
return (
<div>
{
menuData.forEach(item => {
this.buildItem(item.title, item.ref, item.icon);
if (item.hasOwnProperty("submenu"))
{
item.submenu.forEach(subitem => {
this.buildItem(subitem.title, subitem.ref, subitem.icon);
});
}
})
}
</div>
);
};
}
export default SidebarMenu;
Another solution would be:
render() {
return (
<div>
{
menuData.forEach(function (item) {
this.buildItem(item.title, item.ref, item.icon);
if (item.hasOwnProperty("submenu"))
{
item.submenu.forEach(function (subitem) {
this.buildItem(subitem.title, subitem.ref, subitem.icon);
}.bind(this));
}
}.bind(this))
}
</div>
);
};
}
But, IMO, the best solution would be to refactor the code using a component:
import React, {PropTypes, Component} from 'react';
const menuData = require('./data/admin.menu.json');
function MenuItem({key, ref, title, icon, submenu}) {
return (
<div className={`item${key}`}>
<a href={ref}>{title}<i className={`fa ${icon}`}/></a>
if (submenu) {
submenu.map((subitem) => <MenuItem {...subitem} />)
}
</div>
);
}
MenuItem.propTypes = {
key: PropTypes.string,
title: PropTypes.string,
ref: PropTypes.string,
icon: PropTypes.string,
submenu: PropTypes.array,
};
class SidebarMenu extends Component {
constructor(props) {
super(props);
this.state = {
expanded: true,
};
}
render() {
return (
<div>
{
menuData.map((subitem) => <MenuItem {...subitem} />)
}
</div>
);
}
}
export default SidebarMenu;
You can add this line:
render() {
let that = this
return (
and then instead of this.buildItem use that.buildItem or you may need that.buildItem.bind(that)
Related
So I'm trying to create a list app using React. I don't have any errors or warnings, and my input bar is showing, but my textList won't render. In other words when ever I hit enter, the text info I put in the input bar won't create a list as expected.
I tried using keyCode === 13 and onKeyDown, but it's not having any effect on the app. What am I missng?
Here's my code:
filtered-input.js
import React, { Component } from "react";
import "./filtered-input.css";
import ItemList from "./item-list";
export class FilteredInput extends Component {
constructor(props) {
super(props);
this.state = {
textList: [],
};
}
handleChange(e) {
this.setState({ value: e.target.value });
}
handleAdd = (e) => {
if (e.keyCode === 13) {
let lists = this.props.state.textList;
lists.push(this.props.state.value);
this.setState({ textList: lists });
}
};
render() {
console.log("Here comes state -->");
console.log(this.state);
return (
<div>
<input
className="filtered-input-box"
type="text"
onKeyDown={this.props.handleAdd}
value={this.state.value}
onChange={this.props.handleChange}
/>
<ItemList item={this.state.textList}></ItemList>
</div>
);
}
}
ItemList.js
import React, { Component } from 'react';
export class ItemList extends Component {
render() {
const items = this.props.item.map((item) =>
<li>{item}</li>
);
return (
<ul>
{items}
</ul>
);
}
}
export default ItemList;
App.js
import React, { Component } from 'react';
import { FilteredInput } from './filtered-input.js';
export class App extends Component {
render() {
return (
<div>
<p>This page demonstrates a component that maintains its own state.</p>
<FilteredInput />
</div>
);
}
}
There were small syntactical mistakes here and there, apart from that everything looks perfect:
import React, { Component } from "react";
import "./style.css";
export default class App extends Component {
render() {
return (
<div>
<p>This page demonstrates a component that maintains its own state.</p>
<FilteredInput />
</div>
);
}
}
class FilteredInput extends Component {
constructor(props) {
super(props);
this.state = {
textList: [],
value: ""
};
}
handleChange = e => {
console.log(e.target.value);
this.setState({ value: e.target.value });
};
handleAdd = e => {
console.log("hi");
if (e.keyCode === 13) {
let lists = this.state.textList;
lists.push(this.state.value);
this.setState({ textList: lists });
}
};
render() {
console.log("Here comes state -->");
console.log(this.state);
return (
<div>
<input
className="filtered-input-box"
type="text"
onKeyDown={this.handleAdd}
value={this.state.value}
onChange={this.handleChange}
/>
<ItemList item={this.state.textList} />
</div>
);
}
}
class ItemList extends Component {
render() {
const items = this.props.item?.map(item => <li>{item}</li>);
return <ul>{items}</ul>;
}
}
Full working app : Stackblitz
I have a .jsx with a parent class and a child, in the parent i initialize the api and stock the json content in a state:
constructor(props) {
super(props);
this.state = {
all: '',
};
}
componentDidMount() {
this.loadApi();
}
loadApi(){
this.setState({ all: myApiGet('https://********') });
}
After that i need to get the "url" of the differents pics for show them on the site. But there is the problem, I get the api json when i load the page and i don't success to re-load the function.
componentWillReceiveProps(nextProps) {
this.apiGetProductPicture(nextProps.categorie);
}
apiGetProductPicture = (i) => () => {
// TODO do something with the data
var stock = this.props.all
.then(stock => this.setState({ pictures: stock.content.categories[i].background }))
.catch(error => console.log('home2', error));
}
I try a lot of possibility and check the net but the solution doesn't work for me (or i just doesn't understand them ...)
Thanks for your time :/
Full component:
class ProductItem extends React.Component {
constructor(props) {
super(props);
this.state = {
pictures: '',
name: '',
price: '',
json: '',
};
//this.apiGetProductPicture = this.apiGetProductPicture.bind(this);
}
componentWillReceiveProps(nextProps) {
this.apiGetProductPicture(nextProps.categorie);
}
apiGetProductPicture = (i) => () => {
// TODO do something with the data
var stock = this.props.all
.then(stock => this.setState({ pictures: stock.content.categories[i].background }))
.catch(error => console.log('home2', error));
}
render() {
return (
......
)
}
}
Error message:
The above error occurred in the component:
in ProductItem (created by Home2)
in div (created by Home2)
in div (created by Home2)
in div (created by Home2)
in div (created by Home2)
in main (created by Home2)
in Home2
Consider adding an error boundary to your tree to customize error handling behavior.
You can learn more about error boundaries at https:// fb.me/react-error-boundaries.
react-dom.development.js:9312:5
ReferenceError: props is not defined
Ok i think i see some changes to be made
in your parent component your setting this.state.all to be a promise (the promise returned from your api call)
let's change that to be the actual json from your api call
Parent component:
constructor(props) {
super(props);
this.state = {
all: '',
};
this.loadApi = this.loadApi.bind(this);
}
componentDidMount() {
this.loadApi();
}
loadApi() {
myApiGet('https://********')
.then(all => this.setState({ all }));
}
Child Component:
class ProductItem extends React.Component {
constructor(props) {
super(props);
this.state = {
pictures: '',
name: '',
price: '',
json: '',
};
this.apiGetProductPicture = this.apiGetProductPicture.bind(this);
}
ComponetDidMount() {
apiGetProductPicture(this.props.categorie);
}
componentWillReceiveProps(nextProps) {
if (nextProps.categorie !== this.props.categorie)
{
this.apiGetProductPicture(nextProps.categorie);
}
}
apiGetProductPicture(categorie) {
// TODO do something with the data
if (!this.props.all) return;
var categories = (((this.props.all || {}).stock || {}).content || {}).categories;
if (categories.indexOf(categorie) > -1)
{
this.setState({ pictures: categories[categorie].background }));
}
}
render() {
return (
......
);
}
}
Thanks for your time :/
no worries :)
i se you posted "Lost in the javascriptception"
this and other questions have provided me with enough info to solve your problem, sorry the stackoverflow community was so mean to you, but not all of us are like that.
I would recommend in the future you post more info on your questions, like full code (except sensible stuff), not just parts, the codesanbox was the thing that let me test code and see where the problem was.
Also i f*** up on some of the previous answer, but to be fair i had very limited info to go along with, and most people answering won't test the code for tipos or stuff
version One
import React from "react";
import { render } from "react-dom";
import Hello from "./Hello";
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
class ProductItem extends React.Component {
constructor(props) {
super(props);
this.state = {
pictures: '',
name: '',
price: '',
json: '',
};
this.apiGetProductPicture = this.apiGetProductPicture.bind(this);
}
componentDidMount() {
this.apiGetProductPicture(this.props.categorie);
}
componentWillReceiveProps(nextProps) {
this.apiGetProductPicture(nextProps.categorie);
}
apiGetProductPicture(categorie) {
// TODO do something with the data
var categories = this.props.send;
categorie = parseInt(categorie, 10);
if (categorie < categories.length) {
console.log(categories[categorie].background);
this.setState({ pictures: categories[categorie].background });
}
}
render() {
return (
<div>
<p>{this.props.name}</p>
<img src={this.state.pictures} />
</div>
);
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
all: "",
categories: []
};
this.loadAPI = this.loadAPI.bind(this);
}
componentDidMount() {
this.loadAPI();
}
loadAPI() {
var test = fetch("https:*******")
.then(test => test.json())
.then(testJson => {
// alert(testJson.content.categories[0].description)
var obs = testJson.content.categories.slice();
// alert(testJson);
this.setState({ categories: obs });
});
}
render() {
return (
<div style={styles}>
<Hello name="CodeSandbox" />
<h1>Products</h1>
{this.state.categories.map( (value, i) => {
return <ProductItem
key={value.uid}
send={this.state.categories}
name={value.description}
categorie={i} />
})}
<h2>Start editing to see some magic happen {"\u2728"}</h2>
</div>
);
}
}
render(<App />, document.getElementById("root"));
My recommended Version
import React from "react";
import { render } from "react-dom";
import Hello from "./Hello";
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
class ProductItem extends React.Component {
render() {
return (
<div>
<p>{this.props.name}</p>
<img src={this.props.picture} />
</div>
);
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
all: "",
categories: []
};
this.loadAPI = this.loadAPI.bind(this);
}
componentDidMount() {
this.loadAPI();
}
loadAPI() {
var test = fetch("https:*****")
.then(test => test.json())
.then(testJson => {
// alert(testJson.content.categories[0].description)
var obs = testJson.content.categories.slice();
// alert(testJson);
this.setState({ categories: obs });
});
}
render() {
return (
<div style={styles}>
<Hello name="CodeSandbox" />
<h1>Products</h1>
{this.state.categories.map( (value, i) => {
return <ProductItem
key={value.uid}
picture={value.background}
name={value.description}
categorie={i} />
})}
<h2>Start editing to see some magic happen {"\u2728"}</h2>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Hope this helps you out, don't be so hard on yourself, you know practice makes perfect, also would recommend you follow the react tutorial, to see what react is about, i can seam super hard and weird because it maybe a completely different programming model (it was for me), but when it click it's really cool
I want to be able to delete an specific object from an array in my store. I made a delete item function that works and deletes the objects, however I haven't been able to figure out how to make this function work when I use a button that is rendered with each object with map. This is my component:
import React, { Component } from 'react';
import {addCart} from './Shop';
import { removeCart } from '../../actions';
import { connect } from 'react-redux';
export class Cart extends Component {
constructor(props) {
super(props);
this.state = {items: this.props.cart,cart: [],total: 0};
}
handleClick(item) {
this.props.onCartRemove(item);
}
...
render() {
return(
<div className= "Webcart" id="Webcart">
<div>
{this.state.items.map((item, index) => {
return <li className='cartItems' key={'cartItems_'+index}>
<h4>{item.item}</h4>
<p>Size: {item.size}</p>
<p>Price: {item.price}</p>
<button onClick={this.handleClick}>Remove</button>
</li>
})}
</div>
<div>Total: ${this.countTotal()}</div>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
onCartAdd: (cart) => {
dispatch(addCart(cart));
},
onCartRemove: (item) => {
dispatch(removeCart(item));
},
}
}
function mapStateToProps(state) {
return { cart: state.cart };
}
export default connect(mapStateToProps, mapDispatchToProps)(Cart);
With the function handleClick I get Uncaught TypeError: Cannot read property 'props' of null. If I try something like
deleteItem() {
return this.state.items.reduce((acc, item) => {
return this.props.onCartRemove(item);
})
}
... the code deletes all items in the loop without any errors. How can I can use the button to remove that particular item?
Are you really getting the item for it to be removed?
try this on your button inside your map:
<button onClick={() => this.handleClick(item)}>Remove</button>
You need to bind your handler.
constructor(props) {
super(props);
this.state = {items: this.props.cart,cart: [],total: 0};
this.handleClick = this.handleClick.bind(this);
}
https://facebook.github.io/react/docs/handling-events.html
I have the following class
class MatchBox extends React.Component {
constructor(props) {
super(props);
this.countdownHandler = null;
this.showBlocker = true;
this.start = this.start.bind(this);
}
start() {
...
}
render() {
...
return (
<div style={ styles.mainContainer } className="fluid-container">
...
</div>
);
}
};
function mapStateToProps(state) {
...
}
function matchDispatchToProps(dispatch) {
...
}
export default withRouter(connect(mapStateToProps, matchDispatchToProps, null, { withRef: true })(MatchBox));
which is used in this class
class GameBox extends React.Component {
constructor(props) {
super(props);
...
}
render() {
var mainElement = null;
switch(this.props.mainElement.element) {
case 'SEARCHING': mainElement = <SearchingBox gameType={ this.props.gameType }/>; break;
case 'MATCH': mainElement = <MatchBox ref='matchBox'/>; break;
default: mainElement = <SearchingBox/>;
}
return (
<div style={ styles.mainContainer } className="fluid-container">
{ mainElement }
</div>
);
}
};
function mapStateToProps(state) {
...
}
function matchDispatchToProps(dispatch) {
...
}
export default withRouter(connect(mapStateToProps, matchDispatchToProps, null, { withRef: true })(GameBox));
And I can't get the ref of the object MatchBox. I tried with this.refs.matchBox and is null, also tried getting directly from ref(ref={(r) => { // r is null } }) and I don't know what to try anymore.
I'm using react-router-dom 4 and I don't know if function withRouter affect the outcome component.
It's not pretty, but I think this is the solution. withRouter exposes the child ref via a wrappedComponentRef callback, which gets us to the connect hoc. That exposes its child ref via getWrappedInstance if you pass the withRef attribute as you did. So you just have to combine both of those.
class GameBox extends React.Component {
matchboxRefCallback = (connectHOC) => {
this.matchboxRef = connectHOC ? connectHOC.getWrappedInstance() : null;
}
render() {
return <MatchBox wrappedComponentRef={this.matchboxRefCallback}/>;
}
}
Much more cleaner solution would be to create a HOC. which will forward the ref to actual component
const matchBoxHOC = (WrappedComponent) => {
class MatchBoxHOC extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return <WrappedComponent {...rest} ref={forwardRef} />;
}
}
const WithRouterMatchBoxHOC = withRouter(MatchBoxHOC, { withRef: true });
return React.forwardRef((props, ref) => {
return <WithRouterMatchBoxHOC {...props} forwardRef={ref} />;
});
}
Call is like
export default matchBoxHOC(connect(mapStateToProps, matchDispatchToProps, null, { withRef: true })(MatchBox));
I have a very simple application where I am trying to update the state of a parent component from a child component as follows:
import React from '../../../../../../../dependencies/node_modules/react';
import ReactDOM from '../../../../../../../dependencies/node_modules/react-dom';
class CalendarMain extends React.Component {
constructor() {
super();
}
handleClick() {
this.props.handleStateClick("State Changed");
}
render() {
return (
<div>
<div className="calendar">
{this.props.checkIn}
<button onClick={ this.handleClick.bind(this) }>Click Me</button>
</div>
</div>
)
}
}
class CalendarApp extends React.Component {
constructor() {
super();
this.state = {
checkIn: "Check-in",
checkOut: "Check-out",
dateSelected: false
};
}
handleStateClick( newState ) {
this.setState({
checkIn: newState
});
}
render() {
return (
<div>
<CalendarMain
checkIn = { this.state.checkIn }
handleStateClick = { this.handleStateClick.bind(this) }
/>
</div>
);
}
}
The error I am receiving is this.setState is not a function and I can't work out why. Any help would be much appreciated!
this is not auto-bound in ES6 style syntax.
Either:
Bind in constructor like so: this.func = this.func.bind(this)
Use arrow function syntax for the function in question like so: func = () => {};
More here: https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding
Use () => lambda to provide lexical scoping and bind correct value of this within the method handleStateClick():
handleStateClick = () => {
this.setState({
checkIn: newState
});
}