How to have Meteor.Collection without MongoDB? - reactjs

While developing a small project, there is absolutely no need for a MongoDB persistence layer, but I would like the benefit of publishing and subscribing for client synchronization.
From a related question, I implemented a very crude interface (untested) :
// server
const _cache = new Map();
const rooms = {
registerObserver: (roomHash, observer) => {
if (!_cache.has(roomHash)) {
_cache.add(roomHash, { messages:[], observers:[] );
}
const room = _cache.get(roomHash);
room.observers.add(observer);
observer.added("rooms", roomHash, { messages:room.messages });
observer.onStop(() => room.observers.delete(observer));
observer.ready();
}
}
Meteor.publish('chatroom', function (roomHash) {
check(roomHash, String);
rooms.registerObserver(roomHash, this);
});
Meteor.methods({
pushMessage: function (roomHash, message) {
check(roomHash, String);
check(message, String);
const room = _cache.get(roomHash);
room.messages.push(message);
room.observers.forEach(observer =>
observer.changed("rooms", roomHash, { messages:room.messags })
);
}
});
But, now, I need to fetch the messages from the given room, so I added :
// client, React hook
const useChatMessages = roomHash => {
const loading = useTracker(() => {
const handle = Meteor.subscribe("chatroom", roomHash);
return !handle.ready();
}, [orderHash]);
const pushMessage = useCallback(message => {
Meteor.call('pushMessage', roomHash, message);
}, [roomHash]);
const messages = []; // .... ???
return { loading, messages, pushMessage };
};
I have no idea how to fetch the messages. Since I removed the MongoDB dependencies, I do not have access to Mongo.Colllection, and it seems like Meteor.Collection is also unavailable (i.e. Meteor.Collection === undefined)
So, I publish, and also subscribe, but how do I fetch the published messages?
(Note: the above code compiles, but it is mostly untested as explained in the question.)

Related

store data in firestore when Browser Tab is closed or the route is changed (react JS)

const handleDraftContracts = async () => {
console.log('/bruhhhhhhandleDraftContract');
const paragraphRef: string | any = document.getElementById('contract');
const contractDetails = {
contractName: 'House Rental',
states: {
amount: amount,
},
content: paragraphRef?.textContent,
};
await makeDraftContract(contractDetails);
};
useEffect(() => {
console.log('///////I am hreeeee');
window.addEventListener('onbeforeunload', (env) => {
handleDraftContracts();
});
return () => {
console.log('///////removing');
window.removeEventListener('onbeforeunload', handleDraftContracts);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
firestore.js
// make Draft Contracts
export async function makeDraftContract(contractDetails: object | any) {
try {
console.log("making a draft contract", contractDetails);
const draftContractRef: any = collection(db,"makeDraftContracts");
let contract = await addDoc(draftContractRef, contractDetails);
console.log("./////////makeDraftContract", contract);
} catch (error) {
console.log('////errror in contract Hanlder', error);
}
}
I want to call my handleDraftContracts method whenever user closes the tab or changes the route. I am using onbeforeunload event. The handleDraftContracts is getting called but the tab unloads before Firestore could update the collection. How can I get around this that as the user closes the tab or move to a new route, my firestore method get executed first then the tab gets unloaded ?
Try with Beacon api
https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API
as 'onbeforeunload' cannot make sure you request to server has been made and requests can slow down the browser
componentWillUnmount is like that one, cannot to make long running script.

Saving an ID value from an API to a User with GraphQL

I'm working on a video game website where a user can save a game to a list. How this is supposed to work is when the user clicks "Complete Game", the ID of the game is saved to a state that holds the value. The value is then passed into the mutation, then the mutation runs, saving the ID of the game to the users list of completed games. However, all I'm seeing in the console is this:
"GraphQLError: Variable \"$addGame\" got invalid value { gameId: 740, name: \"Halo: Combat Evolved\",
The above error continues, listing the entirety of the API response, instead of just the gameId.
I was able to successfully add the game to the list in the explorer with the following mutation:
mutation completeGame($addGame: AddNewGame!) {
completeGame(addGame: $addGame) {
_id
completedGameCount
completedGames {
gameId
}
}
}
with the following variable:
{
"addGame": {"gameId": 740}
}
How can I trim down what is being passed into the mutation to just be the gameId?
Below is the entirety of the page, except the return statement at the bottom.
const [selectedGame, setSelectedGame] = useState([]);
const [savedGameIds, setSavedGameIds] = useState(getSavedGameIds());
const [completeGame, { error }] = useMutation(COMPLETE_GAME);
const { id: gameId } = useParams();
useEffect(() => {
return () => saveGameIds(savedGameIds);
});
useEffect(() => {
async function getGameId(gameId) {
const response = await getSpecificGame(gameId);
if (!response.ok) {
throw new Error('Something went wrong...');
}
const result = await response.json();
const gameData = result.map((game) => ({
gameId: game.id,
name: game.name,
cover: game.cover,
summary: game.summary,
platforms: game.platforms,
platformId: game.platforms,
genres: game.genres,
genreId: game.genres,
}));
setSelectedGame(gameData);
}
getGameId(gameId);
}, [])
const handleCompleteGame = async (gameId) => {
const gameToComplete = selectedGame.find((game) => game.gameId === gameId);
const token = Auth.loggedIn() ? Auth.getToken() : null;
if (!token) {
return false;
}
try {
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
console.log(data);
setSavedGameIds([...savedGameIds, gameToComplete]);
} catch (err) {
console.error(err);
}
};
With the mutation working in the explorer when I'm able to explicitly define the variable, I am led to believe that the issue is not with the resolver or the typedef, so I'm going to omit those from this post because I don't want it to get too long.
However, I'd be happy to attach any extra code (resolver, typeDef, getSavedGameIds function, etc) if it would allow anyone to assist. The issue (I think) lies in getting my response to match the syntax I used in the explorer, which means trimming down everything except the gameId.
I specifically am extremely suspicious of this line
const gameToComplete = selectedGame.find((game) => game.gameId === gameId)
but I have fiddled around with that for awhile to no avail.
Thank you to anyone who is able to help!
It sounds like you're trying to pass more into your mutation then your schema is defined to allow. In this part:
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
You're spreading gameToComplete here which means everything in the gameToComplete object is going to be sent as a variable. If your schema is setup to just expect gameId to be passed in, but your error message is showing that name is also being passed in, you just need to adjust your variables to exclude everything you can't accept. Try:
const { data } = await completeGame({
variables: { addGame: { gameId } },
});

ReactJS: STOMP subscription to multiple topics

My React code creates a WebSocket connection to our company's ActiveMQ 5.15.5 server, and then subscribes to the following two topics: salary and decoding. The problem is that the code is only able to subscribe to one of the topics. It cannot subscribe to both.
const client = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj6.stomp');
const headers = { id: 'username' };
client.debug = null;
client.connect('user', 'pass', () => {
client.subscribe(
'/topic/salary', //BREAKPOINT was set here
message => {
const body = JSON.parse(message.body);
if (body && body.pcId) {
salaries[body.pcId] = body;
setState({ salaries});
}
},
headers,
);
client.subscribe(
'/topic/decoding', //BREAKPOINT was set here
message => {
const newBody = JSON.parse(message.body);
if (newBody && newBody.PcID) {
consoleMessage[newBody.PcID] = newBody;
setState({ consoleMessage });
}
},
headers,
);
});
So in the code above I put a break-point at client.subscribe('/topic/decoding... and client.subscribe('/topic/salary.... I saw that it only subscribes to /topic/decoding but not /topic/salary.
Does anyone know how I can fix this issue so that it subscribes to both topics?
From Stomp documentation:
Since a single connection can have multiple open subscriptions with a server, an id header MUST be included in the frame to uniquely identify the subscription. The id header allows the client and server to relate subsequent MESSAGE or UNSUBSCRIBE frames to the original subscription.
Within the same connection, different subscriptions MUST use different subscription identifiers.
Stomp API definition:
subscribe(destination, callback, headers = {})
So for my understanding, You can't have the same username id for both of your subscriptions
Try creating 2 clients, e.g.:
const salaryClient = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj6.stomp');
const salaryHeaders = { id: 'salary' };
salaryClient.debug = null;
salaryClient.connect('user', 'pass', () => {
salaryClient.subscribe(
'/topic/salary',
message => {
const body = JSON.parse(message.body);
if (body && body.pcId) {
salaries[body.pcId] = body;
setState({ salaries});
}
},
salaryHeaders,
);
});
const decodingClient = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj7.stomp');
const decodingHeaders = { id: 'decoding' };
decodingClient.debug = null;
decodingClient.connect('user', 'pass', () => {
decodingClient.subscribe(
'/topic/decoding',
message => {
const newBody = JSON.parse(message.body);
if (newBody && newBody.PcID) {
consoleMessage[newBody.PcID] = newBody;
setState({ consoleMessage });
}
},
decodingHeaders,
);
});

Twilio + React: How to change audio or video input device during a call?

I've been looking at the documentation but I can't figure it out. When a user selects a different device, the other users can't hear that person anymore. This must mean that something is going right with the unpublishing of tracks, right? I'm not sure.
This is my code for a user to change devices:
const setDevice = (device) => {
if(!room) return
let deviceId = device.deviceId
const localParticipant = room.localParticipant
if(device.kind === 'audioinput'){
setSelectedAudioDevice(device.label)
Video.createLocalAudioTrack({
deviceId: {exact: deviceId}
}).then((localAudioTrack) => {
const tracks = localParticipant.audioTracks
tracks.forEach((track) => {
localParticipant.unpublishTrack(track.track)
})
localParticipant.publishTrack(localAudioTrack)
})
} else if(device.kind === 'videoinput'){
setSelectedVideoDevice(device.label)
Video.createLocalVideoTrack({
deviceId: {exact: deviceId}
}).then((localVideoTrack) => {
const tracks = localParticipant.videoTracks
tracks.forEach((track) => {
localParticipant.unpublishTrack(track.track)
})
localParticipant.publishTrack(localVideoTrack)
})
}
}
Each participant has its own component in which they subscribe to tracks. However, this code was from one of the Twilio examples, so I'm not entirely sure how it works.
const trackpubsToTracks = (trackMap) =>
Array.from(trackMap.values())
.map((publication) => publication.track)
.filter((track) => track !== null);
useEffect(() => {
setVideoTracks(trackpubsToTracks(participant.videoTracks));
setAudioTracks(trackpubsToTracks(participant.audioTracks));
const trackSubscribed = (track) => {
if (track.kind === "video") {
setVideoTracks((videoTracks) => [...videoTracks, track]);
} else if (track.kind === "audio") {
setAudioTracks((audioTracks) => [...audioTracks, track]);
}
};
const trackUnsubscribed = (track) => {
if (track.kind === "video") {
setVideoTracks((videoTracks) => videoTracks.filter((v) => v !== track));
} else if (track.kind === "audio") {
setAudioTracks((audioTracks) => audioTracks.filter((a) => a !== track));
}
};
participant.on("trackSubscribed", trackSubscribed);
participant.on("trackUnsubscribed", trackUnsubscribed);
return () => {
setVideoTracks([]);
setAudioTracks([]);
participant.removeAllListeners();
};
}, [participant]);
useEffect(() => {
const videoTrack = videoTracks[0];
if (videoTrack) {
videoTrack.attach(videoRef.current);
return () => {
videoTrack.detach();
};
}
}, [videoTracks]);
useEffect(() => {
const audioTrack = audioTracks[0];
if (audioTrack) {
audioTrack.attach(audioRef.current);
return () => {
audioTrack.detach();
};
}
}, [audioTracks]);
If anyone knows how I can handle device switching mid-call, I'd greatly appreciate it.
Twilio developer evangelist here.
I have found that the best order of operations here is:
Unpublish the local participant’s existing track from the room, this will trigger the trackRemoved event on the room for any other participants
Detach the existing track from the page
Stop the track completely
Request the new track with createLocal(Video|Audio)Track
Attach the new track to the page
Publish the new track to the room, triggering the trackAdded event on the room for the other participants
This is especially true for iOS devices which do not let you access more than one camera at a time.
Here is some code I've used before, though not in a React application:
function stopTracks(tracks) {
tracks.forEach(function(track) {
if (track) { track.stop(); }
})
}
function updateVideoDevice(event) {
const select = event.target;
const localParticipant = activeRoom.localParticipant;
if (select.value !== '') {
const tracks = Array.from(localParticipant.videoTracks.values()).map(
function(trackPublication) {
return trackPublication.track;
}
);
localParticipant.unpublishTracks(tracks);
detachTracks(tracks);
stopTracks(tracks);
Video.createLocalVideoTrack({
deviceId: { exact: select.value }
}).then(function(localVideoTrack) {
localParticipant.publishTrack(localVideoTrack);
log(localParticipant.identity + ' added track: ' + localVideoTrack.kind);
const previewContainer = document.getElementById('local-media');
attachTracks([localVideoTrack], previewContainer);
});
}
}
You can see the entire application in this repo on GitHub and I wrote about it here.
I think the React example you're referring to was one of mine too. I actually had a go at adding camera changes to that repo on a branch. It was apparently a year ago, but you can see the updates here. Hopefully that can point you in the right direction too.

React.js Updating state where multiple API endpoints are involved

I'm currently trying to get a project working to test some things and I'm stuck at a point where I'm trying to update the state properly.
I have an endpoint accessed via axios.get("/docker/containers") which will return an array for all IDs of the containers which are currently running on my system this is done like so:
componentDidMount() {
this.interval = setInterval(() => this.updateContainers(), 3000);
};
componentWillUnmount() {
clearInterval(this.interval);
}
At this point my state looks like this:
state = {
containers: [{id: 'id1'}, {id: 'id2'}]
}
The user interface then just shows a list of IDs.
I can then click on an ID on my user interface and it will set a watcher:
state = {
containers: [{id: 'id1', watcher: true}, {id: 'id2'}]
}
The point of the watcher is so that on the next update cycle more detailed information about a particular container is retrieved.
state = {
containers: [{id: 'id1', watcher: true, name: 'container1'}, {id: 'id2'}]
}
Upon clicking the container in the user interface where a watcher is already set then the watcher is dropped and the more detailed information is then no longer retrieved
state = {
containers: [{id: 'id1', watcher: false}, {id: 'id2'}]
}
Where I'm getting stuck is on how to get the more detailed information. My updateContainers method has 3 steps:
Read the response from the API and destruct the state into separate variables, compare the state var with the response var and remove any containers that have gone down (no setState is done here).
Add any new containers from the response to the state that have since come up (again no setState).
...All good thus far...
Loop through the filtered array of containers from steps 1 and 2 and find any containers where a watcher is set. Where it is set perform an API call to retrieve the more detailed info. Finally set the state.
In step 3 I use a forEach on the filtered array and then do an axios.get("/docker/containers/id1") where a watcher has been set otherwise simply keep the container details I already have but that's where I get stuck, Typescript is also giving me the error:
TS2322: Type 'void' is not assignable to type 'IndividualContainer[]'.
currently I have:
updateContainers() {
axios.get('/docker/containers')
.then(response => {
const apiRequestedContainers: string[] = response.data.containers;
// array of only IDs
const stateContainers: IndividualContainer[] = [
...this.state.containers
];
// remove dead containers from state by copying still live containers
let filteredContainers: IndividualContainer[] = [
...this.filterOutContainers(stateContainers, apiRequestedContainers)
];
// add new containers
filteredContainers = this.addContainerToArray(
filteredContainers, apiRequestedContainers
);
return this.updateContainer(filteredContainers);
})
.then(finalArray => {
const newState: CState = {'containers': finalArray};
this.setState(newState);
});
};
updateContainer(containers: IndividualContainer[]) {
const returnArray: IndividualContainer[] = [];
containers.forEach(container => {
if (container.watcher) {
axios.get('/docker/containers/' + container.id)
.then(response => {
// read currently available array of containers into an array
const resp = response.data;
resp['id'] = container.id;
resp['watcher'] = true;
returnArray.push(resp);
});
} else {
returnArray.push(container);
}
return returnArray;
});
};
Any pointers to where my logic fails would be appreciated!
Edit:
Render Method:
render() {
const containers: any = [];
const curStateOfContainers: IndividualContainer[] = [...this.state.containers];
if (curStateOfContainers.length > 0) {
curStateOfContainers.map(container => {
const container_id = container.id.slice(0, 12);
containers.push(
<Container
key = {container_id}
container_id = {container.id}
name = {container.name}
clickHandler = {() => this.setWatcher(container.id)}
/>
);
});
}
return containers;
}
I'm not an expert in TypeScript so I had to change the response to JS and thought you'll re-write it in TS in case it's needed.
async updateContainers() {
const response = await axios.get('/docker/containers')
const apiRequestedContainers = response.data.containers; // array of only IDs
const stateContainers = [...this.state.containers];
// remove dead containers from state by copying still live containers
let filteredContainers = [...this.filterOutContainers(stateContainers, apiRequestedContainers)];
// add new containers
filteredContainers = this.addContainerToArray(filteredContainers, apiRequestedContainers);
const containers = await this.updateContainer(filteredContainers)
this.setState({ containers });
};
async updateContainer(containers) {
return containers.map(async (container) => {
if (container.watcher) {
const response = await axios.get('/docker/containers/' + container.id)
// read currently available array of containers into an array
return {
...response.data,
id: container.id,
watcher: true,
}
} else {
return container;
}
});
}
Here's what I've updated in updateContainer:
I'm now mapping the array instead of doing a forEach
I'm now waiting for the container details API to return a value before checking the second container. --> this was the main issue as your code doesn't wait for the API to finish ( await / async )
The problem is that you are returning nothing from updateContainer method which will return void implicitly:
// This function return void
updateContainer(containers: IndividualContainer[]) {
const returnArray: IndividualContainer[] = [];
containers.forEach(container => {
if (container.watcher) {
axios.get("/docker/containers/" + container.id).then(response => {
// read currently available array of containers into an array
const resp = response.data;
resp["id"] = container.id;
resp["watcher"] = true;
returnArray.push(resp);
});
} else {
returnArray.push(container);
}
// this is inside the forEach callback function not updateContainer function
return returnArray;
});
}
Then you assign void to containers which is supposed to be of type IndividualContainer[] so TypeScript gives you an error then you set that in the state:
updateContainers() {
axios
.get("/docker/containers")
.then(response => {
const apiRequestedContainers: string[] = response.data.containers; // array of only IDs
const stateContainers: IndividualContainer[] = [
...this.state.containers
];
// remove dead containers from state by copying still live containers
let filteredContainers: IndividualContainer[] = [
...this.filterOutContainers(stateContainers, apiRequestedContainers)
];
// add new containers
filteredContainers = this.addContainerToArray(
filteredContainers,
apiRequestedContainers
);
// this return void as well
return this.updateContainer(filteredContainers);
})
// finalArray is void
.then(finalArray => {
// you assign void to containers which should be of type IndividualContainer[]
const newState: CState = { containers: finalArray };
// containers will be set to undefined in you state
this.setState(newState);
});
}
You meant to do this:
// I added a return type here so that TypeScript would yell at me if I return void or wrong type
updateContainer(containers: IndividualContainer[]): IndividualContainer[] {
const returnArray: IndividualContainer[] = [];
containers.forEach(container => {
if (container.watcher) {
axios.get("/docker/containers/" + container.id).then(response => {
// read currently available array of containers into an array
const resp = response.data;
resp["id"] = container.id;
resp["watcher"] = true;
returnArray.push(resp);
});
} else {
returnArray.push(container);
}
// removed the return from here as it's useless
});
// you should return the array here
return returnArray;
}
First, I've commented on errors in your code:
updateContainers() {
axios.get('/docker/containers')
.then(response => {
...
return this.updateContainer(filteredContainers);
// returns `undefined`...
})
.then(finalArray => { ... });
// ...so `finalArray` is `undefined` - the reason for TS error
// Also `undefined` is not a `Promise` so this second `then()`
// doesn't make much sense
};
updateContainer(containers: IndividualContainer[]) {
const returnArray: IndividualContainer[] = [];
containers.forEach(container => {
if (container.watcher) {
axios.get('/docker/containers/' + container.id)
.then(response => {
...
returnArray.push(resp)
// because `axios.get()` is asynchronous
// this happens only some time after
// `.then(finalArray => { ... })` is finished
});
// at this moment code inside `.then()` has not been executed yet
// and `resp` has not yet been added to `returnArray`
} else {
returnArray.push(container)
// but this happens while `forEach()` is running
}
return returnArray;
// here you return from `forEach()` not from `updateContainer()`
// also `forEach()` always returns `undefined`
// so even `return containers.forEach(...)` won't work
});
// no return statement, that implicitly means `return undefined`
};
Now, why the #RocKhalil's answer, kind of, works:
async updateContainers() {
const response = await axios.get('/docker/containers')
// he favors a much clearer syntax of async/await
...
const containers = await this.updateContainer(filteredContainers)
this.setState({ containers });
};
async updateContainer(containers) {
return containers.map(async (container) => {
if (container.watcher) {
const response = await axios.get('/docker/containers/' + container.id)
// Because `axios.get()` was **awaited**,
// you can be sure that all code after this line
// executed when the request ended
// while this
// axios.get(...).then(() => console.log(2)); console.log(1)
// will lead to output 1 2, not 2 1
return {
...response.data,
id: container.id,
watcher: true,
}
} else {
return container;
}
});
// he does not forget to return the result of `map()`
// and `map()` in contrast with `forEach()` does have a result
// But...
}
But...
containers.map() returns an array. An array of Promises. Not a single Promise. And that means that
const containers = await this.updateContainer(filteredContainers)
waits for nothing. And updateContainer() function is not actually async.
To fix that you need to use Promise.all():
const containers = await Promise.all(this.updateContainer(filteredContainers))

Resources