How to connect to Laravel Websocket with React? - reactjs

I'm building an ordering app, where I do the backend with Laravel and the front end with both ReactJS and React Native
I want real-time updates whenever a customer posts an order and whenever an order gets updated.
Currently, I managed to get WebSocket running that uses the pusher API using devmarketer his tutorial.
I'm successful in echoing the order in the console, but now I want to access the channel using my react app
And at this step is where I am facing difficulties.
As I'm unsure how to create a route that is accessible to both my apps and how to access the channel through this route.
The official laravel documentation gives an example of how to access pusher but not how to connect to it with for example an outside connection (example: my react native app)
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'rapio1',
host: 'http://backend.rapio',
authEndpoint: 'http://backend.rapio/broadcasting/auth',
auth: {
headers: {
// Authorization: `Bearer ${token}`,
Accept: 'application/json',
},
}
// encrypted: true
});
window.Echo.channel('rapio.1').listen('OrderPushed', (e) =>{
console.log(e.order)
})
So my question is how can I access a broadcasting channel on my React apps?
ADDED BACKEND EVENT
class OrderPushed implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $neworder;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Order $neworder)
{
$this->neworder = $neworder;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
//return new Channel('Rapio.'.$this->neworder->id);
return new Channel('Rapio');
}
public function broadcastWith()
{
return [
'status' => $this->neworder->status,
'user' => $this->neworder->user->id,
];
}
}

Are you using the broadcastAs() method on the backend?
It's important to know this in order to answer your question properly because if you are, the Laravel echo client assumes that the namespace is App\OrderPushed.
When using broadcastAs() you need to prefix it with a dot, to tell echo not to use the namespacing so in your example, it would be:
.listen('.OrderPushed')
Also, you don't need to do any additional setup on the backend in order for each client application to connect to the socket server unless you want to have a multi-tenancy setup whereby different backend applications will make use of the WebSockets server.
I also use wsHost and wsPort instead of just host and port, not sure if that makes a difference though

If you can access the data on the frontend by simply console.log'ing to the console you should already be most of the way there.
The way you would actually get the data into your react components depends on if you're using a state management library (such as redux) or just pure react.
Basically, you would maintain a local copy of the data on the frontend and then use the Echo events to update that data. For example, you could have a list of orders in either redux, one of your react components, or somewhere else, that you could append to and modify based on creation, update, and deletion events.
I would personally create an OrderCreated, OrderUpdated, and OrderDeleted event on the backend that would contain the given order model.
class OrdersList extends React.Component {
componentDidMount() {
this.fetchInitialDataUsingHttp();
//Set up listeners when the component is being mounted
window.Echo.channel('rapio.1').listen('OrderCreated', (e) =>{
this.addNewOrder(e.order);
}).listen('OrderUpdated', (e) =>{
this.updateOrder(e.order);
}).listen('OrderDeleted', (e) =>{
this.removeOrder(e.order);
});
}
componentWillUnmount() {
//#TODO: Disconnect echo
}
}

Related

Get single data request with id or slug on Inertiajs and react code

I am beginner in writing program using Inertiajs, i have been working using both laravel and react js both separately. but in case of Inertia using to combine the two i got stuck in getting a single data from the controller and also to send bunch to the controller from the view
it's hard to understand what's your problem exactly. but as I understand your problem is transferring data between server-side and client-side nodes of your app.
to transfer data from controller to client-side(react component) :
use Inertia\Inertia;
class UsersController extends Controller
{
public function show(User $user)
{
return Inertia::render('Users/Show', [
'user' =>$user,
]);
}
}
and you can get data(user object) in your component's props.
//...
export function Show = ({user})=>{
// access to the user object
}
//...
to transfer data from client-side to server-side use :
import { Inertia } from '#inertiajs/inertia'
Inertia.get(url, data, options)
Inertia.post(url, data, options)
Inertia.put(url, data, options)
Inertia.patch(url, data, options)
Inertia.delete(url, options)
Inertia.reload(options)
for more information see Manual visits

React native, get value from url browser

How do i get the value of url from the browser? using the react native
If you can make callbacks from the gateway website, then I recommend to use deep linking to handle flow between app and browser. Basically, your app will open the gateway website for payment, and depending on payment result, the website will make a callback to the app using its deep link. App then will listen to the link, take out necessary information and continue to proceed.
What you need to do is:
Set up deep linking in your app. You should follow the guide from official website (here) to enable it. Let pick a random URL here for linking, e.g. gatewaylistener
Set the necessary callbacks from gateway to your app. In your case, since you need to handle successful payment and failed payment, you can add 2 callbacks, e.g. gatewaylistener://success?id={paymentId} and gatewaylistener://error?id={paymentId}
Finally, you need to listen to web browser from the app. One way to do that is add listener right inside the component opening the gateway.
// setup
componentDidMount() {
Linking.getInitialURL().then((url) => {
if (url) {
this.handleOpenURL(url)
}
}).catch(err => {})
Linking.addEventListener('url', this.handleOpenURL)
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleOpenURL)
}
// open your gateway
async openGateWay = () => {
const { addNewOrderGatewayToken } = this.props
const url = `${BASEURL}${addNewOrderGatewayToken}`
const canOpen = await Linking.canOpenURL(url)
if (canOpen) {
this.props.dispatch(setPaymentStatus('checked'))
Linking.openURL(url)
}
}
// handle gateway callbacks
handleOpenURL = (url) => {
if (isSucceedPayment(url)) { // your condition
// handle success payment
} else {
// handle failure
}
}

Check for existing instance of ApolloClient to clear cache?

We have a React/GraphQL app inside of a main "app". When the user logs out we want to clear the GQL cache. However, the logout functionality exists in the wrapper app, not the React app.
When we log back in the cache is not cleared, which needs to be solved. A couple of questions on how to solve this:
1) can we check for a cache when the React app tries to create a new instance? Is there a "version" flag we can add?
const client = new ApolloClient({
link: authLink.concat(restLink),
cache: () => {
if (client.cache) {
client.cache.reset()
}
return new InMemoryCache();
}
});
2) or can we find the existing client App through any other window or global object?
3) should the react app set the client as part of local state and then compare client with useRef perhaps? If they don't match, then reset?
Open to suggestions...
The official way to clear the cache is calling resetStore in the client instance. You can get the client instance inside of any component within the Apollo context by using e.g. the useApolloClient hook
function MyLogoutButton() {
const client = useApolloClient();
const onLogout = () => {
backend.logout();
client.resetStore();
};
return <button onClick={onLogout}>Logout</button>;
}
Maybe this is doing what you want to do. You seem to be trying to create a new instance of the client, but this is not needed. The resetStore method was build for exactly this use case.

Trigger an event to private channel in react app

I want to trigger an event to pusher private channel and my server side language is laravel I reviewed a lot of resources, but I did not find a comprehensive approach which covers both the server side and the front side Finally I got this solution
in the first step :
export const SendChat = () => {
try {
var pusher = new Pusher('YOUR_APP_KEY', {
cluster: 'ap2',
forceTLS: true,
authTransport: 'jsonp',
authEndpoint: `${baseUrl}pusher/auth`,
});
var channel = pusher.subscribe('private-channel');
channel.bind('pusher:subscription_succeeded', function() {
var triggered = channel.trigger('client-EVENT_NAME', { 'message': 'Hi ....' });
console.log(triggered)
});
} catch (error) {
console.error(error);
}
}
and call it somewhere
<Button onClick={this.props.SendChat} waves='light' >Send</Button>
you must Enable client events in pusher account setting
login to your pusher account -> select the channel ->App Settings -> select Enable client events -> update
add your app key, channel name and event name after that we need authorization in server side this is sample laravel code first add this route in web.php
Route::get('pusher/auth', 'PusherController#pusherAuth');
make PusherController.php like this :
public function pusherAuth()
{
$user = auth()->user();
if ($user) {
$pusher = new Pusher('auth_key', 'secret', 'app_id');
$auth= $pusher->socket_auth(Input::get('channel_name'), Input::get('socket_id'));
$callback = str_replace('\\', '', $_GET['callback']);
header('Content-Type: application/javascript');
echo($callback . '(' . $auth . ');');
return;
}else {
header('', true, 403);
echo "Forbidden";
return;
}
}
test it you should see something like this
Pusher : State changed : connecting -> connected with new socket ID 3953.****556
Pusher : Event sent : {"event":"pusher:subscribe","data":{"auth":"83045ed1350e63c912f5:328fb78165d01f7d6ef3bb6d4a30e07c9c0ad0283751fc2c34d484d4fd744be2","channel":"private-chat"}}
Pusher : Event sent : {"event":"client-MessageSent","data":{"message":"Hi ...."},"channel":"private-chat"}
true
It doesn't matter much which client-side language you are using. Angular, Vue, React they all are JS framework and libraries. And, you can consider using a generic JS code which you can place in all 3 apps.
Let me try to give you a detailed answer I can give as per my knowledge.
In order to get started, you should first complete try to complete Chat scenario without pusher. i.e: user should be able to send a message from front-end via the API and it should be stored inside the database.
Once you have done this it is very easy to include pusher in the flow. ( In simple words, you'll have to broadcast an event and that'll inform the Socket Server to broadcast a message to all/other user(s) on the channel )
For Pusher Authentication, you don't need to explicitly create a route and a method. Once you have uncommented BroadcastServiceProvider inside config/app.php. You can run:
php artisan route:list
and, you'll see a route for broadcast broadcasting/auth.
You can use this route to authenticate. Although, you can make few changes and prepend /api before this.
Go into BroadcastServiceProvider.php and replace your boot method with:
public function boot()
{
Broadcast::routes(
[
'prefix' => 'api',
'as' => 'api.broadcasting.auth',
'middleware' => ['auth:sanctum'],
]
);
require base_path('routes/channels.php');
}
I assume you're using Laravel Sanctum for Authentication. If not you need to change the authentication middleware to your provider.
Once done, you can authenticate from frontend using this auth route. So, what I have done is created a service in ReactJS and in the constructor I have created a Pusher instance :
this.pusher = new Pusher(PUSHER_APP_KEY, {
authEndpoint: 'http:localhost:8000/api/broadcasting/auth',
cluster: PUSHER_CLUSTER,
useTLS: true,
auth: {
headers: {
Authorization: 'Bearer ' + authHeader
}
}
});
You only need to instantiate your Pusher once and use this instance throughout the app. So, that's why I have created a service class for Pusher.
If you want things to be simple for now you need to execute this code on the page where you will use pusher. Once the Page load, you need to call this code. So, you'll do:
let pusher = null;
useEffect(() => {
pusher = new Pusher(PUSHER_APP_KEY, {
authEndpoint: 'http:localhost:8000/api/broadcasting/auth',
cluster: PUSHER_CLUSTER,
useTLS: true,
auth: {
headers: {
Authorization: 'Bearer ' + authHeader
}
}
});
}, []);
So, this way we have an instance of Pusher in our functional component or page.
Now, we need to subscribe to channel.
Using this instance of pusher we can subscribe to channels. If you have followed the useEffect approach on the same page then, right after getting the instance you can subscribe to channels and bind to events using this code:
const channel = pusher.subscribe('private-chat.' + channelName)
And, to bind to an event you can do:
channel.bind('event.name', function(data) {
console.log(data);
});
Make sure to replace "channelName" and "event.name" with your channel and event name respectively.
Now you'll be able to listen to your event once broadcasted from the backend.
So, you'll do something like this from the backend. You'll have a method that will store the message inside the database so, let's say that code is:
public function sendMessage (Request $request){
//.... Rest of the logic
$user = $request->user();
// Store the message
$chatMessage = $chat->messages()->create([
'message' => $message,
'sender_id' => $user->id
]);
broadcast(new NewMessage($user, $chatMessage))->toOthers();
//... Rest of the logic
}
This broadcast message will send this message to other user in the chat.
I hope this answer gives you a good idea and direction.
For work with WebSockets via Pusher on Laravel, I recommended using the package Laravel Echo for React part. And on the backend side in config/broadcasting.php setup configuration for Pusher.
See more detail on official documentation Laravel how to use Pusher on the backend side and frontend side.
https://laravel.com/docs/8.x/broadcasting#pusher-channels

ReactJS where do I store the API URI?

Where would I store the API URI centrally in a ReactJS Application? The URI only changes between environments and should be easily configurable (i.e. through environment variables).
I have looked into this package and into the new Context API, but am unsure it's the best way to achieve this. I have also looked into dotenv, but I don't like that I would have to use process.env.REACT_APP_SERVICE_URI in every component that wants to access the API. What is the usual approach?
I am not using Redux.
I don't think you need an external dependency to do that.
I usually create simple module called api-client.js, which is responsible for calls to external API and defining endpoints.
In your case you might have:
import axios from 'axios' // some http client lib
const endpoint = process.env.REACT_APP_SERVICE_URI? process.env.REACT_APP_SERVICE_URI : 'https://foo.api.net/'
export default {
getAllProducts () {
return axios.get(endpoint + 'products').then(response => {
log.debug(`api client fetched ${response.data.length} items`)
return response.data
}).catch(err => {
log.error(err.message)
throw err
})
}
},
getProductById (id) {
...
},
}
You read process.env.REACT_APP_SERVICE_URI only once.
I like to put this module inside api directory (and any other API related stuff).

Resources