Display content from AJAX request using setState - ajax

I'm doing a webapp using github search API. I want the info for each repo to be displayed under the specific repo.
I want the content from the AJAX request to be displayed when the specific button is being pressed. I am using React. Since I'm using item.x to access the information I need to get that item and I assume I need to use map but when doing that it will display all the results and not just the specific repository's. Anyway that I can get the Item since it currently says it's undefined?
let searchTerm;
class SearchBox extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = { repositories: [],
showInfo: false };
}
render() {
let moreDetail;
if(this.state.showInfo){
moreDetail= <div className="info"> <li>
<p>Open issue count </p>:{item.open_issues_count}
</li>
<li>
<p>Number of forks </p>:{item.forks}
</li>
<li>
<p>Language </p>:{item.language}
</li></div>;
}
return(
<div>
<form>
<input type="text" className="searchbox" ref={(input) => { this.searchBox = input; }}/>
<button onClick={this.onClick}>Search</button>
</form>
<h2>Repositories</h2>
<ul>
{ this.state.repositories.map( ( item, index ) => (
<div key={ index }>
<a href={item.html_url}> <li >
{ item.name }
</li>
</a>
{moreDetail}
<button onClick={this._handleClick.bind(this)}>Detailed view</button>
</div>
)) }
</ul>
</div>
);
}
_handleClick(){
this.setState({
showInfo: !this.state.showInfo
});
}
onClick(event) {
searchTerm = this.searchBox.value;
let endpoint = 'https://api.github.com/search/repositories?sort=stars&order=desc&q=' + searchTerm;
console.log(searchTerm);
fetch(endpoint)
.then(blob => blob.json())
.then(response => {
this.setState({ repositories: response.items });
});
event.preventDefault();
}
}

The problem is with context. When you define the moreDetail variable, you don't have item in your context (only during the map function you have that.)
One option is to use the variable moreDetail as a function that receives the item you want to show.
Your render method should look something like:
render() {
const moreDetail = (item) => ( !this.state.showInfo ? <span /> :
<div className="info">
<li>
<p>Open issue count </p>:{item.open_issues_count}
</li>
<li>
<p>Number of forks </p>:{item.forks}
</li>
<li>
<p>Language </p>:{item.language}
</li>
</div>
);
return (
<div>
<form>
<input type="text" className="searchbox" ref={(input) => { this.searchBox = input; }}/>
<button onClick={this.onClick}>Search</button>
</form>
<h2>Repositories</h2>
<ul>
{ this.state.repositories.map( ( item, index ) => (
<div key={ index }>
<a href={item.html_url}> <li >
{ item.name }
</li>
</a>
{moreDetail(item)}
<button onClick={this._handleClick.bind(this)}>Detailed view</button>
</div>
)) }
</ul>
</div>
);
}

Related

How to self reference Astro component

I have this component Astro component (located at "src/components/Menu.astro");
---
export interface MenuItem {
name: string;
link: string;
items?: MenuItem[];
}
export interface Props {
items: MenuItem[];
depth: number;
}
const { items, depth } = Astro.props;
---
<ul data-depth={depth}>
{
items.map(({ name, link, items: subItems }) => {
if (subItems && subItems.length > 0) {
return (
<li>
<div class="dropdown">
{link ? <a href={link}>{name}</a> : <button>{name}</button>}
<Menu items={subItems} depth={depth + 1} />
</div>
</li>
);
}
return (
<li>
<a href={link}>{name}</a>
</li>
);
})
}
</ul>
On line 28 (where the line reads <Menu items={subItems} depth={depth + 1} />) an error thrown saying;
ReferenceError: Menu is not defined
How can I self reference an Astro component in this case? Thanks in advance.
PS: "Menu" is the component file's name.
Astro has a built in method for this called Astro.self that you can use
Astro.self
Example from docs:
---
const { items } = Astro.props;
---
<ul class="nested-list">
{items.map((item) => (
<li>
<!-- If there is a nested data-structure we render `<Astro.self>` -->
<!-- and can pass props through with the recursive call -->
{Array.isArray(item) ? (
<Astro.self items={item} />
) : (
item
)}
</li>
))}
</ul>
In the front-matter, i.e. between the two ---, you write import Menu from "./Menu.astro".
You'll get new errors, as you can't use statements in the body.
Change
items.map(({ name, link, items: subItems }) => {
if (subItems && subItems.length > 0) {
return (
<li>
<div class="dropdown">
{link ? <a href={link}>{name}</a> : <button>{name}</button>}
<Menu items={subItems} depth={depth + 1} />
</div>
</li>
);
}
return (
<li>
<a href={link}>{name}</a>
</li>
);
})
to
items.map(({ name, link, items: subItems }) =>
(subItems && subItems.length > 0)
? <li>
<div class="dropdown">
{link ? <a href={link}>{name}</a> : <button>{name}</button>}
<Menu items={subItems} depth={depth + 1} />
</div>
</li>
: <li>
<a href={link}>{name}</a>
</li>
)

Splice doesn't delete any item from array - Angular12

I am trying create a function for deleting specific items from a shopping cart.
It doesn't give any errors, but when I click the button nothing happens. Any idea what might be the problem?
See the codes below about my issue.
service.ts
removeCartItem(product: Product){
this.cartItemList.map((a:any, index:any)=>{
if(product.id=== a.id){
this.cartItemList.splice(index,1);
}
})
cartitem.component.html
<div class="cartitem">
<div class="container">
<div class="row">
<div class="col">
{{ cartItem.name }}
</div>
<div class="col">
<img [src]="cartItem.imageUrl" class="card-img-top" alt="..." />
</div>
<div class="col-5">
{{ cartItem.description }}
</div>
<div class="col">{{ cartItem.price | currency: "EUR" }}</div>
<div class="col">{{ cartItem.qty }}</div>
<div>
<button (click)="removeItem(item)" class="btn btn-primary">
Remove from cart
</button>
</div>
</div>
</div>
</div>
cartitem.component.ts
constructor(public service: MessengerService) { }
ngOnInit(): void {
}
removeItem(item: Product){
this.service.removeCartItem(item);
}
cart.component.ts
cartItems: Product[] = [];
cartTotal = 0;
product: any;
constructor(private msg: MessengerService, public dialog: MatDialog) {}
ngOnInit() {
this.msg.getMsg().subscribe((product: Product) => {
this.addProductToCart(product);
});
}
addProductToCart(product: Product) {
let productExists = false;
for (let i in this.cartItems) {
if (this.cartItems[i].id === product.id) {
this.cartItems[i].qty++;
productExists = true;
break;
}
}
if (!productExists) {
this.cartItems.push({
id: product.id,
name: product.name,
description: product.description,
qty: 1,
price: product.price,
imageUrl: product.imageUrl,
purchased:product.purchased
});
}
this.cartItems.forEach((item) => {
this.cartTotal += item.qty * item.price;
});
}
cart.component.html
<ul *ngIf="cartItems.length > 0" class="list-group">
<li class="list-group-item">
<h3>My Cart</h3>
</li>
<li class="list-group-item" *ngFor="let item of cartItems">
<app-cartitem [cartItem]="item"></app-cartitem>
</li>
<li class="list-group-item">
<span>Total: {{ cartTotal | currency: "EUR" }} </span>
</li>
<li class="list-group-item">
<button
id="btnFinalize"
class="btn btn-primary"
(click)="purchaseDisabled(product)"
>
Finalize purchase
</button>
</li>
</ul>
The problem is that you are trying to splice inside map. Changing to the following will work:
removeCartItem(product: Product){
let indexToRemove: number = -1;
this.cartItemList.map((a:any, index:any)=>{
if(product.id === a.id){
indexToRemove = index;
}
return a;
});
if(indexToRemove !== -1){
this.cartItemList.splice(indexToRemove,1);
}
}
But notice there is no need to use map here. It's a waste of time and memory to copy the entire array again. Just a loop through the array to find the index to remove would be enough:
removeCartItem(product: Product){
let indexToRemove: number = -1;
let index: number = 0;
for(const cardItem of this.cartItemList){
if(product.id === cardItem.id){
indexToRemove = index;
}
index++;
}
if(indexToRemove !== -1){
this.cartItemList.splice(indexToRemove,1);
}
}

Why does Vue evaluate my v-if to false despite it being true?

When I run this, vue returns the second template, even though groups.length is equal to 1.
Why? Does it have to do with the order in which the mounting occurs and v-if is evaluated? Again, I am certain that groups.length evaluates to 1. I have tried using beforeMount as opposed to mounted, but that did not work.
<template v-if = "groups.length">
<ul id = "groupList">
<li v-for = "group in groups">
<a>{{ group.name }}</a>
</li>
<div class = "addSidebar">
<label class = "btn" for = "modal-1">+</label>
</div>
</ul>
</template>
<template v-else>
<ul id = "groupList">
<li>
<a>You have not created/joined any groups.</a>
</li>
<div class = "addSidebar">
<label class = "btn" for = "modal-1">+</label>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
groups: {}
}
},
methods: {
getGroups() {
axios.get('groupList').then((response) => {
this.groups = response.data
}).catch((errors) => {
console.log(errors)
});
},
newModal() {
$('#modal').modal('show');
}
},
mounted() {
this.getGroups()
},
name: "groupList"
}
</script>
you need to use javascript Async
https://www.w3schools.com/js/js_async.asp
<template >
<div>
<ul id="groupList" v-if="groups.length">
<li v-for="group in groups" :key="group.id">
<a>{{ group.name }}</a>
</li>
<div class="addSidebar">
<label class="btn" for="modal-1">+</label>
</div>
</ul>
<ul id="groupList" v-else>
<li>
<a>You have not created/joined any groups.</a>
</li>
<div class="addSidebar">
<label class="btn" for="modal-1">+</label>
</div>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
groups: {},
};
},
methods: {
async getGroups() {
await axios
.get("groupList")
.then((response) => {
this.groups = response.data;
})
.catch((errors) => {
console.log(errors);
});
},
newModal() {
$("#modal").modal("show");
},
},
async mounted() {
await this.getGroups();
},
name: "groupList",
};
</script>
in your code you created 2 <template > which is not valid syntax and vue should have root element
https://v2.vuejs.org/v2/guide/components.html#A-Single-Root-Element
Change
v-if = "groups.length"
to:
v-if="groups && groups.length > 0"
And you should have a single that contains one elements in it.

Active user does not show in real time chat app

I am following up this guide https://www.codechief.org/article/real-time-chat-app-with-laravel-6-vue-js-and-pusher#gsc.tab=0 to create real-time chat app in Laravel and Vue.
But it does not show list of active user.
Also this span never shows
<span class="text-muted" v-if="activeUser" >{{ activeUser.first_name }}` is typing...</span>
Also, this method does not work properly because in console log it shows undefined is typing...
sendTypingEvent() {
Echo.join('chat')
.whisper('typing', this.user);
console.log(this.user.fist_name + ' is typing now')
}
And it is not actually real time, because I see new messages only if I reload page.
This is Vue component
<template>
<div class="row">
<div class="col-8">
<div class="card card-default">
<div class="card-header">Messages</div>
<div class="card-body p-0">
<ul class="list-unstyled" style="height:300px; overflow-y:scroll" v-chat-scroll>
<li class="p-2" v-for="(message, index) in messages" :key="index" >
<strong>{{ message.user.first_name }}</strong>
{{ message.message }}
</li>
</ul>
</div>
<input
#keydown="sendTypingEvent"
#keyup.enter="sendMessage"
v-model="newMessage"
type="text"
name="message"
placeholder="Enter your message..."
class="form-control">
</div>
<span class="text-muted" v-if="activeUser" >{{ activeUser.first_name }} is typing...</span>
</div>
<div class="col-4">
<div class="card card-default">
<div class="card-header">Active Users</div>
<div class="card-body">
<ul>
<li class="py-2" v-for="(user, index) in users" :key="index">
{{ user.first_name }}
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props:['user'],
data() {
return {
messages: [],
newMessage: '',
users:[],
activeUser: false,
typingTimer: false,
}
},
created() {
this.fetchMessages();
Echo.join('chat')
.here(user => {
this.users = user;
})
.joining(user => {
this.users.push(user);
})
.leaving(user => {
this.users = this.users.filter(u => u.id != user.id);
})
.listen('ChatEvent',(event) => {
this.messages.push(event.chat);
})
.listenForWhisper('typing', user => {
this.activeUser = user;
if(this.typingTimer) {
clearTimeout(this.typingTimer);
}
this.typingTimer = setTimeout(() => {
this.activeUser = false;
}, 1000);
})
},
methods: {
fetchMessages() {
axios.get('messages').then(response => {
this.messages = response.data;
})
},
sendMessage() {
this.messages.push({
user: this.user,
message: this.newMessage
});
axios.post('messages', {message: this.newMessage});
this.newMessage = '';
},
sendTypingEvent() {
Echo.join('chat')
.whisper('typing', this.user);
console.log(this.user.fist_name + ' is typing now')
}
}
}
</script>
"Also, this method does not work properly because in console log it shows undefined is typing..."
i assume you made a typo in your console.log, you probably meant:
this.user.first_name
Concerning your "realtime" problem, i suspect it might be because your broadcasted Event is being queued, so you might want to use the ShouldBroadcastNow instead of ShouldBroadcast

How can I put an Image for a specific user in vuejs template?

I'm actually working on profile picture for users.
I now want to put this profil picture next to their name in my chat which is working with pusher and vuejs.
My img is like this :
<img class="profil_pic" src="{{url('uploads/logo_'.Auth::user()->firstName."_".Auth::user()->lastName.".png")}}" onerror="this.src='images/user.jpg'" alt="">
I can not just past it in the file it's not working and it would be for every user, but I want it to be for the auth user. I'm kinda new to vue.js any help would be thankful.
This is my chatMessage.vue :
<template>
<ul class="chat messages" >
<li class="clearfix list-group-item" v-for="message in messages" v-bind:class="{
classForUser: (message.user.id === currentuserid),
classForNotUser: (message.user.id !== currentuserid)}" >
<div class="chat-body clearfix" >
<div class="header">
<strong class="primary-font"
v-bind:class="{
classForAuthorSameAsUser: (message.user.id === currentuserid),
classForAuthorDiffThanUser: (message.user.id !== currentuserid)
}">
{{ message.user.firstName }}
{{ message.user.lastName}}
:
</strong>
{{ message.human_readable_time}}
</div>
<p>{{ message.message }}</p>
</div>
</li>
</ul>
</template>
<script>
export default {
props: ['messages','currentuserid']
};
</script>
<style scoped>
.classForAuthorSameAsUser {
color: lightseagreen ;
}
.classForAuthorDiffThanUser {
color: black;
}
.classForUser{
width: 70%;
left: 30%;
}
.classForNotUser{
width: 70%;
}
</style>
And this is the html of my chat :
<div class="row">
<div class="col-md-12 col-md-offset-2">
<div class="col-md-12 col-md-offset-2">
<div class="panel-body panel-content" id="mess_cont" :userid="{{Auth::user()->id}}">
<chat-messages id="mess" :messages="messages" :currentuserid="{{Auth::user()->id}}"></chat-messages>
</div>
<div class="panel-footer">A
<chat-form
v-on:messagesent="addMessage"
:user="{{ Auth::user() }}">
</chat-form>
</div>
</div>
</div>
</div>
I think I have to use again the thing that help me to get the user id, but I don't really know how it works with vue.js.
Thanks for your time
And now I have this :
<template>
<ul class="chat messages" >
<li class="clearfix list-group-item" v-for="message in messages" v-bind:class="{
classForUser: (message.user.id === currentuserid),
classForNotUser: (message.user.id !== currentuserid)}" >
<div class="chat-body clearfix" >
<div class="header">
<strong class="primary-font"
v-bind:class="{
classForAuthorSameAsUser: (message.user.id === currentuserid),
classForAuthorDiffThanUser: (message.user.id !== currentuserid)
}">
<img class="profil_pic" :src="`path/to/uploads/logo_${user.firstName}_${user.lastName}.png`" alt="Auth image"/>
{{ message.user.firstName }}
{{ message.user.lastName}}
:
</strong>
{{ message.human_readable_time}}
</div>
<p>
{{ message.message }}
</p>
</div>
</li>
</ul>
</template>
<script>
export default {
props: ['messages','currentuserid'],
data() {
return {
user: null,
}
},
methods: {
this:$http.get('/api/get-auth')
.then((response) => {
this.user = response.data.auth
})
.catch((error) => {
// handle error
}),
}
};
</script>
<style scoped>
.classForAuthorSameAsUser {
color: lightseagreen ;
}
.classForAuthorDiffThanUser {
color: black;
}
.classForUser{
width: 70%;
left: 30%;
}
.classForNotUser{
width: 70%;
}
</style>
And it's not working
As I see, what you want to achieve is to take this img tag (with blade's logic):
<img class="profil_pic" src="{{url('uploads/logo_'.Auth::user()->firstName."_".Auth::user()->lastName.".png")}}" onerror="this.src='images/user.jpg'" alt="">
And put it inside your chatMessage.vue component. If that's it, you can do so like this:
1. Create a function inside a controller in order to return the authenticated user information:
public function getAuth()
{
return response()->json([
'auth' => auth()->user(),
], 200);
}
2. Create a new route in order to get that information inside your api.php file:
Route::get('/get-auth', 'YourController#getAuth');
3. Create a new param inside the data() returned object in the chatMessage.vue component:
data() {
return {
user: null,
}
},
4. Create a method inside your chatMessage.vue component with the necessary code to fetch that data:
fetchAuth() {
this.$http.get('/api/get-auth')
.then((response) => {
this.user = response.data.auth
})
.catch((error) => {
// handle error
});
}
5. Call that method inside your created() life cycle method:
created() {
this.fetchAuth();
}
6. Create the img tag like this:
<img class="profil_pic" :src="`path/to/uploads/logo_${user.firstName}_${user.lastName}.png`" alt="Auth image"/>

Resources