Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

kendrick's avatar

TypeError: null is not an object

I am currently trying to listen to a store method which will trigger a BookingEvent to broadcast that a simple booking has been created, which will then push the Booking to a loop within my component. I am getting all Bookings through a Resource, which will list the current bookings.

View:

<booking :bookings="{{ $bookings->toJson() }}" />

Component:

<template>
    <div> 
        <div v-for="booking in bookings"> 
          <div>
              <p>{{booking.user.name}}</p>      
          </div>   
        </div> 
    </div>
</template>

<script>
export default {
    props: ['bookings'],

    data() {
        return {
            booking: null, // error
        }
    },

    methods:{ 
         getNotes() {
             axios.post("/getBookings").then(res => {
                this.booking = res.data.data; 
            });
         }, 

         pushToBookings(booking) {
          this.bookings.push({
            booking: booking,  
          });
        },
    },

    created() {

       this.getBookings();
       window.Echo.private(`Bookings.${this.booking.id}`)
       .listen("BookingEvent", (e) => {
          this.booking = e.booking; 
          this.pushToBookings(this.booking);

       });
    },
};
</script>

BookingEvent

class BookingEvent 
{

  public $booking;

  public function __construct(Booking $booking)
  {
    $this->booking = $booking;
  }
 
 public function broadcastOn()
    {
        return new PrivateChannel("Bookings.{$this->booking->id}");
    }
}


Controller

public function store(Booking $booking, Request $request)
{
        $user = Auth::user(); 
        $booking = $user->booking()->create([...]); 
    
        broadcast(new BookingEvent($booking));

        return response($user->id, 200);
} 

The loop is yet not User related, just a real-time data aggregation on the booking model, and I would like to push the new Booking to the loop whenever the Event is triggered.

Currently I get an error of "TypeError: null is not an object (evaluating 'this.booking.id')".

0 likes
57 replies
tykus's avatar

Well, whenever the component is created, the booking property in the component's datais null. How is that booking intended to get a value (object) that would have an id?

bugsysha's avatar

Move from created

       window.Echo.private(`Bookings.${this.booking.id}`)
       .listen("BookingEvent", (e) => {
          this.booking = e.booking; 
          this.pushToBookings(this.booking);

       });

to watcher

watch: {
  booking() {
    window.Echo.private(`Bookings.${this.booking.id}`)
       .listen("BookingEvent", (e) => {
          this.booking = e.booking; 
          this.pushToBookings(this.booking);
    });
  }
}
kendrick's avatar

How is that booking intended to get a value (object) that would have an id?

Through the broadcast(new BookingEvent($booking)); ?

kendrick's avatar

to watcher

Info: (The Event will be triggered). After moving from created() to watch: the error is gone, but they won't be pushed in real time to the loop.

tykus's avatar

The issue is the initial value of this.booking, it being null, there is no id property for the channel name to be built Bookings.${this.booking.id}. My question concerned where this booking object should come from; you are not passing a prop except the collection of bookings.

bugsysha's avatar

Then extract a booking component and put that Echo logic back in created method.

kendrick's avatar

you are not passing a prop except the collection of bookings.

Basically out of nowhere. I tried to show all the bookings through the bookings props and then push every newly created booking to the loop. Is that possible without adding this.booking.id to the channel?

kendrick's avatar

extract a booking component

What do you mean by extract? A second component?

bugsysha's avatar

Yeah, a nested component which will just display that booking.

kendrick's avatar

And after refresh it will be sorted within the bookings loop?

tykus's avatar

In a Vue data-driven approach, you would break this problem into two components, first a BookingList which accepts the collection of Bookings as a prop and iterates over them to display (usually) the individual bookings:

// some Blade view
<BookingList :bookings="{{json_encode($bookling}}" />

A BookingList component ,might look like this:

<template>
    <div> 
        <div v-for="booking in bookings"> 
            <Booking :booking="booking" />
        </div> 
    </div>
</template>
<script>
    props: ['bookings'],
</script>

Inside the loop, we would have an individual Booking compomnent with the data and behaviours of an individual Booking:

<template>
    <div>
        <p>{{booking.user.name}}</p>      
    </div>   
</template>

<script>
    export default {
        name: 'Booking'
        props: ['booking'],
        methods: {
            // I don't know if you need to fetch a remote resource for each Booking; it is not efficient when you could have that data in the `$bookings` collection instead
        },
            created() {
            this.getBookings();
            window.Echo.private(`Bookings.${this.booking.id}`)
                .listen("BookingEvent", (e) => {
                        this.booking = e.booking; 
                        this.pushToBookings(this.booking);
                });
        },
    }
</script>
bugsysha's avatar

And after refresh it will be sorted within the bookings loop?

Yes if you do not make some mistake.

kendrick's avatar

The second component is created. When I refresh the page, the Bookings will be shown shortly (less than a sec), but then hidden. Currently I am seeing a broadcasting/auth error and this one:

Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "booking"

BookingListComponent

<template>
    <div>  
        <div v-for="booking in bookings">  
          <booking :booking="booking"/>  
        </div> 
    </div>
</template>
 
<script>
    export default {
        props: ['bookings'],
    };
</script>

BookingComponent

<template>
    <div>  
        <div> 
          <div>
              <p>{{booking.user.name}}</p>      
          </div>   
        </div> 
    </div>
</template>

<script>
 
export default { 

    name: 'booking',
    props: ['booking'],


    methods:{ 
         getBookings() {
             axios.post("/getBookings").then(res => {
                this.booking = res.data; 
            });
         }, 

         pushToBookings(booking) {
          this.bookings.push({
            booking: booking,  
          });
        },
    },
     

    created() {
      this.getBookings();
       window.Echo.private(`Bookings.${this.booking.id}`)
       .listen("BookingEvent", (e) => {
          this.booking = e.booking; 
          this.pushToBookings(this.booking);

       });
    },
 
};
</script>

Do I need to import the BookingComponent to the first one?

import BookingComponent from './BookingComponent';
tykus's avatar

This is why I mentioned that the fetch might be unnecessary. I lifted the code from the original example, without intending that it would work directly. IMO the needgetBookings and pushToBookings is not there... I don't know exactly what you are intending to do in the application, so I don't want to start coding the wrong approach...

Why do you believe you need to make a further XHR request for every booking in bookings?

bugsysha's avatar

This method should live on Bookings component.

         getBookings() {
             axios.post("/getBookings").then(res => {
                this.booking = res.data; // not sure what you are doing here since the route implies that you are fetching all bookings but you are assigning it to a single booking instance variable. also no need to assign it if you passed it as a prop
            });
         }, 

Do I need to import the BookingComponent to the first one?

If you did not register it as a global component then you have to import it. If you did register it as a global component do you do not need to import it.

kendrick's avatar

Ok, I thought that it would be necessary to push the booking to the bookings loop. Now I simplified it to:

<template>
    <div>  
        <div> 
          <div>
              <p>{{booking.user.name}}</p>      
          </div>   
        </div> 
    </div>
</template>

<script>
 
export default { 

    name: 'booking',
    props: ['booking'],

    created() { 
       window.Echo.private(`Bookings.${this.booking.id}`)
       .listen("BookingEvent", (e) => {
          this.booking = e.booking;  
       });
    },
 
};
</script>

But I am still getting a 403 /broadcasting/auth. Is there a need for a PrivateChannel?

BookingEvent


public function broadcastOn()
    {
        return new PrivateChannel("Bookings.{$this->booking->id}");
    }

Channel

Broadcast::channel('Bookings.{id}', function ($booking, $id) {
    return $booking->id === (int) $id;
});

bugsysha's avatar

Shouldn't it be something like

Broadcast::channel('bookings.{bookingId}', function (User $user, int $bookingId) {
    return $user->id === Booking::findOrFail($bookingId)->user_id;
});
kendrick's avatar

Yes, later, but the loop is yet not User related, just a real-time data aggregation on the booking model, and I wanted to push the new Booking to the loop whenever the Event is triggered, to see how such a use-case works at the core.

kendrick's avatar

For the start, I wanted to broadcast all Bookings to one place, which doesn't depend on a user_id, e.g. on the homepage, to see the workflow with the component and props, and aggregate all bookings in real-time.

Sorry, again, for the confusion.

bugsysha's avatar

You are saying that something like this does not work?

Broadcast::channel('bookings.all', function () {
    return true;
});
kendrick's avatar

It will push the booking to the view. But there is a weird behavior, which overrides all bookings (prop) to this newly created booking. After refresh it will have all bookings with the correct information, sorted.

Error: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. booking

bugsysha's avatar

On your booking component you have a prop called booking. Stop modifying that prop!!! 🤞🏻

kendrick's avatar

Where am I modifying the prop? What means modifying in our case?

bugsysha's avatar

Doing anything to that object like

this.booking.something = 123;
kendrick's avatar

BookingListComponent

<template>
    <div>  
        <div v-for="booking in bookings">  
          <booking :booking="booking"/>  
        </div> 
    </div>
</template>
 
<script>
    export default {
        props: ['bookings'],
    };
</script>

BookingComponent

<template>
    <div>  
        <div> 
          <div>
              <p>{{booking.user.name}}</p>      
          </div>   
        </div> 
    </div>
</template>

<script>
 
export default { 

    name: 'booking',
    props: ['booking'],

    created() { 
       window.Echo.private(`Bookings.all`)
       .listen("BookingEvent", (e) => {
          this.booking = e.booking;   // Is this a modification ?
       });
    },
 
};
</script>

bugsysha's avatar

this.booking = e.booking; // Is this a modification?

Yes.

kendrick's avatar

What would be the workaround here? Sorry for the delayed answer.

bugsysha's avatar

You have to explain what you are trying to do.

kendrick's avatar

Push the new booking to the bookings loop, only once, at the top. Currently, as I mentioned with this.booking = e.booking; it will override all bookings in real-time as the new one, with the error of avoiding mutations. After a refresh the new one is at the top.

Next

Please or to participate in this conversation.