I'm using React Js as frontend and node express as backend. When I start the my frontend using npm start it runs without errors but when I start my backend too then it throws this error: TypeError: Cannot read property 'map' of undefined and I could not find any solution for this.
Here's the code where the error is:
import React, { Component } from 'react'
import ApiService from "../../service/ApiService";
import SearchField from "./SearchField";
class ListModuleComponent extends Component {
constructor(props) {
super(props)
this.state = {
modules: [],
message: null
}
this.deleteModule = this.deleteModule.bind(this);
this.editModule = this.editModule.bind(this);
this.addModule = this.addModule.bind(this);
this.reloadModuleList = this.reloadModuleList.bind(this);
}
componentDidMount() {
this.reloadModuleList();
}
reloadModuleList() {
ApiService.fetchModules()
.then((res) => {
this.setState({ modules: res.data.result })
});
}
deleteModule(moduleId) {
ApiService.deleteModule(moduleId)
.then(res => {
this.setState({ message: 'Delete successful.' });
this.setState({ modules: this.state.modules.filter(module => module.id !== moduleId) });
})
}
editModule(id) {
window.localStorage.setItem("moduleId", id);
this.props.history.push('/edit-module');
}
addModule() {
window.localStorage.removeItem("moduleId");
this.props.history.push('/add-module');
}
render() {
return (
<div>
<h2 className="text-center">Modul data</h2>
<button className="btn btn-danger" style={{ width: '100px' }} onClick={() => this.addModule()}>Add</button>
<SearchField />
<table className="table table-striped">
<thead>
<tr>
<th className="hidden">Id</th>
<th>Id</th>
<th>Name</th>
{/* <th>Key</th> */}
<th>Default value</th>
<th>Description</th>
<th>State</th>
</tr>
</thead>
<tbody>
{
this.state.modules.map( // Error throws on this line //
module =>
<tr key={module.id}>
<td>{module.moduleName}</td>
<td>{module.moduleDefaultValue}</td>
<td>{module.description}</td>
<td>{module.isActive}</td>
<td>
<button className="btn btn-success" onClick={() => this.deleteModule(module.id)}> Delete</button>
<button className="btn btn-success" onClick={() => this.editModule(module.id)} style={{ marginLeft: '20px' }}>Edit</button>
</td>
</tr>
)
}
</tbody>
</table>
</div>
);
}
}
export default ListModuleComponent;
So the error only show up if my backend starts and I don't know if it is caused by backend or frontend, only see the error for the React frontend side. I can provide more code if needed.
res.result.data is most likely undefined. You could solve this in 2 ways:
1. Prevent undefined in your state.
You could set your state to an empty array in case res.result.data is undefined like so:
this.setState({ modules: res.data.result || [] });
This will make sure your state always contains an array even if the backend provides no data.
2. Do a null-check before rendering your data.
Check if your this.state.modules holds a value before rendering your content.
<div>
{this.state.modules && this.state.modules.map(module => {
// Code
}}
</div>
The default state for modules is an array, this means that mapping over it is okay, so if it doesnt fetch any data then the component will render just fine.
this.state = {
modules: [],
message: null
}
When the backend has been started the reload modules method is setting the modules to be res.data.result
The error is Cannot read property 'map' of undefined.
Because of this I think res.data.result is undefined.
to fix this check what res is and make sure the thing you are setting in state is the array of data
Seems like a null check would fix the obvious problems here.
{
this.state.modules instanceof Array && this.state.modules.map(
module => { /*...*/ }
)
}
Nevertheless, I strongly assume that the backend has already delivered the data correctly. Also your code has an assumption that the array is always there and I cannot find a place where it has been deleted/false initialized. So I Just assume that the backend returns you an undefined/null value, which would also be covered by this check. So maybe investigate your backend whether it returns a null value.
Related
I'm trying to wrap my head around the MERN stack. So far I've managed to query my database and get the data on an API endopoint, but I'm having some trouble getting it to show up on my front-end.
Here's my fronted code :
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component<{}, {res:any}> {
constructor(props:any) {
super(props);
this.state = {res: Array};
}
callAPI() {
fetch("http://localhost:5000")
.then(res => res.json())
.then(json => this.setState({res: json}));
}
componentWillMount() {
this.callAPI();
}
render() {
// WORKS
console.log(this.state.res[0]);
// DOESN'T WORK
console.log(this.state.res[0].name);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)};
}
export default App;
As you can see it's just a modified version of the default React homepage. I just added a state and fetching data from my backend.
The problem comes when I try to console.log my data.
If I console.log(this.state.res[0]), everything is fine, and I get { "_id": "62207b47d40bca8ea8b60560", "name": "Patère", "checked": false, "links": [ "" ] } in my console. But if I try to only log the name, I get Uncaught TypeError: this.state.res[0] is undefined, which is weird, since it managed to display this.state.res[0] just fine before ?
What's causing this and how can I fix it ?
Thank you in advance.
app.component.ts
Debugger given the error that this.task is undefined
updateTodo(task: any){
this.todoService.updateData(task._id, this.task).subscribe(res => {
this.data= res;
console.log(res);
console.log(this.task);
});
}
app.service.ts
This is service file where the backend api are call in my angular app
updateData(id: any , data: any){
return this.httpClient.put('http://localhost:3000/todos/'+id, data);
}
app.component.html
This is the frontend of my app where the todos show and others user interface
<tbody>
<tr *ngFor="let todo of tasks ; let i = index">
<td>{{todo.todos}}</td>
<td> <input type="checkbox" (change)="updateTodo(todo)"[checked]="todo.isDone</td>
<td>
<button class="btn btn-danger btn-sm" id="del-btn"
(click)="deleteData(todo._id)">Delete</button>
</td>
</tr>
</tbody>
app.model.ts
This is model file
export class Todo {
_id:any;
todos:any;
isDone:any;
}
backend api
This is the backedn function which i created to update my todo
router.put('/:id' , async (req,res) => {
const id = req.params.id;
if(ObjectId.isValid(id)){
const todo = (req.body);
const todoUpdate = await Todo.findByIdAndUpdate(id ,{$set:emp}, {new:true});
res.status(200).json({code:200, message:'Todo Updated Successfully'});
}
else{
res.status(400).send('Todo Not Found By Given Id' + id);
}
});
I'm not sure if we understood each other, but you are passing the task as a parameter but then on two occasions you are trying to use the value of this.task. They are not the same thing and if this.task is not initialized then of course it will show that it's undefined.
updateTodo(task: any) {
console.log('task:', task); // Is the task correct?
this.todoService.updateData(task._id, task).subscribe(res => {
this.data = res;
console.log(res);
console.log(task); //not this.task
});
}
EDIT:
If the DB is not updated you might be sending incorrect data there. If there are no errors on Angular side you have to check the Back-End side.
I solve this question to add [(ngModel)]="todo.isDone" in my checkbox input filed
<tbody>
<tr *ngFor="let todo of tasks ; let i = index">
<td>{{todo.todos}}</td>
<td> <input type="checkbox" (change)="updateTodo(todo)" [(ngModel)]="todo.isDone</td>
<td>
<button class="btn btn-danger btn-sm" id="del-btn"
(click)="deleteData(todo._id)">Delete</button>
</td>
</tr>
And In my app.component.ts
updateTodo(task: any) {
this.todoService.updateData(task._id, task).subscribe(res => {
this.data = res;
console.log(res);
console.log(task);
});
}
I'm working on a NodeJS/Angular project and while trying to do a simple CRUD, I'm blocked when I try to get an element by ID.
I would like to retrieve all the info of a "Member" based on its ID and display the info in a table. I manage to get my JSON with the API call but when trying to display it in the table, it doesn't show anything.
My service, with the API call :
public getMember(id: number) {
return new Promise((resolve, reject) => {
this.http.get(this.config.apiServer + `astreintes/member/get/${id}`)
.subscribe((res) => {
resolve(res as Member);
console.log(res);
}, err => {
reject(err);
});
});
}
Result of the console.log: My correct JSON with the info of the member
My component.ts :
public search(){
this.memberService.getMember(this.id).then((data) => {
if(data){
this.member = (data as any).recordset;
console.log("Get member :"+ data);
this.indice = true;
}else{
}},
(error) => {
console.log(error);
}
);
}
Result of the console.log: "Get member : [Object Object]"
For the interface, I just have a dropdown list of all my members, and when I select one and click on the button "Search", it gets the info of my member correctly in the console. Then, I want to display it in my table below. My html code:
<form (submit)='search()' #searchMemberForm="ngForm" class="form-horizontal">
<select [(ngModel)]="id" name="member">
<option *ngFor="let member of membersList"
[value]="member.Id_OnCall_Member">{{member.Oncall_Member_Name}}</option>
</select>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-save btn-primary">Search</button>
</div>
</div>
</form>
<div *ngIf="indice">
<h1 style="text-align: center">
Informations
</h1>
<table class="table table-striped" *ngIf="indice">
<th>ID</th>
<th>Nom</th>
<th>Numéro de téléphone</th>
<th>Statut d'activité</th>
<tr *ngFor="let m of member">
<td>{{m.Id_OnCall_Member}}</td>
<td>{{m.Oncall_Member_Name}}</td>
<td>{{m.OnCall_Member_Phone}}</td>
<td>{{m.OnCall_Member_Status}}</td>
</tr>
</table>
</div>
Thanks for your help !
you used "+" in console.log: console.log("Get member :"+ data);
it means javascript is trying to convert the output to one type (string), but data is object. Below you can find how to get the correct output.
const res = {
memberId: 1
}
console.log(res) // {memberId: 1}
console.log('Member: '+ res); //Member: [object Object]
console.log('Member: ', res); // Member: {memberId: 1}
console.log('Member: '+ JSON.stringify(res)); // Member: {memberId: 1}
thanks for your answers.
My problem wasn't the console.log though, it was just an indication.
My real problem was that it didn't show anything in my table. But I solved it, here's how in case it might help other people.
In my component.ts, I replaced:
this.member = (data as any).recordset;
By this, simply:
this.member = data;
I have the code of my component like :
import React, { Component } from 'react';
import axios from 'axios';
import "bootstrap/dist/css/bootstrap.min.css";
import {
Table
} from 'reactstrap';
const Adhoc = async (props) => {
let cost = await props.cost(props.adhoc._id)
return(
<tr>
<td>{props.adhoc.jIssue}</td>
<td>{props.adhoc.paid ? "Paid" : "Not paid"}</td>
<td>APP{props.adhoc.sprint}</td>
<td>£{cost.data[0]}</td>
</tr>
)}
export default class QAdhocsDisplay extends Component {
constructor(props) {
super(props);
this.costingAdhoc = this.costingAdhoc.bind(this)
this.state = {
adhocs: []
};
}
componentDidMount() {
axios.get('http://localhost:5000/adhocs/retrieve')
.then(response => {
this.setState({ adhocs: response.data })
})
.catch((error) => {
console.log(error);
})
}
async costingAdhoc(id) {
const data = await axios.get('http://localhost:5000/jira/issue/' + id)
.catch((error) => {
console.log(error);
})
return data;
}
adhocsList() {
return this.state.adhocs.map(currentadhoc=> {
return <Adhoc adhoc={currentadhoc} cost={this.costingAdhoc} key={currentadhoc._id}/>;
})
}
render(){
return (
<div className="toborder" style = {{paddingBottom: "59px"}}>
<div className="display" style ={{backgroundColor: "#5394b2"}}>
<h5 style = {{padding:"13px"}}>Adhoc status</h5>
</div>
<div className="table">
<Table size="sm" bordered striped>
<thead className="thead-light">
<tr className="adhocs">
<th className="sticky-column medium" >Adhoc issue</th>
<th className="sticky-column medium" >Payment status</th>
<th className="sticky-column medium" >Sprint</th>
<th className="sticky-column medium" >Projected cost</th>
</tr>
</thead>
<tbody>
{ this.adhocsList() }
</tbody>
</Table>
</div>
</div>
)}
}
My issue is that I have the function costingAdhoc(id) which I pass as a prop to the child component Adhoc. To be able to access the information from the axios call I need these both functions to be async.
A child component of type Adhoc will be rendered for each item in the state that will get mapped in the function adhocsList(). For some reason this causes the axios call in the componentDidUpdate() to throw this error:
Objects are not valid as a React child (found: [object Promise]). If
you meant to render a collection of children, use an array instead.
And the error points to the line where I set the state. This means that the costingAdhoc(id) function async nature causes my axios call in the componentDidUpdate() function only return a promise and not the actual data.
Your problem that is Adhoc component which you're declaring as a child component is actually a promise.
Why?
A function that is declared as async returns promise by default, so even if you return:
<tr>
<td>{props.adhoc.jIssue}</td>
<td>{props.adhoc.paid ? "Paid" : "Not paid"}</td>
<td>APP{props.adhoc.sprint}</td>
<td>£{cost.data[0]}</td>
</tr>
you're actually returning it wrapped inside a promise. React components cannot be promises.
I am more familiar with NodeJs than react. I have build a react component that searches for user input and provides the output in a table format based on the value that the user has typed into the search input form. This is working as I want and the code for the module is below:
import React, { Component } from 'react';
import axios from 'axios';
import Suggestions from './Suggestions';
// API url
const API_URL = 'http://localhost:3000/api/file_infos'
class Search extends Component {
state = {
query: '',
results: []
}
getCount = () => {
axios.get(`${API_URL}count?filter[where][id][regexp]=/${this.state.query}/i`)
.then(count => {
this.setState({
results: count.data
})
})
}
// query loop back API for matching queries base on text input
getInfo = () => {
axios.get(`${API_URL}?filter[where][id][regexp]=/${this.state.query}/i&filter[limit]=20`)
.then(response => {
this.setState({
results: response.data
})
})
}
// check to see if input on the search bar has changed and update the search query accordingly
handleInputChange = () => {
this.setState({
query: this.search.value
}, () => {
if (this.state.query && this.state.query.length > 1) {
if (this.state.query) {
this.getInfo()
}
} else if (!this.state.query) {
}
})
}
// render form and pass results back to the home component
render() {
return (
<div>
<form>
<input
placeholder="Search for..."
ref={input => this.search = input}
onChange={this.handleInputChange}
/>
</form>
<Suggestions results={this.state.results} />
</div>
)
}
}
export default Search
The second module is the suggestions module that displays the output in the table format.
The next portion of the app I am building will open a file based on the table row that the user selected. I want that table data returned to a function so that I can make an http post request to my API that will in turn open the file using a NodeJS module.
I want the suggestions component to return the value of the data items in the table cells so that the data can be used to send to the API in order to open my files. The code I have come up with so far is only returning an undefined error.
Below is what I currently have:
import React from 'react';
// return results in a table format based on the text input entered
const Suggestions = (props) => {
const state = {
results: []
}
const handleFormOpen = () => {
this.setState({
results: this.results.value
},
console.log(this.state.results)
)
}
const options = props.results.map(r => (
<tr key={r.id} ref={tr => this.results = tr} onClick={handleFormOpen.bind(this)}>
<td>{r.id}</td>
<td>{r.OriginalPath}</td>
<td>{r.CreateDate}</td>
<td>{r.AccessDate}</td>
<td>{r.WriteDate}</td>
<td><i className="fas fa-book-open"></i></td>
</tr>
))
return <table className="striped responsive-table">
<thead>
<tr>
<th>File Name</th>
<th>Parent Directory</th>
<th>Creation Date</th>
<th>Access Date</th>
<th>Write Date</th>
<th>Open File</th>
</tr>
</thead>
<tbody>
{options}
</tbody>
</table>
}
export default Suggestions;
I am really unsure at this point if I am trying to tackle this issue in the correct way. I am thinking that maybe the suggestions component may need to be turned into a full class extending component but I am fairly lost at this point. Can someone please kindly point out my folly and get me going in the right direction?
UPDATE
As requested in the comments here is the error log from my browser:
Suggestions.js:10 Uncaught TypeError: Cannot read property 'results' of undefined
at Object.handleFormOpen (Suggestions.js:10)
at HTMLUnknownElement.callCallback (react-dom.development.js:145)
at Object.invokeGuardedCallbackDev (react-dom.development.js:195)
at invokeGuardedCallback (react-dom.development.js:248)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:262)
at executeDispatch (react-dom.development.js:593)
at executeDispatchesInOrder (react-dom.development.js:615)
at executeDispatchesAndRelease (react-dom.development.js:713)
at executeDispatchesAndReleaseTopLevel (react-dom.development.js:724)
at forEachAccumulated (react-dom.development.js:694)
at runEventsInBatch (react-dom.development.js:855)
at runExtractedEventsInBatch (react-dom.development.js:864)
at handleTopLevel (react-dom.development.js:4857)
at batchedUpdates$1 (react-dom.development.js:17498)
at batchedUpdates (react-dom.development.js:2189)
at dispatchEvent (react-dom.development.js:4936)
at interactiveUpdates$1 (react-dom.development.js:17553)
at interactiveUpdates (react-dom.development.js:2208)
at dispatchInteractiveEvent (react-dom.development.js:4913)
First thing Since your Suggestions component plays with state, I would recommend you to go with statefull component.
Stateless component is meant for getting props and returning jsx elements, there wont be any state mutations in stateless component. This is called pure function in javascript. Hope this makes clear.
Also since you declared handleFormOpen as an arrow function you no need to do binding. binding takes care automatically by arrow function. If you don't want to use arrow function and you want to bind it then do the binding always in constructor only but don't do binding anywhere in the component like you did in map.
PFB corrected Suggestions component code
import React, { Component } from 'react';
// return results in a table format based on the text input entered
export default class Suggestions extends Component {
constructor(props){
super(props);
this.state = {
results: [],
value: ""
}
}
handleFormOpen = (path, id) => {
console.log("id", id, path);//like wise pass value to this function in .map and get the value here
this.setState({
value: id
});
}
render(){
const { results } = this.props;
return (<div>
<table className="striped responsive-table">
<thead>
<tr>
<th>File Name</th>
<th>Parent Directory</th>
<th>Creation Date</th>
<th>Access Date</th>
<th>Write Date</th>
<th>Open File</th>
</tr>
</thead>
<tbody>
{Array.isArray(results) && results.length > 0 && results.map(r => (
<tr key={r.id} ref={tr => this.results = tr} onClick={() => this.handleFormOpen(r.OriginalPath, r.id)}>
<td>{r.id}</td>
<td>{r.OriginalPath}</td>
<td>{r.CreateDate}</td>
<td>{r.AccessDate}</td>
<td>{r.WriteDate}</td>
<td><i className="fas fa-book-open"></i></td>
</tr>
))}
</tbody>
</table>
</div>)
}
}
export default Suggestions;
You are using states in Functional Component, You need to use React Component
import React from 'react';
// return results in a table format based on the text input entered
class Suggestions extends React.Component {
constructor(props) {
super(props);
this.state = {
results: [],
}
}
handleFormOpen = () => {
this.setState({
results: this.results.value
},
console.log(this.state.results)
)
}
render () {
const options = this.props.results.map(r => (
<tr key={r.id} ref={tr => this.results = tr} onClick={handleFormOpen.bind(this)}>
<td>{r.id}</td>
<td>{r.OriginalPath}</td>
<td>{r.CreateDate}</td>
<td>{r.AccessDate}</td>
<td>{r.WriteDate}</td>
<td><i className="fas fa-book-open"></i></td>
</tr>
))
return (
<table className="striped responsive-table">
<thead>
<tr>
<th>File Name</th>
<th>Parent Directory</th>
<th>Creation Date</th>
<th>Access Date</th>
<th>Write Date</th>
<th>Open File</th>
</tr>
</thead>
<tbody>
{options}
</tbody>
</table>
)
}
}
export default Suggestions;