I'm working on a chat system, each message belongs to a user, who sent it . In the database of messages I store just the user id, and I have a problem with fetching the users names, and replace it in the message.
This is the way I tried to fetch the username, by creating a method which takes the users id, and after fetching from api returns the users name, but the return doesn't work :
<div v-for="message in messages" :key="message.id" class="message">
#{{ getUserDetails(message.user_id) }}
<p>#{{ message.message }}</p>
</div>
methods: {
getUserDetails(id) {
fetch('/api/user/' + id)
.then(res => res.json())
.then(res => {
return res.name;
})
}
},
I hope you understand my problem, sorry for my bad english, is not my native language ...
The problem occurs because you are actually not return anything in getUserDetails function.
You are trying to return from a then() callback. But when you return a value from that, the next then() is called with that value. The parent function getUserDetails still returns nothing.
To solve your problem, I think you should use another object to store the usernames. This way, you would not overwhelm the server by doing a request to get the username for every message.
Something like this should work:
<div v-for="message in messages" :key="message.id" class="message">
#{{ getUsername(message.user_id) }}
<p>#{{ message.message }}</p>
</div>
data () {
return {
listUsername: {}
}
},
methods: {
getUsername(id) {
if (!this.listUsername[id]) {
this.getUserDetails(id)
return 'Unknown'
}
return this.listUsername[id]
},
getUserDetails(id) {
fetch('/api/user/' + id)
.then(res => res.json())
.then(res => {
this.$set(this.listUsername, id, res.name)
})
}
}
Related
I have a problem with Laravel/Inertia which gets me the error message :
Cannot read properties of null (reading 'allowRecurse')
There is a form, i post the form to a controller and after saving the data i tried to redirect to the main page. I tried several ways, return Redirect::route('home'); or return redirect()->back(); even other ways i found searching for a solution. They all give me the same error message.
The redirect()->back() function works and brings me back to my form, but when i try to redirect there i get that same message.
I deleted everything of what i think is not essential, so the form and controller now don't do anything anymore, but still i get that error message.
I can't find anything about this error on the internet. What mistake am I overlooking?
Edit: I found something... Somewhere in page that i am going to (that home.vue page) i have a link with the same URL as where i came from. Apparently Vue or Laravel doesn't approve. But i need that. Can i set the value of AllowRecurse to True somewhere? Or is that a bad idea?
My vue-file:
<template>
<div>
<form #submit.prevent="submit">
<div class="flex justify-center mt-4">
<app-button class="w-1/2 justify-center" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Insturen
</app-button>
</div>
</form>
</div>
</template>
<script>
import AppButton from '#/Components/Button.vue'
export default {
components: {
AppButton,
},
props: {
game_id: String,
code: String,
},
data() {
return {
form: this.$inertia.form({
name: '',
email: '',
})
}
},
setup() {
},
methods: {
submit() {
this.form.post(route('toto_store',{'game_id': this.game_id, 'code': this.code}), {
preserveScroll: true,
resetOnSuccess: false,
onSuccess: () => { this.$inertia.visit(route('home'))} ,
});
}
}
}
</script>
My controller:
namespace App\Http\Controllers;
use Inertia\Inertia;
use App\Models\Guess;
use Illuminate\Http\Request;
class TotoController extends Controller
{
public function store(Request $request, $id, $code)
{
return redirect()->back()->with("success", "Opgeslagen");
}
}
My webroutes:
Route::get('/', [HomeController::class, 'show'])->name('home');
Route::get('/guesses/{game_id?}', [GuessController::class, 'index'])->name('guesses');
Route::get('/toto/{game_id}/{code}', [TotoController::class, 'create'])->name('toto_create');
Route::post('/toto/{game_id}/{code}', [TotoController::class, 'store'])->name('toto_store');
Route::get('/qr-code/{id}', [QRController::class, 'show'])->name('qr-code');
The error:
Any ideas?
I want to test the response in the console log. I am using the google inspect tool. I can't see any response in Network>>XHR. But I have seen that "Form submission canceled because the form is not connected" in console. The sample screen inspect tool screen I can't trace the problem actually where. I am following a course video about laravel and vue. Thanks in advance for your time.
Form
<form v-if="editing" #submit.prevent="update">
<div class="form-group">
<textarea rows="10" v-model="body" class="form-control"></textarea>
</div>
<button #click="editing = false">Update</button>
<button #click="editing = false">Cancel</button>
</form>
in Controller
if ($request->expectsJson()) {
return response()->json([
'message' => 'Answer updated!',
'body_html'
]);
}
Vue.JS
<script>
export default {
props: ['answer'],
data () {
return {
editing: false,
body: this.answer.body,
bodyHtml: this.answer.body_html,
id: this.answer.id,
questionId: this.answer.question_id
}
},
methods: {
update () {
axios.patch(`/questions/${this.questionId}/answers/${this.id}`, {
body: this.body
})
.then(res => {
console.log(res);
this.editing = false;
})
.catch(err => {
console.log("something went wrong");
});
}
}
}
</script>
The form is by default hidden. It appears only when clicking on the Edit button. The only problem is to submit the form. ErrorMessage: Form submission canceled because the form is not connected
You have v-if="editing" in your form set to false. It should be true, because form has to exist on submit. You are removing your form from DOM. Also move this.editing to finally() block in axios call.
I have an application with one subscription already using subscribeToMore
Query Component:
<Query query={GET_MESSAGES}>
{({ data, subscribeToMore }) => {
return (
<MessageList
messages={data.allMessages}
subscribeToMore={subscribeToMore}/>
);
}}
</Query>
That query loads a list of messages where as far as I know we attach the subscriber in the ComponentDidMount so whenever I add a new element into my list inside the Query, my subscription will listen to the subscription and do whatever I want (in this case add the new message into my current list of messages).
List of Messages Component:
export default class MessageList extends React.Component {
componentDidMount() {
this.props.subscribeToMore({
document: MESSAGE_CREATED,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
return {
allMessages: [
subscriptionData.data.messageCreated,
...prev.allMessages
],
};
},
});
}
render() {
return (
<div>
{this.props.messages.map(message => (
<MessageElement key={message.id}
message={message}/>
))}
</div>
);
}}
I would like to add another subscription so if I edit my message the information is updated in real time. In order to achieve that I have created the following component using Subscription
Message component (where I would like to add another subscription based on an updated message)
export default class MessageElement extends Component{
componentDidMount() {
this.messageUpdatedSubscription();
}
messageUpdatedSubscription = () => (
<Subscription
subscription={MESSAGE_UPDATED}>
{({ data: { messageUpdated } }) => (
console.log('Do smthg???',messageUpdated)
)}
</Subscription>
);
render(){
return(
<Mutation mutation={UPDATE_MESSAGE} key={this.props.message.id}>
{updateMessage => (
<div>
<div>{this.props.message.text}</div>
<div>
<Star active={this.props.message.isFavorite}
onClick={() => { updateMessage({ variables: {
id: this.props.message.id,
text:this.props.message.text,
isFavorite:!this.props.message.isFavorite } });
}}/>
</div>
</div>
)}
</Mutation>);
}}
My subscriptions on the server are working as I already have the subscription for MESSAGE_CREATED on the Query working correctly and I have tested that on the server my subscription for the MESSAGE_UPDATED is triggered. However, I cannot figure out why the UI is not displaying or console.log anything as if it is not listening to the subscription.
Can I achieve this with a subscription component or with a subscribeToMore?
Thanks in advance
The subscription component cannot be initiated in ComponentDidMount. It has to reside in a render() lifecycle method. It's parameter can be used in ComponentDidMount, but not the component.
I can think of 3 possible solutions:
1) You could try to put the Subscription method in your render method. You would just need to nest this inside or outside of your Mutation component.
2) You could initiate the Subscription Component in this component's parent and pass its parameter (messageUpdate) down to the component MessageElement. You could then use messageUpdate off of props in ComponentDidMount.
3) You could use Apollo's higher order component. You could then access messageUpdate in props. (Disclaimer - I have not tried this before).
I hope this helps!
Based on the suggestion of #cory-mcaboy I nested my subscription into the mutation.
I also found out that as I had a list of messages and I just wanted to trigger the subscription based on the message I am updating and not the entire list; I had to modified my subscription on the backend and on the front end in the following way:
Server schema
const typeDefs = gql` type Subscription {
messageUpdated(id: Int!): Message}`;
Server resolver
Subscription: {
messageUpdated: {
subscribe: withFilter(
() => pubsub.asyncIterator([MESSAGE_UPDATED]),
(payload, variables) => {
return payload.messageUpdated.id === variables.id;
},
),
},
}
Client component
const MESSAGE_UPDATED = gql` subscription messageUpdated($id: Int!){
messageUpdated(id:$id)
{
id
text
isFavorite
}}`;
export default class MessageElement extends Component{
render(){
return(<Mutation mutation={UPDATE_MESSAGE} key={this.props.message.id}>
{updateMessage => (
<div>
<div>{this.props.message.text}</div>
<Subscription subscription={MESSAGE_UPDATED}
variables={{ id: this.props.message.id }}>
{() => {
return <Star active={this.props.message.isFavorite}
onClick={() => { updateMessage({ variables: {
id: this.props.message.id,
text: this.props.message.text,
isFavorite: !this.props.message.isFavorite } });}}/>
}}
</Subscription>
</div>
)}
</Mutation>
);
}}
You can see the code in the following repo: back-end itr-apollo-server, front-end itr-apollo-client
I saw a lot of users struggling to use useSubscription in Class Component as Apollo deprecated the Subscription component in the newer version. So, I decided to create quick gist of how to use it.
https://gist.github.com/syedadeel2/a9ec6ff0d9efade2b83d027f32ce21dc
I'm using laravel 5.6 as the api for the backend and vuejs for the frontend spa application. And https://github.com/nicolaslopezj/searchable package for search which worked well in my previous projects where i used blade templating engine.
I'm new to vuejs and im learning how to do things and im stuck at a point where im confused how to use laravel search on the frontend.
What i did previously in other projects where i used blade for the frontend
In my controller
public function index(Request $request)
{
$start_search = microtime('s');
$searchterm = $request->input('search');
if($request->has('search')){
$results = Web::search($searchterm)->paginate(10);
} else {
$results = Web::latest()->paginate(10);
}
$stop_search = microtime('s');
$time_search = ($stop_search - $start_search);
return view('search.index', compact('results', 'searchterm', 'time_search'));
}
And in my view
<small>{{$results->total()}} results found for "<em class="b-b b-warning">{{$searchterm}}</em>" in {{$time_search}} seconds</small>
#forelse ($results as $result)
<div class="mb-3">
{{$result->meta_title}} <span class="badge badge-pill primary">{{$result->rank}}</span>
<span class="text-success clear h-1x">{{$result->url}}</span>
<span class="h-2x">{{$result->meta_description}}</span>
</div>
#empty
No Results Found
#endforelse
<div class="pt-2 pagination-sm">
{{ $results->links('vendor.pagination.bootstrap-4') }}
</div>
This code worked well where i was able to search and display results properly along with search time.
Now i want to do the same with laravel api and vuejs frontend. So this is what i tried
public function index(Request $request)
{
$start_search = microtime('s');
$searchterm = $request->input('search');
if($request->has('search')){
$results = Web::search($searchterm)->paginate(10);
} else {
$results = Web::latest()->paginate(10);
}
$stop_search = microtime('s');
$time_search = ($stop_search - $start_search);
return WebResource::collection($results);
}
I created a collection resource for the same.
Question 1. My question related to controller code, how to return $searchterm and $time_search so that i can use them on the frontend.
In my vue component, i tried with my learning till now
<template>
other code..
<div class="mb-3" v-for="result in results" :key="result.results">
<router-link :to="result.url" class="text-primary clear h-1x"> {{ result.meta_title }} </router-link>
<span class="h-2x">{{ result.meta_description }}</span>
</div>
</template>
<script>
import axios from 'axios'
import { mapGetters } from 'vuex'
export default {
layout: 'main',
computed: mapGetters({
locale: 'lang/locale',
authenticated: 'auth/check'
}),
metaInfo () {
return { title: this.$t('searchpagetitle') }
},
data: function () {
return {
results: [],
title: window.config.appName
}
},
watch: {
locale: function (newLocale, oldLocale) {
this.getResults()
}
},
mounted() {
console.log(this.locale)
this.getResults ()
},
methods: {
getResults() {
var app = this;
axios.get('/api/web')
.then(response => {
// JSON responses are automatically parsed.
this.results = response.data.data
})
.catch(e => {
this.errors.push(e)
})
}
}
}
</script>
Question 2: My second question related to vuejs, how to create a search form and render results according to the search and with proper search url ending with ?searchterm=searchkeywords in my case.
When i used blade i used the normal html form with action url so that it rendered results. Also i used $searchterm to use the same search terms to search in other routes like images, videos.
I just used {{ url('/images?searchterm='.$searchterm) }} in the href so that when user clicks on it, it renders results of all images with the same keyword just like google. And also used placeholder as "enter your search" and value as "{{$searchterm}}" so that the search term stayed in the search form too.
I want to know how to do all the same in vuejs.
Thank you
It is better to append the search keyword in request URL of API in frontend. So your request url will be like
axios.get('/api/web?searchterm='+this.searchterm)
And in your Laravel controller, you can get this value by using Input::get() , so your code to get searchterm will be
$searchterm = Input::get('searchterm');
And then you can fetch your data on basis of $searchterm variable.
In my TaskList.vue I have the code below:
<template>
<div>
<ul>
<li v-for="task in tasks" v-text="task"></li>
</ul>
<input type="text" v-model="newTask" #blur="addTask">
</div>
</template>
<script>
export default {
data(){
return{
tasks: [],
newTask: ''
}
},
created(){
axios.get('tasks')
.then(
response => (this.tasks = response.data)
);
Echo.channel('task').listen('TaskCreated', (data) => {
this.tasks.push(data.task.body);
});
},
methods:{
addTask(){
axios.post('tasks', { body: this.newTask })
this.tasks.push(this.newTask);
this.newTask = '';
}
}
}
</script>
When I hit the axios.post('tasks') end point, I got duplicate result in my current tab that i input the value and the another tab got only 1 value which is correct.
To avoid this, I tried to use
broadcast(new TaskCreated($task))->toOthers();
OR
I put $this->dontBroadcastToCurrentUser() in the construct of my TaskCreated.php
However, both methods are not working. Any idea why?
The image below is from network tab of mine. One of it is pending, is that what caused the problem?
https://ibb.co/jpnsnx (sorry I couldn't post image as it needs more reputation)
I solved this issue on my Laravel Spark project by manually sending the X-Socket-Id with the axios post.
The documentation says the header is added automatically if you're using vue and axios, but for me (because of spark?) it was not.
Below is an example to show how I manually added the X-Socket-Id header to an axios post using the active socketId from Laravel Echo:
axios({
method: 'post',
url: '/api/post/' + this.post.id + '/comment',
data: {
body: this.body
},
headers: {
"X-Socket-Id": Echo.socketId(),
}
})
Laravel looks for the header X-Socket-ID when using $this->dontBroadcastToCurrentUser(); Through this ID, Laravel identifies which user (current user) to exclude from the event.
You can add an interceptor for requests in which you can add the id of your socket instance to the headers of each of your requests:
/**
* Register an Axios HTTP interceptor to add the X-Socket-ID header.
*/
Axios.interceptors.request.use((config) => {
config.headers['X-Socket-ID'] = window.Echo.socketId() // Echo instance
// the function socketId () returns the id of the socket connection
return config
})
window.axios.defaults.headers.common['X-Socket-Id'] = window.Echo.socketId();
this one work for me..!!