Get data back when successfully mutated (react-use-form, trpc and prisma) - next.js

Currently I have a form with a text input "title". When I submit the form, I can access this property via the submit handler inside my component, like this:
const formSubmit: SubmitHandler<FormSchemaType> = (formData) => {
const { title } = formData;
...
and in the form:
<form
onSubmit={handleSubmit(formSubmit)}
>
<input
{...register("title")}
type="text"
/>
...
(obs: I'm aware can also use the watch() function from useForm from react-hook-form as well to have access to this fields)
All is good, but when I submit I want to look at my sqlite database from prisma and see if this title is already in there, if it is, I want to save that on a variable, and if not, I want to do a mutation to include this on my database as a title model object (type Title - which has an id and a title String) and then save that into a variable as well. But I'm struggling to find a way to do this. Here's the code and all the ways I've tried:
...
import {type Title} from "#prisma/client"
...
const titlesQuerry = api.edit.getTitles.useQuery();
const titlesMutation = api.edit.newTitle.useMutation();
let savedTitle: Title | undefined; //try#1
//const [savedTitle, setSavedTitle] = useState<Title>(); //try#2
const formSubmit: SubmitHandler<FormSchemaType> = (formData) => {
const { title } = formData;
const possibleTitle = titlesQuerry.data?.find((t) => t.title === title);
if (!possibleDate) {
titlesMutation.mutate(title, { //this is working - title is going to database if its not already there
onSettled: (newTitle) => {
console.log(newTitle); //this is logging the right thing
savedTitle = newTitle //try#1.1
//onSuccess: (newTitle) => savedTitle = newTitle //try#1.2
//onSuccess: () => setSavedTitle(await datesQuerry.refetch()) //try#2
});
} else {
savedTitle = possibleTitle; //try#1
//setSavedTitle(possibleTitle); //try#2
}
console.log(savedTitle); //this is logging a title object only when the 'else' is met, more precisely, only in the case that mutation doesnt occur.
The problem, as I written above, is that I am not capable of saving this newTitle into a variable savedTitle after I do the mutation. It logs right when I log it on the "onSuccess" or "onSettled" methods, but doesnt save it to the variable, using state or not.
Please help! What can I do?

Related

Discord.JS, How to use one discord button to allow the purchase of various server roles

Sorry for the poorly worded title, I'll try to explain as best as I can. I am creating a role shop command using the new discord-buttons module, and came across a problem, to my understanding I would have to create a button for each individual role, in order for someone to buy it. After searching through documentation, I'm still a bit stumped. Here's some example code I put together to show what I'm trying to do:
let embedRed = new Discord.MessageEmbed()
.setTitle('Red Role')
.setColor('#c46413')
.addField('**Price**', '10,000', true)
.addField('**Color Hex:**', '#ffffff',true)
let embedBlue = new Discord.MessageEmbed()
.setTitle('Blue')
.setColor('#c2a289')
.addField('**Price**', '10,000', true)
.addField('**Color Hex:**', '#ffffff',true)
///Buttons
let buttonBuyRed = new MessageButton()
.setStyle('green')
.setLabel('Buy Red Role')
.setID('role_buy1')
let buttonBuyBlue = new MessageButton()
.setStyle('green')
.setLabel('Buy Blue Role')
.setID('role_buy2')
//embeded messages being sent
message.channel.send({ buttons: [buttonBuyRed], embed: embedRed});
message.channel.send({ buttons: [buttonBuyRed], embed: embedBlue});
//What happens if buttons are pressed
client.on('clickButton', async (role_buy1) => {
if (button.id === 'roley_buy1') {
button.channel.send(`${button.clicker.user.tag} bought red role`);
db.push(message.author.id, `${message.guild.roles.cache.get('role id here')}`) //role being pushed to user's inventory
}
});
client.on('clickButton', async (role_buy2) => {
if (button.id === 'role_buy2') {
button.channel.send(`${button.clicker.user.tag} bought blue role`);
db.push(message.author.id, `${message.guild.roles.cache.get('role id here')}`) //role being pushed to user's inventory
}
});
Since I have about 25 different roles that I want users to be able to purchase, it's quite a hassle to create a button for each role, I am looking for a way to just use one single "buy_role" button that works for all available roles.
If I didn't explain something clearly, please let me know, any help is appreciated!
So i came to a conclusion, this code works, but if your guild has a lot of roles, it would throw an error "Invalid form body"
const rolesInGuild = message.guild.roles.cache.array(); //creating array from collection of roles in a guild
const buttons = []; // an empty array for our buttons
for (const role of rolesInGuild) { // creating a loop inorder to create a button for every roles in rolesInGuild Array
const button = new MessageButton()
.setStyle('red') // default: blurple
.setLabel(`${role.name}`) // default: NO_LABEL_PROVIDED
.setID(`${role.id}`);
buttons.push(button); // button id is the same as role id so its unique!
}
console.log(rolesInGuild);
console.log(buttons);
await message.channel.send('test', { buttons: buttons }); // sending our buttons
bot.on('clickButton', async(button) => {
for (const btn of buttons) {
if (btn.custom_id == button.id) {
const role = button.guild.roles.cache.get(btn.custom_id);
const member = message.guild.members.cache.get(button.clicker.user.id);
member.roles.add(role);
}
}
});
you could add specific roles to the array rolesInGuild in this format
[{ name: 'rolename', id: 'roleid' }] instead of every roles in the guild ( I wasn't sure what your goal was)
you have ${message.guild...}, that’s the wrong if you have an error, so try this:
client.on('clickButton', async (button) => {
if (button.id === 'roley_buy1') {
button.channel.send(`${button.clicker.user.tag} bought red role`);
db.push(message.author.id, `${button.guild.roles.cache.get('role id here')}`)
//role being pushed to user's inventory
button.clicker.roles.add('your role id');
// or you can find the role using
const role = button.guild.roles.cache.find(role => role.name == 'rolename');
button.clicker.roles.add(role);
}
});```

Data validation using RxJS

I have the following function that validates that rangeFrom is not superior to rangeTo and that the rangeFrom does not already exist in the list of ranges.
How can rewrite this using RxJS?
const isTagAlreadyExist = (tags, currentTag) => _(tags)
.filter(x => x.id !== currentTag.id)
.some(x => _.inRange(currentTag.rangeTo, x.rangeFrom, x.rangeTo))
.value();
const validateRangeFrom = (tags, currentTag) => {
const errors = {};
if (isNumeric(currentTag.rangeFrom)) {
if (!_.inRange(currentTag.rangeFrom, 0, currentTag.rangeTo)) {
errors.rangeFrom = 'FROM_TAG_CANNOT_BE_GREATER_THAN_TO_TAG';
} else if (isTagAlreadyExist(tags, currentTag)) {
errors.rangeFrom ='TAG_ALREADY_EXISTS';
}
}
return {
errors
};
};
The question is: what parts do you want to rewrite to rxjs? Those are two pure functions that run synchronously from what I can see, I do not really see much a usecase for rxjs here - of course you could always utilize your functions within an rxjs stream:
const validateRangeFrom$ = (tags, currentTag) => {
return Observable.of(currentTag)
.map(tag => validateRangeFrom(tags, tag));
}
validateRangeFrom$(myTags, currentTag)
.subscribe(errors => console.log(errors));
But as you can see, this does not make much sense if you simply wrap it inside a stream, the essence of useful reactive programming is, that everything is reactive, not just some small parts, so for your example, you should start with having tags$ and currentTag$ as observables - let's assume that you have that, then you could do something like:
const tags$: Observable<ITag[]>... // is set somewhere, and emits a new array whenever it is changed
const currentTag$: Observable<ITag>... // is set somewhere and emits the tag whenever a new currentTag is set
const validateRangeFrom$ = Observable
.combineLatest(tags$, currentTag$, (tags, tag) => ({tags, tag}))
.map(({tags, tag}) => validateRangeFrom(tags, tag));
validateRangeFrom$.subscribe(errors => console.log(errors));
This will automatically trigger the validation for you whenever a new tags-array is emitted or a new currentTag is selected/set - but again: your validation-method is kept the same - as even in reactive programming you have to do validation and logic-operations at some point, the reactive part usually just concerns the flow of the data (see: tags$ and currentTag$)

how to create a global variable for every entrance to the website?

I have global array that works just fine and stores the URL's of the chosen images from the user after i click submit in the form.
the problem is when i want to submit another form, the global array will still have the URL's of the previous submission.
what i want to do is to create an array for every user to store his URL's, one he click submit, the array will be dropped or deleted. so if there were multiple users using the same function, every one of them will have his own array to store his URL's
How do i do this?
this is what i have tried but when i click on submit on the form page, nothing happens
first, this is the method that returns the url of the chosen image by the user, the method exists in both folder (both/file.js)
storeUrlInDatabaseSS: function( url ) {
check( url, String );
Modules.both.checkUrlValidity( url );
try {
return url;
} catch( exception ) {
return exception;
}
}
then i created the session variables in the client side (client/file.js)
Session.set("screenshots", []);
Session.set("i", 0);
var screenshots = Session.get("screenshots");
var i = Session.get("i");
and here i store the url in the array
let _addUrlToDatabaseSS = ( url ) => {
screenshots[i++] = url;
Session.set("screenshots", screenshots);
};
and am using Meteor Collection Hook Package
and i added these two lines of code which should be excited after the user press submit, they exist inside "client/files.js" directory
Products.before.insert(function (userId, doc) {
doc.screenShots = Session.get("screenshots");
});
now whenever i click submit nothing happens, i think the problem is because nothing is actually stored inside the screenShots attribute in the collection here
screenShots: {
type: [String]
},
when i set the screenShots attribute to an empty array by default like the code below, the submit button works
screenShots: {
type: [String],
autoValue: function() {
return [];
}
},
I tried to use the other way of using AutoForm.hooks
AutoForm.hooks({
submitPostForm: {
before: {
insert: function(doc) {
doc.$set.screenShots = Session.get("screenshots");
}
}
}
});
the is my form in the .html file
{{> quickForm collection="Products" id="submitPostForm"
type="method" meteormethod="submitPost" omitFields="createdAt, previewImage, screenShots, sourceCode, userID"}}
and this is the method triggered once the user submit the form, it exist in the server side.
submitPost: function (app) {
// Console.log('new App:', app);
check(app, {
title: String,
description: String,
category: String,
price: Number
});
Products.insert(app);
}
for some reason my before hook isn't working and i can't see why!
what am i doing wrong here?
One of the ways to create a global array per user is to use Session. This way it is also possible to persist the data across the app (only client-side).
Simple way to use Session is thus:
Create an array in Session called url_list:
Session.set("url_list", []);
Retrieve the array from Session:
var url_list = Session.get("url_list");
Make changes to url_list:
url_list.push(someData);
Store url_list in the Session again:
Session.set("url_list", url_list);
Note: Session can only be used on client-side and all related code should be on the client-side.
More about Session.
PERSISTING DATA TO SERVER-SIDE:
The best way to persist the url_list to the server, would be to insert a new document into the database collection containing the Session data.
insertToDB = function() {
var url_list = Session.get('url_list');
Products.insert({
'url_list': url_list
});
Session.set('url_list', []); // To empty the client-side list
}

Is there a way to update the URL in Flow Router without a refresh/redirect?

Is there a way to update a part of the URL reactively without using FlowRouter.go() while using React and react-layout?
I want to change the value in the document that is used to get the document from the DB. For example, if I have a route like ~/users/:username and update the username field in the document, I then have to user FlowRouter.go('profile', {data}) to direct the user to that new URL. The "old" route is gone.
Below is the working version I have, but there are two issues:
I have to use FlowRouter.go(), which is actually a full page refresh (and going back would be a 404).
I still get errors in the console because for a brief moment the reactive data for the component is actually wrong.
Relevant parts of the component are like this:
...
mixins: [ReactMeteorData],
getMeteorData() {
let data = {};
let users = Meteor.subscribe('user', {this.props.username});
if (user.ready())
data.user = user;
return data;
}
...
updateName(username) {
Users.update({_id:this.data.user._id}, {$set:{username}}, null, (e,r) => {
if (!e)
FlowRouter.go('profile', {username});
});
},
...
The route is like this:
FlowRouter.route('/users/:username', {
name: 'profile',
action(params) {
ReactLayout.render(Main, {content: <UserProfile {...params} />});
}
});
The errors I get in the console are:
Exception from Tracker recompute function:
and
TypeError: Cannot read property '_id' of undefined

Meteor.autorun() not working on client when insert occures

i have been knocking my head for 2 days now in that .
am creating a search engine, am creating queries dynamically using Meteor Framwork, the queries are working fine and when i search i can rebind the UI (Table in My Case) with the dynamic data query output.
however if an insert/update/delete operation occures the data object
and the UI (html Table) is not updating.
which means that the template is not re-rendered when the data object changes.
Template.search.rendered = function () {
Meteor.autorun(function() {
alarmsData = Alarms.find(getSearchSelector($('#searchTxt').val(), $('#startTimeTxt').val(), $('#endTimeTxt').val())).fetch()
console.log("rendered")
//alarmsData = Alarms.find({},{sort: {timestamp: "desc"} }).fetch();
searchControls(alarmsData)
getConsole(alarmsData, ".console")
$('#badge').html(alarmsData.length)
})
}
the get console function is just reading the array from teh search and creating an html table (this is working fine)
as for the begining i am creating a simple query as the default for my search. and then am changing this query whenever user changes the search criteria. i can notice that only the first instance of teh data object is kept and tracked for changes, so if the second search criteria resides within the first one, it's updating the UI, if not nothing happenes
i have used Meteor.autorun(function(){}) function however i traced it's execution with console.log and i can see it's no excuting when i insert data in the database for the same collection.
One, I believe you are trying to use Deps.autorun. Also, there is nothing in your autorun that seems to be dependent on a reactive source. Since alarmsData is taking a snapshot of data it won't care when Alarms has data changing.
Second, I would probably approach this with a redirect. I would compile my data, and redirect to the same page, allowing the server to handle the querying for me. This easily allows you to jump to this page from anywhere else with a prefilled query in the parameters (because the route would then handle it) and also gives a visual change to the navigation bar when a search has happened (just like every other search engine). You would do something like this on a button click:
var query = {},
path;
query.text = encodeURIComponent($('#searchTxt').val()),
query.start = encodeURIComponent($('#startTimeTxt').val()),
query.end = encodeURIComponent($('#endTimeTxt').val()),
// redirect to current path
path = Router.routes[Router.current().route.name].path({}, {
query: query
});
Router.go( path );
In your router you would just pass the query into your server and route as a data object (assuming you are using iron-router):
this.route( "search", {
path: "/search",
waitOn: function() {
return [
Meteor.subscribe( "searchAlarms", _.omit( this.params, "hash" ) ),
]
},
data: function () {
return { "query": _.omit( this.params, "hash" ) };
}
});
This will not only give you the query data that was used for the search (in your template) but the server can now handle the search for you! Your Alarms data now holds all of the documents needed to display to the user and you no longer need to subscribe to all your Alarms. This is also great because it is automatically reactive. So if a new Alarm matches your query filter it will automatically be passed down to the client and displayed to the user without needing to setup any extra dependencies/autoruns.
Note though, that if you are subscribing to Alarms elsewhere you will still need to do filtering client-side.
What a strange meteor code…
The "rendered" code method code is called once you will be rendering the search template
getSearchSelector($('#searchTxt').val() is not reactive, my advise is to use the session variable to put your search criteria inside and use this same session to inject the find parameters inside.
Are you looking for displaying all the alarms Data ?
function getAlarms()
{
var text = Session.get("text");
var from = Session.get("start");
var to = Session.get("end");
var filter = getSearchSelector(text, from, to);
return Alarms.find(filter);
}
Template.search.alarms = function () {
return getAlarms();
}
Template.search.alarmsCount = function () {
return getAlarms().count();
}
Template.search.events({
'keypress input[name=text]' : function(e,o)
{
var val = $("input[name= text]").val()
Session.set("text", val);
},
'keypress input[name=start]' : function(e,o)
{
var val = $("input[name=start]").val()
Session.set("start", val);
},
'keypress input[name=end]' : function(e,o)
{
var val = $("input[name=end]").val()
Session.set("end", val);
}
});
// And your template will look something like:
<template name="search">
Search alarms
<input type="text" name="text" placeholder="Enter your text here…"/>
<input type="text" name="start" placeholder="start time"/>
<input type="text" name="end" placeholder="end time/>
There is {{alarmsCount}} alarms(s);
{{#each alarms}}
Alarm object: {{.}}
{{/each}}
</template>
I Guess its Solved it by using Session.set & get, and automatically subscribing to the Serevr and send the dynamic Query.
Check the below Code
Template.zConsole.rendered = function () {
Session.set("obj", getSearchSelector($('#searchTxt').val(), $('#startTimeTxt').val(), $('#endTimeTxt').val()))
Deps.autorun(function (){
Meteor.subscribe("dynamicAlarms", Session.get("obj"))
console.log("Count from AutoRun ==> " + Alarms.find(Session.get("obj")).count())
})
}
on the server
Meteor.publish('dynamicAlarms',function (searchObj) {
return Alarms.find(searchObj)
})
& it works perfect with less code.

Resources