Why can I not call upon the property of this object? (Angular, MongoDB) - node.js

I am making an angular application which uses a MongoDB database and NodeJS server.
The idea is that I make an application which for now only has a list of posts and beside that the detailed-post. The components are nicely standing next to eachother and working but I have one problem. When I try to retrieve a single post I can see via console.dir(post) that all is good and the object has been transmitted to the angular app. The problem is that when I try to use post.content I get an undefined message.
I have searched for hours but can not seem to find the cause of this. I would greatly appreciate any help you can give me. Beneath here is all the information, if you need to see something else, please tell me.
Thanks in advance.
This is the post-detail.component.html where I want to display the data.
<div class="row">
<div class="col-xs-12">
<p>Content:</p>
<h1>{{ post.content }}</h1>
</div>
</div>
The detail.ts file (I left out the imports)
#Component({
selector: 'app-post-detail',
templateUrl: './post-detail.component.html',
styleUrls: ['./post-detail.component.css']
})
export class PostDetailComponent implements OnInit {
post: Post = new Post();
id: string;
constructor(private postService: PostService,
private route: ActivatedRoute,
private router: Router) {
}
ngOnInit() {
this.route.params
.subscribe(
(params: Params) => {
this.id = params['id'];
this.postService.getPost(this.id).then(res => {
console.dir(res);
console.dir(res.content);
this.post = res;
});
}
);
}
}
The post.service.ts which I am using to retrieve the actual data:
#Injectable()
export class PostService {
postChanged = new Subject<Post[]>();
private headers = new Headers({'Content-Type': 'application/json'});
private serverUrl = environment.serverUrl + '/blogPosts/'; // URL to web api
private posts: Post[];
constructor(private http: Http) {
}
//this one DOES work
getPosts() {
console.log('Fetching BlogPosts from database.')
return this.http.get(this.serverUrl, {headers: this.headers})
.toPromise()
.then(response => {
this.posts = response.json() as Post[];
return response.json() as Post[];
})
.catch(error => {
return error;
});
}
getPost(index: string) {
console.log('Fetching individual BlogPost from database.');
console.log('index' + index);
if (index == null) {
console.log('null');
return null;
}
return this.http.get(this.serverUrl + index, {headers: this.headers})
.toPromise()
.then(response => {
console.dir(response.json().content);
console.dir(response.json());
return response.json() as Post;
})
.catch(error => {
return this.handleError(error);
});
}
}
The Post model:
export class Post {
private id: string;
private _content: string;
constructor(values: Object = {}) {
Object.assign(this, values);
}
public get _id(): string {
return this.id;
}
public set _id(n: string) {
this.id = n;
}
public get content(): string {
return this._content;
}
public set content(n: string) {
this._content = n;
}
}
And I added in the Postman GET /blogPost/id and the console log as images.
Thanks!
Console log
Postman GET route

I might be wrong but can you please change the _content to content everywhere in the service ?
Edit: are you sure the this.id is correct when you call the service method ? cause if it is null or undefined then return null will be executed.
Another note is that in Postman i see the response is an array of objects (one object) at this example. Can you try this.post = res[0]; in the component ?

return response.json() as Post;
In post.service.ts should be:
return response.json()[0] as Post;
I did not see that the object was wrapped in an array, by accessing it I was able to get it out and use it.

Related

Performing a fetch request only on state update

I'm new to React and I'm trying to figure out how to work with fetch correctly.
I have a React component that I'd like to update from a remote server whenever its parent's state updated.
i.e - parent's state changed -> myComponent calls remote server and re-renders itself.
I've tried the following:
If I only perform the .fetch call on componentDidMount, it disregards any state updates.
If I perform the .fetch call on componentDidUpdate as well it calls the server endlessly (I assume because of some update-render loop)
I have tried using the componentWillReceiveProps function, and it works, but I understand it's now deprecated.
How can I achieve this kind of behavior without componentWillReceiveProps ?
class myComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
images: []
};
}
componentDidMount() {
let server = "somethingserver.html";
fetch(server)
.then(res => {
if (res.ok) {
return res.json();
}
else {
console.log(res);
throw new Error(res.statusText);
}
})
.then(
(result) => {
this.setState({
images: result.items
});
}
).catch(error => console.log(error));
}
componentWillReceiveProps(nextprops) {
if (this.state !== nextprops.state) {
//same as componentDidMount
}
}
render() {
return (
<Gallery images={this.state.images} enableImageSelection={false} />
);
}
}
Given our conversation in the comments I can only assume that your search term is in a parent component. So what I recommend you to do is pass it to this component as a prop so you can do the following in your componentDid update:
componentDidUpdate(prevProps) {
const { searchTerm: previousSearch } = prevProps;
const { searchTerm } = this.props;
if (searchTerm !== previousSearch) fetch() ....
}
You can use getDerivedStateFromProps. It's the updated version of componentWillReceiveProps.
You should also read this, though: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
Using props to update internal state in a component can lead to complex bugs and there are often better solutions.

Reactjs - this.state.data.map arguments is UNDEFINED

ive been trying to fetch data from my api but its somehow confusing that this.state.Data.map does work but the argument dynamicdata is undefined.
in App.js react
class ShowAll extends Component {
constructor(props){
super(props);
this.state = {
Data: [],
}
}
componentDidMount(){
Request.get('/budget').then((res)=>{
let DataString = JSON.stringify(res.body);
this.setState({
Data: DataString
}, function(){
console.log(DataString);
})
}).catch((err)=> console.log(err));
}
render(){
return(
<div>
{
this.state.Data.map(function(dynamicData, key){
<div>{dynamicData[0]._id}</div> // doesn't render anything and throws an error message saying TypeError: Cannot read property '_id' of undefined
})
}
</div>
)
}
}
**EDIT 1 **
The api data structure is
[{
"_id":"lul",
"_creator":"5a8f8ecdd67afa6494805bef",
"firstItem":"hero",
"secondItem":"30",
"thirdItem":"3",
"__v":0,
"tBudget":9,
"thirdPrice":3,
"secondPrice":3,
"firstPrice":3
}]
Oh. Your original post was a completely different problem.
Two problems. First:
You're mapping, so you don't need to index the mapped value
this.state.Data.map(function(dynamicData, n) {
// dynamicData _is_ the nth element in the array, you don't need dynamicData[x]
dynamicData._id === "lul"
})
second, you're not returning anything from your map callback
map(function(...) {
return (<div>...</div>)
})
Based on the server response, you don't need to access the item 0 when rendering each one of them
class ShowAll extends Component {
constructor(props){
super(props);
this.state = {
Data: [],
}
}
componentDidMount(){
Request.get('/budget').then((res)=>{
this.setState({
Data: res.body, // Assuming res.body is already an array
}, function(){
console.log(DataString);
})
}).catch((err)=> console.log(err));
}
render(){
return(
<div>
{
this.state.Data.map((dynamicData, key) => // Using an arrow function
<div>{dynamicData._id}</div> // Don't access the item 0
)
}
</div>
)
}
}
The previous code assumes res.body is already an array. If that's not the case and you are actually getting a string or something else, you need to parse the response and make sure you assign an array to the state.

Storing observable data into global variable returns 'undefined' in Angular 4

I am currently working with a node server that I've set up and created an endpoint /user-info which res.send({id: <my-id>, name: <my-display-name>})
On Angular I have created a global.service.ts file that will call this endpoint using http.get and subscribe that data and store into two variables I have declared.
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class Globals {
constructor(private http: HttpClient) { }
public id: string;
public name: string;
userInfo() {
this.http.get('/user-info').subscribe(
(data: any) => {
this.id = data.id;
}
);
console.log(this.id);
}
}
Once I console.log(this.id) it returns undefined. I have already the server-side to see if that was causing the problem but it returns a string.
In my server.js file (node server) I am working with express. This is the endpoint:
app.get('/user-info', function(req, res) {
client.get('id', (err, data) => {
client.get('name', (err, reply) => {
res.send({id: data, name: reply})
})
})
})
I am using redis to store values 'id' and 'name' and the client.get is just a redis command used to call those values from cache. I have tested just checking localhost:8000/user-info and everything looks fine.
Am I missing/misunderstanding something? Thanks!
if console.log still outside of call, it will execute before you got a response. Try this:
userInfo() {
this.http.get('/user-info').subscribe(
(data: any) => {
this.id = data.id
console.log(this.id);
}
)
}

fetch not returning data in react

I'm new to react, i'm having difficulty getting data for a single book out of list, be passed through via axios' get method.
I think it has something to do with the url, but I have been unable to get fix it.
Here's my code:
export function loadBook(book){
return dispatch => {
return axios.get('http://localhost:3000/api/books/book/:id').then(book => {
dispatch(loadBookSuccess(book.data));
console.log('through!');
}).catch(error => {
console.log('error');
});
};
}
//also tried this
export function loadBook(id){
return dispatch => {
return axios.get('http://localhost:3000/api/books/book/' + {id}).then(book => {
dispatch(loadBookSuccess(book.data));
console.log('through!');
}).catch(error => {
console.log('error');
});
};
}
Html code that contains a variable link to each individual book
<div className="container">
<h3><Link to={'/book/' + book._id}> {book.title}</Link></h3>
<h5>Author: {book.author.first_name + ' ' + book.author.family_name}</h5>
<h4>Summary: {book.summary}</h4>
<BookGenre genre={genre} />
</div>
link in Route:
<Route path="/book/:id" component={BookPage} />
Edit: code for the book component
class BookPage extends React.Component{
render(){
const book = this.props;
const genre = book.genre;
console.log(book);
return(
<div>
<div>
<h3> {book.title}</h3>
<h5>Author: {book.author.first_name + ' ' + book.author.family_name}</h5>
<h4>Summary: {book.summary}</h4>
<BookGenre genre={genre} />
</div>
</div>
);
}
}
BookPage.propTypes = {
book: PropTypes.object.isRequired
};
//setting the book with mapStateToProps
function mapStateToProps (state, ownProps){
let book = {title: '', author: '', summary: '', isbn: '', genre: []};
const bookid = ownProps.params._id;
if(state.books.length > 0){
book = Object.assign({}, state.books.find(book => book.id));
}
return {
book: book
};
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(loadBook, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(BookPage);
Instead of doing this:-
axios.get('http://localhost:3000/api/books/book/' + {id})
You should do like this:-
axios.get(`http://localhost:3000/api/books/book/${id}`)
So your action.js might look like this:-
export function loadBook(id){
const request = axios.get(`http://localhost:3000/api/books/book/${id}`);
return dispatch => {
request.then(book => {
dispatch(loadBookSuccess(book.data));
}).catch(error => {
console.log('error');
})
};
}
Since the id, you have passed it seems to be a string so it can be concatenated using ES6 template strings and make sure you wrap your strings in backtick . or you can do it by + operator, also make sure you pass id as a parameter in your loadbook function so that you can join it to your URL.
Figured out the solution to this problem.
My mistake was that I failed to send the id of the item I along with the api call.
Using componentDidMount and sending the dynamic id from the url params solved this problem for me.
Thank you, #Vinit Raj, I guess I was too much of a rookie then.

Angular2 Passing parameters to web service http GET

I have a profileComponent which is making a GET call to service endpoint as follows , AparmentService is injected in bootstarp, hence no providers
#Component({
selector: 'profile',
template: `<h1>Profile Page</h1>
{{userEmail.email}}
{{profileObject | json}}
`,
directives: [ROUTER_DIRECTIVES]
})
export class ProfileComponent implements OnInit {
userEmail = JSON.parse(localStorage.getItem('profile'));
public profileObject: Object[];
constructor(private apartmentService: ApartmentService) {
this.apartmentService = apartmentService;
}
ngOnInit(): any {
console.log(this.userEmail.email); <--This value displays fine in the console
this.apartmentService.getProfile(this.userEmail.email).subscribe(res => this.profileObject = res); <-- getting [] response for this
console.log(JSON.stringify(this.profileObject)); <-- undefined
}
}
The service looks like this
#Injectable()
export class ApartmentService {
http: Http;
constructor(http: Http) {
this.http = http;
}
getProfile(userEmail :string){
return this.http.get('/api/apartments/getprofile/:userEmail').map((res: Response) => res.json());
}
}
when I try to hit the endpoint directly in the browser with the parameter, I am getting the respone. But not within Angular.
Any Ideas ?
http.get() is async
ngOnInit(): any {
console.log(this.userEmail.email); <--This value displays fine in the console
this.apartmentService.getProfile(this.userEmail.email).subscribe(res => this.profileObject = res); <-- getting [] response for this
// at this position the call to the server hasn't been made yet.
console.log(JSON.stringify(this.profileObject)); <-- undefined
}
When the response from the server arives res => this.profileObject = res is executed. console.log() is made before the call to the server was even initalized
Use instead
ngOnInit(): any {
console.log(this.userEmail.email); <--This value displays fine in the console
this.apartmentService.getProfile(this.userEmail.email)
.subscribe(res => {
this.profileObject = res;
console.log(JSON.stringify(this.profileObject));
});
}
I think :userEmail in the URL isn't doing what you expect. Try instead:
getProfile(userEmail :string){
return this.http.get(`/api/apartments/getprofile/${userEmail}`).map((res: Response) => res.json());
}

Resources