React: Render a child element from an array - javascript

I'm trying to pull a children element from a collection but I'm not able to, it doesn't seem to find the properties, I know I'm close to get what I want but I'm stuck on this. I'm using meteor 1.6 with React 16
this is my code:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Col, Thumbnail, Button, Modal, Row, Form, FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
import { Medicamento } from '../api/medicamento.js';
import PropTypes from 'prop-types';
export default class ListMeds extends Component {
constructor(props){
super(props);
}
render(){
function RenderLotes(){
const lotesArray = this.props.meds.Lotes.map((lista) =>
// The console indicates Cannot read property 'meds' of undefined
<div key={lista.codigoLote}>
<h3>{lista.codigoLote}</h3>
</div>
);
return(
<div>{lotesArray}</div>
);
}//function RenderLotes ends here
return(
<div>
<Col md={4}>
<div align="center"><strong>{this.props.meds.name}</strong></div>
<div align="center"><em>{this.props.meds.principio}</em></div>
</Col>
//the two lines above work fine, but crashes when I try to render the function
<Col md={4}>
<div><RenderLotes/></div>
</Col>
);
}
}
ListMeds.propTypes = {
meds: PropTypes.object.isRequired,
};

The this in RenderLotes is not the same this as in the outer render() function.. But you probably don't even need RenderLotes like that, just make the array like Andrew Kim suggested.. Or, if you really wanted to have it, something like this should work:
function RenderLotes(props) {
const lotesArray = props.meds.Lotes.map((lista) =>
<div key={lista.codigoLote}>
<h3>{lista.codigoLote}</h3>
</div>
);
return(
<div>{lotesArray}</div>
);
}
// later:
<div><RenderLotes meds={this.props.meds} /></div>

As the console error says, this.props is undefined within your RenderLotes() function, suggesting that this is a scoping issue; the this in your RenderLotes() does not refer to the React Component, but to something else.
One option would be to define the RenderLotes() function outside of the render() function, declaring both functions at the same level :
RenderLotes() {...}
render() {...}
Then, in your JSX, call it this way:
<div>{this.RenderLotes()}</div>

Put your props in state when in your constructor
export default class ListMeds extends Component {
constructor(props){
super(props);
this.state = {
meds = props.meds
}
}
render(){
return (
{this.state.meds.map(lista => {
return (
<div key={lista.codigoLote}>
<h3>{lista.codigoLote}</h3>
</div>
)
})}
)
}
And make sure when you render that you are actually passing a prop called meds
ReactDOM.render(<ListMeds meds={meds} />, document.getElementById('root'))

I'm not entirely sure what your question is, but your code should look probably more like this:
render(){
let lotesArray = this.props.meds.Lotes.map((lista) => {
return(
<div key={lista.codigoLote}>
<h3>{lista.codigoLote}</h3>
</div>
)
})
return (
<div>
<Col md={4}>
<div align="center"><strong>{this.props.meds.name}</strong></div>
<div align="center"><em>{this.props.meds.principio}</em></div>
</Col>
<Col md={4}>
<div>{lotesArray}</div>
</Col>
</div>
)
}

First of all if console says that meds property is undefined it means that it doesn't exist hence you cannot iterate over it. Second thing is how you invoke RenderLotes function. <div><RenderLotes/></div> this won't work. Instead you need to change it to <div>{this.RenderLotes()}</div>. And move RenderLotes function declaration out of render method. Also you don't close div tag in render function. Change the code to something like that:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { Col, Thumbnail, Button, Modal, Row, Form, FormGroup, FormControl, ControlLabel } from 'react-bootstrap'
import { Medicamento } from '../api/medicamento.js'
import PropTypes from 'prop-types'
export default class ListMeds extends Component {
renderLotes = () => this.props.meds.Lotes.map(lista =>
(<div key={lista.codigoLote}>
<h3>{lista.codigoLote}</h3>
</div>))
render() {
return (
<div>
<Col md={4}>
<div align="center"><strong>{this.props.meds.name}</strong></div>
<div align="center"><em>{this.props.meds.principio}</em></div>
</Col>
<Col md={4}>
<div>{!!this.props.meds && this.renderLotes()}</div>
</Col>
</div>
)
}
}
ListMeds.propTypes = {
meds: PropTypes.object.isRequired,
}

Related

Pass a function from a Functional Component to a Class based Component

Parent Component is like below
import React, { useState } from "react";
import EntityDefinition from "./EntityDefinition";
export default function EntitySelection(props) {
const testFun = () => {
console.log("Function activated");
};
return (
<>
<div>
<EntityDefinition
testFun={testFun}
/>{/**Calling a Class based Component*/}
</div>
</>
);
}
Class based Component (Child)
import React from "react";
import { ComboBox, DropdownOption, Button } from "react-widgets";
import axios from "axios";
export default class EntityDefinition extends React.Component {
render() {
return (
<React.Fragment>
<div>
{" "}
<Button onClick={this.testFun}>Close</Button>{" "} {/*/Calling the function passed*/}
</div>
</React.Fragment>
);
}
}
but when i clicked the button the testFun function is not being called.
i tried something like onClick={this.state.testFun} , onClick={testFun}
but nothing is happening.
can someone point what am i doing wrong here.
testFun is a prop. So use this.props.testFun
onClick={this.props.testFun}
testFun is a prop, and you are using the ES6 class component, and it receives the props as a props object, so you can try accessing it as below
onClick={this.props.testFun}
You need to refer to props passed to a class based components using this.props from inside the class based components:
Docs
In your case, you should change the onClick listener to this:
<Button onClick={this.props.testFun}>

Unable to call parent component function from child component in React

I am new to react(started this week). I have a parent and child components and I want to call a parent method in the child component. I have searched through stackoverflow and my code is same as all the solutions I got.
I have a parent component ProductDisplay which displays a list of products:
import React, { Component } from 'react';
import data from '../data'
import Product from '../Product/product.component'
class ProductDisplay extends Component {
constructor(props) {
super(props)
this.state = {
pdts: data,
}
}
addToCart = () => {
console.log('add to cart');
}
render() {
return (
this.state.pdts.map(product => (
<Product
key={product.id}
product={product}
addToCart={this.addToCart}
/>
))
);
}
}
export default ProductDisplay;
and the child component is Product which renders each product
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import "./product.css";
class Product extends Component {
constructor(props) {
super(props);
}
handleClick = () => {
this.props.addToCart();
console.log('click');
}
render() {
const product = this.props.product;
console.log(this.props.addToCart);
return (
<div className="product">
<img className="image" src={product.imgPath} alt={product.name} />
<p className="name">{product.name}</p>
<p className="price">Price: ₹{product.price}</p>
<p className="category">Category: {product.category}</p>
<button className="add">Add To Cart <i className="fa fa-cart-plus"
onClick={this.handleClick}></i></button>
</div>
);
}
}
export default withRouter(Product);
I want to call a function addToCart of ProductDisplay from Product on click of the button but it is not working. The handleClick function of the child omponent itself is not getting called. Hence the parent function which is being called from handleClick is also not getting called.
I’m also not sure if what I am doing would work in binding the method to all the buttons. Please help
You've put the onClick listener on the <i> tag, not on the actual button, which is why its not triggering anything when you click the button.
Try this instead:
<button
className="add"
onClick={this.handleClick}
>
Add To Cart <i className="fa fa-cart-plus"></i>
</button>
You need to bind the addCart method with "this" of the class. And as Chistopher's answer your onClick is on i and not on button.
Either while passing.
<Product
key={product.id}
product={product}
addToCart={this.addToCart}
/>
Or in state
this.addToCart = this.addToCart.bind(this);

React this is undefined with already bound method [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 3 years ago.
I have a react application and I'm trying to build a Navbar component using data from a javascript file.
My NavbarData.js file looks like the following:
const NavbarData = [
{
id: 1,
text: "Typography"
},
{
id: 2,
text: "Buttons"
},
{
id: 3,
text: "icons"
}
]
export default NavbarData
I'm using .map() to iterate over this data and create NavbarItem components inside my App.js file.
// Build navmenu items
const navbarItems = this.state.navbarData.map(function(item){
return <NavbarItem key={item.id} text={item.text} id={item.id}></NavbarItem>
});
And here is my NavbarItem.js file
import React, { Component } from 'react';
class NavbarItem extends Component{
render(){
return(
<>
<li key={this.props.id} id={this.props.id}>{this.props.text}</li>
</>
)
}
}
export default NavbarItem
All of this gives me something that looks like this. Which is great.
But I want to add a click listener to each of these. As this is a single page application, I would like to render either a typography, buttons, or icons component. To do this, I need a function that will update the state of the parent component which in my case is just App.js
So I put the following function inside App.js
//This function changes the state so that different components can render
navClick(id) {
console.log('changed', id);
}
And I made sure to bind it in my constructor of App.js
this.navClick = this.navClick.bind(this);
My entire App.js file now looks like this
//React stuff
import React, { Component } from 'react';
//Bootstrap stuff
import { Container, Row, Col } from 'reactstrap';
//Layout
import NavbarItem from './layout/NavbarItem'
import NavbarData from './layout/NavbarData'
//Components
import Typography from './components/Typography/Typography'
import Buttons from './components/Buttons/Buttons'
//Styles
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
class App extends Component {
constructor(){
super();
// State determines what component is active and loads navbar data
this.state = {
navbarData: NavbarData,
typography: true,
buttons: false,
icons: false
}
this.navClick = this.navClick.bind(this);
}
//This function changes the state so that different components can render
navClick(id) {
console.log('changed', id);
}
render() {
// Build navmenu items
const navbarItems = this.state.navbarData.map(function(item){
return <NavbarItem key={item.id} text={item.text} id={item.id}></NavbarItem>
});
// Determine what component to display in main area using state
let elementToDisplay;
if(this.state.typography){
elementToDisplay = <Typography></Typography>
}
else if(this.state.buttons){
elementToDisplay = <Buttons></Buttons>
}
////////////////////////////////////////////////////
return (
<Container fluid={true}>
<Row>
<Col>Header</Col>
</Row>
<Row>
<Col xs="12" sm="12" md="1" lg="1" xl="1">
<ul>
{navbarItems}
</ul>
</Col>
<Col xs="12" sm="12" md="11" lg="11" xl="11">
{elementToDisplay}
</Col>
</Row>
<Row>
<Col>Footer</Col>
</Row>
</Container>
);
}
}
export default App;
The problem comes when I try to attach the navClick function to the mapped NavbarItem like so.
// Build navmenu items
const navbarItems = this.state.navbarData.map(function(item){
return <NavbarItem navigationWhenClicked={this.navClick} key={item.id} text={item.text} id={item.id}></NavbarItem>
});
The error I receive is the following:
TypeError: this is undefined
When googleing this issue, this is the top post.
React: "this" is undefined inside a component function
But that's not my problem as I am making sure to bind my function.
I really have no idea what I'm doing wrong here. Any help would be appreciated.
The function you pass to .map also has its own this binding. The simplest solution is to pass this as second argument to .map:
const navbarItems = this.state.navbarData.map(function(item) {
...
}, this);
this inside the function will be set to whatever you pass as second argument, which in this case is the component instance.
Alternatively you can use an arrow function instead of a function expression, since this is resolved lexically (i.e. like any other variabe) inside arrow functions:
const navbarItems = this.state.navbarData.map(
item => <NavbarItem navigationWhenClicked={this.navClick} key={item.id} text={item.text} id={item.id} />
});
See also: How to access the correct `this` inside a callback?

Use array mapping to create react elements in another component

Currently I am getting data from an API and storing those results into an array. The problem, when I do the array mapping to the child component, it never executes because the array is empty to begin with. How can I execute the array mapping when the array has data in it. I tried inline conditional such as doing {array.length > 0 ? //do array mapping}. I also tried making the array both global and an array that is a state of the parent component.
//React Router
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import Main from '../components/Main';
export default () => {
return <Route path="/" component={Main}/>
};
//Main component
import React, { PropTypes, Component } from 'react';
// import bgImage from './ignasi_pattern_s.png';
import Child1 from './Children/Child1';
import axios from 'axios';
const QUERY_URL = "https://api.nytimes.com/svc/search/v2/articlesearch.json?api-key=";
//****Tried global array declaration and as a state property but both do not work.
// var articles = [];
class Main extends Component {
constructor(props){
super(props);
this.state = {
search: "",
articles: []
}
this.getTopic = this.getTopic.bind(this);
this.executeSearch = this.executeSearch.bind(this);
}
getTopic(event) {
this.setState({
search: event.target.value
});
}
executeSearch(event) {
event.preventDefault();
axios.get(QUERY_URL + "&q=" + this.state.search).then((response) => {
for(var i = 0; i < 5; i++){
this.state.articles.push({
headline: response.data.response.docs[i].lead_paragraph
})
}
});
}
render() {
return (
<div className="Main" style={{backgroundImage: `url(${"http://aiburn.com/files/articles/creating_professional_business_backgrounds/06.gif"})`}}>
<div className="page-header">
<h1>{getNiceName(this.props.routes)}{' '}
<small>page</small>
</h1>
<h1>Search For Something</h1>
<form onSubmit={this.executeSearch}>
<input type="text" value={this.state.search} onChange={this.getTopic}/>
<button type="submit" className="btn btn-default">Search</button>
</form>
</div>
<div className="container Main-content">
//Trouble here mapping the array to the child component.
//This never executes because the array is empty to begin with.
{this.state.articles.length > 0 ?
{this.state.articles.map((item, index) => {
return <Child1
key={index}
headline={item.headline}
/>;
})}
}
</div>
</div>
);
}
}
Main.propTypes = {
children: PropTypes.node,
routes: PropTypes.array
};
export default Main;
//Child Component
import React, { Component } from 'react';
class Child1 extends Component {
constructor(props) {
super(props);
}
render() {
return <div>
<div className="container">
<h1>{this.props.headline}<span><button type="submit" className="btn btn-default">Save</button></span></h1>
</div>
</div>;
}
}
export default Child1;
You should not need to check if this.state.articles.length > 0. You can just proceed to call this.state.articles.map immediately. map, when given an empty array, just returns the empty array - it does nothing with it.
That is, [].map(x => whatever(x)) === [].
Therefore, even if this.state.articles.length <= 0, you will just end up rendering the empty collection (that is, nothing at all).
I am not sure if this might be an issue or not, but seems like there is syntax error with inline conditional. The code should be like this.
<div className="container Main-content">
{this.state.articles.length > 0 ?
this.state.articles.map((item, index) => {
return <Child1
key={index}
headline={item.headline}
/>;
}) : null
}
</div>
Also, as #evocatus mentioned there is no need to put check on length as map already handles this.
If you want to render a different component when the array is empty, you can put that element instead of null.

receiving null is not an object (evaluating ' this.state.search')

I am receiving null is not an object (evaluating 'this.state.search') I am new to react-native so not 100% sure whats going on. thanks for any help.
This is my basic search bar:
use strict';
import React, { Component } from 'react';
import { StyleSheet, Text, View, TextInput, Button } from 'react-native';
import renderIf from '../common/renderIf';
import { Container, Header, Title, Content, Icon, CardItem, Card, Input, InputGroup} from 'native-base';
export default class SearchBar extends Component {
render() {
return (
<Card>
<CardItem searchBar rounded>
<InputGroup>
<Icon name="ios-search" />
<Input placeholder="Search" value={this.state.search} onChangeText={(text) => this.setState({search:text})} onSubmitEditing={()=>this.search()}/>
</InputGroup>
</CardItem>
</Card>
);
}
}
This is is to take the inputed text from the search bar and display the results:
'use strict';
import React, { Component } from 'react';
import { StyleSheet, Text, View, TextInput, Button } from 'react-native';
import renderIf from '../common/renderIf';
import { Container, Content, Icon, CardItem, Card, Thumbnail, Title, List, ListItem} from 'native-base';
import { Col, Row, Grid } from "react-native-easy-grid";
export default class AddMovieResults extends Component {
search() {
// Set loading to true when the search starts to display a Spinner
this.setState({
loading: true
});
var that = this;
return fetch('http://www.omdbapi.com/?s=' +this.state.search)
.then((response) => response.json())
.then((responseJson) => {
// Store the results in the state variable results and set loading to
// false to remove the spinner and display the list of repositories
that.setState({
results: responseJson,
loading: false
});
return responseJson.Search;
})
.catch((error) => {
that.setState({
loading: false
});
console.error(error);
});
}
render() {
return (
<Card scrollEnabled={true}>
<CardItem button >
<List dataArray={this.state.results.items} renderRow={(item) =>
<ListItem button >
<Row>
<Col size={1}>
<Thumbnail square style={{ height: 90, width:60, bottom:6,justifyContent: 'center',}} source={{uri: item.poster}} />
</Col>
<Col size={3}>
<Row size={3}>
<Text style={{ fontSize: 25, color: '#DD5044',justifyContent: 'center',}}>{item.title}</Text>
</Row>
<Row size={1}>
<Text style={{ fontSize: 15, color: '#DD5044',}}>{item._year_data}</Text>
</Row>
</Col>
</Row>
</ListItem>
} />
</CardItem>
</Card>
);
}
}
This is my index file which displays the above files on one page:
'use strict';
import React, { Component } from 'react';
import { StyleSheet, Text, View, TextInput, Button } from 'react-native';
import renderIf from '../common/renderIf';
import { Container, Header, Title, Content} from 'native-base';
import { Col, Row, Grid } from "react-native-easy-grid";
import AddMovieResults from './AddMovieResults';
import SearchBar from './SearchBar';
export default class AddNewMovie extends Component {
render() {
return (
<Container scrollEnabled={false}>
<Header>
<Title> Add Movie</Title>
</Header>
<Content>
<Grid>
<Col>
{/* import search bar */}
<SearchBar/>
{/*import search results*/}
<AddMovieResults/>
</Col>
</Grid>
</Content>
</Container>
);
}
}
State is not global, it is local to each component so you will need to pass it to the object as a prop.
The problem here however is that as you are defining the search bar and add movie results you will need to find a way to pass the state back from SearchBar.
To accomplish this you can pass a reference function to update the state of AddNewMovie:
Add the following function to your addNewMovie class:
updateAddNewMovieState = (newData) => {
this.setState({search:newData})
}
Next pass it into the search bar class:
<SearchBar
updateState = {this.updateAddNewMovieState}
currentState = {this.state.search}
/>
Now use this.props.currentState to access the search state and this.props.updateState(newState) to modify the state in AddNewMovie from the search bar class.
Finally, pass the variable through to AddMovieResults:
<AddMovieResults
search={this.state.search}
/>
You can then access the variable in AddMovieResults via this.props.search.
While this method is relatively straight forward it quickly becomes convoluted if you are passing many variables around, for this purpose I recommend https://github.com/reactjs/react-redux which allows you to store variables much cleaner through action functions and reduction states.
I would also recommend defining your state variables in each component constructor to make it much clearer where they are being defined:
constructor(props) {
super(props);
this.state = {
something: "Something"
};
}
you need to bind your function in the constructor to be able to access this outside of inherited React class functions like render, constructor, etc...:
export default class AddMovieResults extends Component {
constructor(props){
super(props);
this.search = this.search.bind(this);
}
search() {
// Set loading to true when the search starts to display a Spinner
this.setState({
loading: true
});
}
...
...
}

Categories