Is it possible to set state in Immer's produce()? - reactjs

Now
await this.setState(produce(this.state, (draft) => {
draft.item.is_loading = true;
}));
this.setState(await produce(this.state, async (draft) => {
// is_loading is true now.
const name = await get_name();
draft.item.name = name;
draft.item.is_loading = false;
}));
Expected
this.setState(await produce(this.state, async (draft) => {
let name;
draft.item.is_loading = true;
// set state here first.
name = await get_name();
draft.item.name = name;
draft.item.is_loading = false;
}));
I want to set state in the middle of calling produce(). Is It possible? If there's any good idea, please let me know. Thanks. :D
That's because I think connecting two chunks together is more readable. Especially when I can reuse variable declared in the former. It can looks quite trivial, but the more complex a source code becomes, the more unnatural it becomes to me.

Related

How do I select multiple args in a command and assign it to a variable? Discord.js

I want to do so that when I use !pm (user) (some message), my bot will send a private message to the user, although I have no idea how to select everything after the (user) and assign it to a variable, to send it to the user.
My current code looks like this:
module.exports = {
name: 'pm',
description: 'Sends a personal message!',
execute(message, args, client) {
const pmMessage = args[1];
client.users.cache.get('464839066781745167').send(pmMessage);
}
}
Since you know the array index of the pmMessage, with the help of that index, you can use the slice method which returns the array from specified stating to end index.
Eg:
let pmMessage = args.slice(2); //returns array.
Note: If the end index is not specified, then it considers till the last index of the array.
Now, using the join method, you can join all the indexes with whatever specified character. Eg:
let MessageContent = pmMessage.join(' '); //joined by space.
let args = ["UserID","This","represents","the","message","content","to","be","sent"];
console.log(args.slice(1));
console.log(args.slice(1).join(' '));
Try this, I think this should work:
let pm_args = args.slice(1).join(' ')
if(args[0]){
let user = getUserFromMention(args[0])
if(!user){
return message.reply('please mention a user.');
}
return client.users.cache.get(user.id).send(pm_args);
}
return message.reply('please mention a user.');
(This is what I used for mention thingie:)
function getUserFromMention(mention){
if(!mention) return;
if(mention.startsWith('<#') && mention.endsWith('>')){
mention = mention.slice(2, -1);
if(mention.startsWith('!')){
mention = mention.slice(1);
}
return bot.users.cache.get(mention);
}
};
client.on("message", message => {
if(!message.content.startsWith(config.prefix)) return;
const withoutPrefix = message.content.slice(config.prefix.length);
const split = withoutPrefix.split(/ +/);
const command = split[0];
const args = split.slice(1);
// Code...
});

Restful API failing sporadically but "catch" statement is always called, even when it does not fail

We are using a restful API to retrieve information about esports matches being played. From time to time the page simply loads, with no info being returned form the API.
I am fairly confident that the issue is with the API itself, but wanted to double-check that we are not doing anything wrong. Please see our code below:
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url = "http://datafeed.bet/en/esports.json ";
fetch(proxyurl + url)
.then(response => response.json())
.then(data => {
const list = data;
const games =
list &&
list.Sport &&
list.Sport.Events &&
list.Sport.Events.map((match) =>
match.Name.substr(0, match.Name.indexOf(","))
);
const uniqueGames = [...new Set(games)];
let combinedMatches = [];
data &&
data.Sport &&
data.Sport.Events &&
data.Sport.Events.map((game) => {
game.Matches.map((match) => {
match.Logo = game.Logo;
match.TournamentName = game.Name;
match.CategoryID = game.CategoryID;
match.ID = game.ID;
});
combinedMatches = combinedMatches.concat(game.Matches);
});
this.setState({
gameData: data,
games: games,
uniqueGames: uniqueGames,
preloading: false,
filteredGames: combinedMatches,
allMatches: combinedMatches,
count: Math.ceil(combinedMatches.length / this.state.pageSize),
});
var i;
let allMatches = 0;
let temp;
for (i = 0; i < this.state.filteredGames.length; i++) {
temp = allMatches =
allMatches + this.state.filteredGames[i].Matches.length;
}
this.setState({ allMatches: allMatches });
})
.catch(console.log('error');
Something that confuses me is that whether the data is returned or not, the "catch" statement gets called, outputting "error" to the console. I would like to build in some workaround for when the data is not returned. Would this be placed in the "catch" statement? If so, why how do I only let the catch run if the operation actually fails.
When you do this:
.catch(console.log('error'))
You immediately invoke console.log('error') and pass its result (which is undefined) to the catch. In this case it's invoking it right away, before the AJAX operation is even performed.
What you want to pass to catch is a function which would invoke that operation if/when it needs to:
.catch(e => console.log('error'))
As an aside, you'd probably also want to log the error itself so you can see what happened, as opposed to just the string 'error':
.catch(e => console.log('error', e))

Rewrite command handler

I want to rewrite my command handler due to it breaking if I attempt to make a new command that's a little bit different, and somehow manages to break it. Someone can help by giving me a new code fully or just fixing mine. The error is somewhere in the: I don't know why, I think due to I didn't define config or name, but I would like some help with this please. Thank you!
let pull = require(`../src/commands/${dirs}/${file}`);
bot.commands.set(pull.config.name, pull);
if (pull.config.aliases) pull.config.aliases.forEach(a => bot.aliases.set(a, pull.config.name));
};
Heres the full code.
const logger = require("../utils/logger");
const Discord = require('discord.js')
const client = new Discord.Client();
const fs = require("fs")
const ms = require('ms');
module.exports = (bot) => {
const load = dirs => {
const commands = readdirSync(`./src/commands/${dirs}/`).filter(d => d.endsWith('.js'));
for (let file of commands) { //for each of these files
let pull = require(`../src/commands/${dirs}/${file}`);
bot.commands.set(pull.config.name, pull);
if (pull.config.aliases) pull.config.aliases.forEach(a => bot.aliases.set(a, pull.config.name));
};
};
readdir(`./src/commands/`, (err, directories) => {
if (err) logger.error(err);
var dirArray = [];
directories.forEach((f, i) => {
if (lstatSync(`./src/commands/${f}`)) {
dirArray.push(f);
}
});
dirArray.forEach(x => load(x));
});
};
You can take a look at mine if you want:
https://github.com/azul-i/shuxue
works with subdirs, required argument lengths, aliases, and admin requirement.
If you go with this, you will need to check main.js, handlers/commands.js and events/message.js
lmk if you need help understanding any of it.

Can anyone explain why my state is getting updated even when i dont set it manually

So I just spent an hour debugging this code and finally got it to work, but I would want to know why this happened in the first place. I have a function that takes a value from my state, operates on it and saves the output in another variable in the state. This is the fuction:
getFolderNames = async () => {
const promises = this.state.rows.map(async item => {
if (item[".tag"] == "folder" && item.name.length > 20) {
item.name = await getFolderName(item.name);
return item;
} else return item;
});
const result = await Promise.all(promises);
this.setState({
rowsToDisplay: result
});
};
when i run this function, it was updating both the rows and rowsToDisplay to the result variable when i was only calling setState on only one of them.
Changing the function as below solves the issue but I would like to know why.
getFolderNames = async () => {
const promises = this.state.rows.map(async item => {
if (item[".tag"] == "folder" && item.name.length > 20) {
let item2 = {
...item
};
item2.name = await getFolderName(item.name);
return item2;
} else return item;
});
const result = await Promise.all(promises);
this.setState({
rowsToDisplay: result
});
};
It's because of how JavaScript handles variables. When you set a variable to an array or object, it doesn't make a new object but rather just references the original array/object.
As such, if you set a variable to equal some object, and then set a property of that variable, the original object will also be updated. Check this snippet for an example.
var foo = {changed: false};
var bar = foo;
bar.changed = true;
console.log("foo", foo.changed)
console.log("bar", bar.changed)
You can read more about the subject here: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0
I hope this helps you in the future, since I know I also spent many hours banging my head against exactly the sort of cases you described in your original question.

How to consume an Observable multiple times?

I am new to Angular and RxJS. I wonder what is the best/correct way to consume a value from an Observable multiple times.
My setup:
I have a component which calls a service which uses a REST service.
After the server returns the result I want to
use this result in the service at hand
AND return the result to the component.
// foo.component.ts
onEvent() {
this.fooService.foo()
.subscribe((data: FooStatus) => doSomething());
}
// foo.service.ts
private lastResult: FooStatus;
constructor(protected http: HttpClient) {
}
foo(): Observable<FooResult> {
return this.http.get('/foo')
.map((data: FooStatus) => {
this.lastResult = data; // use the data...
return data; // ... and simply pass it through
});
}
Using subscribe() multiple times will not work because the request would be send multiple times. This is wrong.
At the moment I use map() to intercept the result. But I am not comfortable with this because I introduce a side effect. Seems like a code smell to me.
I experimented with
foo(onSuccess: (result: FooResult) => void, onFailure: () => void): void {
...
}
but this looks even worse, I loose the Observable magic. And I do not want to have to write these callbacks in every service method myself.
As another way I considered the call to subscribe() in the service and then to create a fresh Observable I then can return to the component. But I could not get it work... seemed to complicated, too.
Is there a more elegant solution?
Is there a helpful method on Observable I did miss?
There are a number of ways to do this, and the answer will depend on your usage.
This codepen https://codepen.io/mikkel/pen/EowxjK?editors=0011
// interval observer
// click streams from 3 buttons
console.clear()
const startButton = document.querySelector('#start')
const stopButton = document.querySelector('#stop')
const resetButton = document.querySelector('#reset')
const start$ = Rx.Observable.fromEvent(startButton, 'click')
const stop$ = Rx.Observable.fromEvent(stopButton, 'click')
const reset$ = Rx.Observable.fromEvent(resetButton, 'click')
const minutes = document.querySelector('#minutes')
const seconds = document.querySelector('#seconds')
const milliseconds = document.querySelector('#milliseconds')
const toTime = (time) => ({
milliseconds: Math.floor(time % 100),
seconds: Math.floor((time/100) % 60),
minutes: Math.floor(time / 6000)
})
const pad = (number) => number <= 9 ? ('0' + number) : number.toString()
const render = (time) => {
minutes.innerHTML = pad(time.minutes)
seconds.innerHTML = pad(time.seconds)
milliseconds.innerHTML = pad(time.milliseconds)
}
const interval$ = Rx.Observable.interval(10)
const stopOrReset$ = Rx.Observable.merge(
stop$,
reset$
)
const pausible$ = interval$
.takeUntil(stopOrReset$)
const init = 0
const inc = acc => acc+1
const reset = acc => init
const incOrReset$ = Rx.Observable.merge(
pausible$.mapTo(inc),
reset$.mapTo(reset)
)
app$ = start$
.switchMapTo(incOrReset$)
.startWith(init)
.scan((acc, currFunc) => currFunc(acc))
.map(toTime)
.subscribe(val => render(val))
You will notice that the reset$ observable is used in two other observables, incOrReset$ and stopOrReset$
You can also introduce a .multicast() operator, which will explicitly allow you to subscribe multiple times. See description here: https://www.learnrxjs.io/operators/multicasting/
Take a look at share, it allows you to share a single subscription to the underlying source. You could do something like the following (it's not tested, but should give you the idea):
private lastResult: FooStatus;
private fooObs: Observable<FooStatus>;
constructor(protected http: HttpClient) {
this.fooObs = this.http.get('/foo').share();
this.fooObs.subscribe(((data: FooStatus) => this.lastResult = data);
}
foo(): Observable<FooResult> {
return this.fooObs.map(toFooResult);
}
as mentioned by #Mikkel in his answer that approach is cool however you can make use of following methods from Observable and achieve this.
these important methods are publishReplay and refCount,
use them as below,
private lastResult: FooStatus;
private myCachedObservable : Observble<FooResult> = null;
constructor(protected http: HttpClient) {
}
foo(): Observable<FooResult> {
if(!myCachedObservable){
this.myCachedObservable = this.http.get('/foo')
.map((data: FooStatus) => {
this.lastResult = data; // use the data...
return data; // ... and simply pass it through
})
.publishReplay(1)
.refCount();
return this.myObsResponse;
} else{
return this.myObsResponse;
}
}
Now in your component simply subscribe the observable returned from your method as above and notice the network request, you will see only one network request being made for this http call.

Resources