Render data from async method is undefined - reactjs

I'm trying to render data from my MongoDB document but I can't figure out why the state is undefined when it's an async function and I'm awaiting it.
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
offers: null
};
this.getOffers = this.getOffers.bind(this);
this.renderOffer = this.renderOffer.bind(this);
}
componentDidMount() {
if(!this.state.offers) {
this.getOffers();
}
}
async getOffers() {
let res = await offerService.getAll();
this.setState({ offers: res });
}
renderOffer(product) {
return (
<li key={product._id} className="list__item product">
<h3 className="product__name">{product.title}</h3>
</li>
);
};
render() {
return (
<div className="App">
<Nav />
<ul className="list">
{(this.state.offers && this.state.offers.length > 0) ? (
this.state.offers.map(offer => this.renderOffer(this.state.offer))
) : (
<p>Loading...</p>
)}
</ul>
</div>
);
}
}
Whenever I load the page, it returns TypeError: Cannot read property '_id' of undefined, despite the fact that if I log the output of res in getOffers() it shows the data. Why is this?

The problem lies in your map call:
this.state.offers.map(offer => this.renderOffer(this.state.offer))
You're attempting to use the undefined variable this.state.offer rather than your offer map variable. What you actually want is:
this.state.offers.map(offer => this.renderOffer(offer))

Related

Create New Line after each Array Element

I'm very new to react and trying to teach it to myself. Trying to make an API call and loop through the records. I've been successful so far, however, I can't seem to figure out how to create a new line after each element that gets displayed. I'm sure it's something simple that I'm just missing. Can someone advise here?
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
isLoaded: false,
}
}
async componentDidMount() {
const url = "https://api.randomuser.me/?results=10";
const response = await fetch(url);
const data = await response.json();
this.setState({ person: data, loading: false, len: data.results.length });
}
render() {
let items = [];
for (let i = 0; i < this.state.len; i++) {
const item = this.state.person.results[i].name.title;
items.push(item);
}
return (
<div>
{this.state.loading || !this.state.person ? (
<div>loading...</div>
) : (
<div>
{items}
</div>
)}
</div>
);
}
}
You can use .map() function to loop over the array display it in within the tags.
return (
<div>
{this.state.loading || !this.state.person ? (
<div>loading...</div>
) : (
<div>
{
items.map((item) => (
<>
<span>{item}</span>
<hr />
</>
)
}
</div>
)}
</div>
);

parse string from an api to html

I have api call that sets a collection of 10 objects into a state array. Below is an example of that code:
class Example extends Component {
constructor(props) {
super(props);
this.state = {
quiz_data: [],
quiz_answers:[]
}
}
componentDidMount() {
Axios.get('api call here')
.then(response => {
this.setState({ 'quiz_data': response.data });
})
}
Then, I map over that state array like so:
this.quizData = this.state.quiz_data.map((item, id) => {
return (
<div key={id}>
<h3 className='quiz-question'>{item.Title}</h3>
<p>{item.Question}</p>
</div>
)
})
My question/problem is, that the item.question is returned as a string in the array, and comes out that way.
So i end up with example text with code inside instead of just example text.
How do i get it to return as html instead of a string?
why dont you try setting the html dangerously read here
this.quizData = this.state.quiz_data.map((item, id) => {
return (
<div key={id}>
<h3 className='quiz-question'>{item.Title}</h3>
<div dangerouslySetInnerHTML={{__html:item.Question}} />
</div>
)
})

Updated State not being retrieved by method

In this file, I'm pulling in a bunch of data from an API and assigning it to the array of "baseData". In another file, I have an onkeyup event calling the generateResults method. But then generateResults method is only getting the original, blank state of the array. I'm new to react so any help is appreciated. Thanks!
import React from 'react';
import axios from 'axios';
export class LINKS extends React.Component{
constructor(props){
super(props);
this.state={
baseData: []
}}
getBaseData(){
axios.get("http://localhost:3000/api/links")
.then(response => {
this.setState({baseData: response.data});
}).catch((error) => {
console.error(error);
});
}
componentDidMount(){
this.getBaseData();
}
generateResults(){
var linkInfo = this.state.baseData
var searchBar = document.getElementById('searchBar')
console.log(linkInfo)
for(var i = 0; i < linkInfo.length; i ++){
}
}
render(){
var linkInfo = this.state.baseData
// console.log(linkInfo)
if(linkInfo.length === 0){
return(
<div><h1> Loading... </h1></div>)
} else {
return(
<div>{linkInfo.map((info, i) =>
<div>
<u>{info['client']}</u>
{info.links.map((link, i) =>
<div> {link.linkTitle}
<br/> {link.url} </div>) }
<hr/></div>
)}</div>
)
}
}
}
Method in the other class calling the generateResults method.
handleSearch(){
let links = new LINKS
links.generateResults()
}
Because you should return any HTML into your arrow function map loop with double Parentheses like this :
<ul>
{
this.props.items.map((item) => (
<li>{item}</li>
))
}
</ul>
I've edited your code, try this :
<div>
{
linkInfo.map((info, i) => (
<div>
<u>{info['client']}</u>
{
info.links.map((link, i) => (
<div> {link.linkTitle}
<br/> {link.url} </div>
))
}
<hr/></div>
))
}
</div>

Delete record from todo list in ReactJS giving error

Am learning ReactJS and building my todo application.
However am facing an issue when I try to delete a task.
I have two files TodoList.js and TodoItems.js
TodoList.js
import React, {Component} from 'react';
import TodoItems from './TodoItems';
class TodoList extends Component {
//Function to handle adding tasks
addTask(event) {
//Get task Value
let task = this.refs.name.value;
//Newitem Object
if (task !== "") {
let newItem = {
text: task,
key: Date.now()
}
this.setState({
items: this.state.items.concat(newItem)
});
this.refs.name.value = ""; //Blank out the task input box
}
}
deleteItem(key) {
var filteredItems = this.state.items.filter(function (item) {
return (item.key !== key);
});
this.setState({
items: filteredItems
});
}
constructor(props) {
super(props);
this.state = {
items: []
};
this.addTask = this.addTask.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
render() {
return (
<div className="todoListMain">
<div className="header">
<form>
<input placeholder="Enter Task" id="name" ref="name"></input>
<button type="button" onClick={this.addTask}>Add Task</button>
</form>
</div>
<div className="list">
<TodoItems entries={this.state.items} delete={this.deleteItem} />
</div>
</div>
);
}
}
export default TodoList;
TodoItems.js has following code
import React, {Component} from 'react';
class TodoItems extends Component {
constructor(props) {
super(props);
this.state = {};
}
delete(key) {
this.props.delete(key);
}
listTasks(item) {
return <li key={item.key} onClick={() => this.delete(item.key)}>{item.text}</li>
}
render() {
let entries = this.props.entries;
let listItems = entries.map(this.listTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
}
export default TodoItems;
I am getting an error on deleting task when clicked on it.
and I am getting error as here
I guess it means function delete is not defined but it has been defined still am getting an error.
Can anyone explain how do I resolve this issue?
You should never attempt to modify your props directly, if something in your components affects how it is rendered, put it in your state :
this.state = {
entries: props.entries
};
To delete your element, just filter it out of your entries array :
delete(key) {
this.setState(prevState => ({
entries: prevState.entries.filter(item => item.key !== key)
}))
}
And now the render function :
render() {
const { entries } = this.state //Takes the entries out of your state
return (
<ul className="theList">
{entries.map(item => <li key={item.key} onClick={this.delete(item.key)}>{item.text}</li>)}
</ul>
);
}
Full code :
class TodoItems extends Component {
constructor(props) {
super(props);
this.state = {
entries: props.entries
};
}
delete = key => ev => {
this.setState(prevState => ({
entries: prevState.entries.filter(item => item.key !== key)
}))
}
render() {
const { entries } = this.state
return (
<ul className="theList">
{entries.map(item => <li key={item.key} onClick={this.delete(item.key)}>{item.text}</li>)}
</ul>
);
}
}
You should also try to never use var. If you do not plan to modify a variable, use const, otherwise, use let.
EDIT : The error shown in your edit come from listTasks not being bound to your class. To solve it you can either bind it (as shown in an other answer below) or convert it in another function :
listTasks = item => {
return <li key={item.key} onClick={() => this.delete(item.key)}>{item.text}</li>
}
Short syntax :
listTasks = ({ key, text }) => <li key={key} onClick={() => this.delete(key)}>{text}</li>
Welcome to Stackoverflow!
Check this section of the React Docs. You either have to bind your class functions in the constructor or use arrow functions.
class TodoItems extends Component {
constructor(props) {
// ...
this.delete = this.delete.bind(this);
}
delete(key) {
this.props.delete(key);
}
// Or without binding explicitly:
delete2 = (key) => {
// ...
}
}
Replace this:
onClick={this.delete(item.key)}
// passes the result of `this.delete(item.key)` as the callback
By this:
onClick={() => this.delete(item.key)}
// triggers `this.delete(item.key)` upon click

React.Js 0.14 - Invariant Violation: Objects are not valid as a React child

I have 2 sub classes and 1 super class (3 components):
Navigation (super)
TopNavZone
MobileNavZone
export default class Navigation extends Component {
constructor(props) {
super(props);
this.state = {
navItems: [],
navMenu: []
};
}
fetchNavItems(clientId) {
NavMenuAPI.getAll(clientId)
.then((response) => {
const data = response.data;
this.setState({ navItems: data });
this.showNavMenu();
});
}
}
Each sub class calls the fetch method in componentDidMount, and then the fetch call, after getting the data, calls the respective sub class's showMenu method:
export default class TopNavZone extends Navigation {
constructor(props) {
super(props);
}
componentDidMount() {
const clientId = this.props.clientId;
// in super class
this.fetchNavItems(clientId);
}
showNavMenu() {
const navItems = this.state.navItems;
const results = [];
navItems.map((item, index) => {
results.push(
// whatever html markup belongs here for TopNavZone
);
});
this.setState({ navMenu: results });
}
render() {
if (!this.state.navMenu) return <div>Loading</div>;
return (
<div>{ this.state.navMenu }</div>
)
}
I know what the error message is telling me. I know React no longer allows objects to be rendered as a child. I tried ...
React.addons.createFragment(results)
in the showNavMenu and received the error that cannot create fragment of undefined.
I like as much of my html away from the render section and refactored into the respective functions that deal with it, so I really do not want to load up my render section with the showNavMenu markup. I'd just assume call it in one line from the render section.
What must I do to make this work and keep a tidy render section?
Many Thanks!
I found the solution and have kept the render section tidy.
The key to my solution lies in performing the mapping inside the render and NOT calling a function to perform that same said mapping.
So .......
Remove the this.showNavMenu() from the super class' fetchNavItems, and the navMenu array from state.
Render now looks like this:
render() {
if (!this.state.navItems) return <div>Loading ...</div>;
return (
<section>
<nav>
<ul>
{ this.state.navItems.map(this.showNavMenu.bind(this)) }
</ul>
</nav>
</section>
);
}
showNavMenu has changed to:
showNavMenu(item) {
const results = [];
let subMenu = [];
let childrenLength = 0;
if (item.children !== undefined) {
const children = item.children;
childrenLength = children.length;
subMenu = this.fetchSubMenu(children);
}
results.push(
<li key={ item.index }>
{ item.title }
</Link>
{ subMenu }
</li>
);
return results;
}
fetchSubMenu:
fetchSubMenu(children) {
const results = [];
children.map((child, idx) => {
results.push(
<div key={ idx }>
<Link to={ child.linkTo }>
{ child.title }
</Link>
</div>
);
});
return results;
}

Resources