Reactions inside the class - reactjs

I'd like mobx to trigger a reaction whenever an observable changes. I want it to be trigerred inside the class that has that observable so the trigger method could manipulate other data in the store, for example data in a sub-store.
class Animal {
name
energyLevel
constructor(name) {
reaction(
() => giraffe.isHungry,
isHungry => {
if (isHungry) {
console.log("Now I'm hungry!")
} else {
console.log("I'm not hungry!")
}
console.log("Energy level:", giraffe.energyLevel)
}
)
this.name = name
this.energyLevel = 100
makeAutoObservable(this)
}
reduceEnergy() {
this.energyLevel -= 10
}
get isHungry() {
return this.energyLevel < 50
}
}
(The example is taken from the docs: https://mobx.js.org/reactions.html)
If I move the reaction inside the constructor function, it will not be triggered (in the original code it's outside the class). How can I trigger reactions inside the class?

First of all, to refer to the current instance of the class you need to use this keyword, like this.isHungry and etc.
And second is that you need to use reaction after you use makeAutoObservable, so just move it in the end of constructor:
class Animal {
name;
energyLevel;
constructor(name) {
this.name = name;
this.energyLevel = 100;
makeAutoObservable(this);
reaction(
() => this.isHungry,
(isHungry) => {
if (isHungry) {
console.log("Now I'm hungry!");
} else {
console.log("I'm not hungry!");
}
console.log('Energy level:', this.energyLevel);
}
);
}
reduceEnergy() {
this.energyLevel -= 60;
}
addEnergy() {
this.energyLevel += 60;
}
get isHungry() {
return this.energyLevel < 50;
}
}
const animal = new Animal('cat');
animal.reduceEnergy();
animal.addEnergy();

Related

Response Handle

I am pulling data database,
My response class
sealed class Response<out T>{
object Loading: Response<Nothing>()
data class Success<out T>(val data: T): Response<T>()
data class Error(val message: String): Response<Nothing>()
}
Where should I handle the incoming data like this?
First approach
View Model
class DataViewModel (private val useCase: UseCase, ) : ViewModel() {
private val _dataState = mutableStateOf("")
val dataState = _dataState
val loading = mutableStateOf(false)
private val _eventFlow = MutableSharedFlow<UIEvent>()
val eventFlow = _eventFlow.asSharedFlow()
fun getData() {
viewModelScope.launch {
useCase.getData().collect { response ->
when(response){
is Response.Error -> {
_eventFlow.emit(UIEvent.ShowSnackBar(response.message))
loading.value=false
}
is Response.Loading -> loading.value=true
is Response.Success -> {
_dataState.value = response.data
loading.value=false
_eventFlow.emit(UIEvent.Success)
}
}
}
}
}
sealed class UIEvent {
data class ShowSnackBar(val message: String) : UIEvent()
object Success : UIEvent()
}
}
Screen
#Composable
fun DataScreen(dataViewModel: DataViewModel) {
val scaffoldState = rememberScaffoldState()
LaunchedEffect(key1 = true) {
dataViewModel.eventFlow.collectLatest { event ->
when (event) {
is DataViewModel.UIEvent.ShowSnackBar -> {
scaffoldState.snackbarHostState.showSnackbar(event.message)
}
is DataViewModel.UIEvent.Success -> {
"Do something"
}
}
}
}
Scaffold(
scaffoldState = scaffoldState,
content = { EditProfileContent(dataViewModel.dataState.value) }
)
}
Second Approach
View Model
class DataViewModel (private val useCase: UseCase, ) : ViewModel() {
private val _dataState = mutableStateOf<Response<Data>>(Response.Loading)
val dataState = _dataState
fun getData() {
viewModelScope.launch {
useCase.getData().collect { response ->
_dataState.value = response
}
}
}
}
Screen
#Composable
fun DataScreen(dataViewModel: DataViewModel) {
val scaffoldState = rememberScaffoldState()
when(val dataState = dataViewModel.dataState.value){
is Response.Error -> {
LaunchedEffect(key1 = true, block = {scaffoldState.snackbarHostState.showSnackbar(dataState.message)}) }
is Response.Loading -> { Loading() }
is Response.Success -> {
Scaffold(
scaffoldState = scaffoldState,
content = { EditProfileContent(dataState.data) }
)
// Do something else
}
}
}
The first approach seems much easier to me, especially when I'm pulling in more than one data, but when I search, the second approach is usually used in most places.
What's wrong with using the first approach? Which is the best practice?

Missing properties in TypeScript

class RolloutStoreClass {
import { observable, action, makeAutoObservable } from "mobx";
public queue = observable<IDeploymentProject>([]);
public inProcess = observable<IDeploymentProject>([]);
public successfull = observable<IDeploymentProject>([]);
public failed = observable<IDeploymentProject>([]);
constructor() {
makeAutoObservable(this);
}
#action
private clearQueue(): void {
this.queue = [] ;
this.inProcess = [];
this.failed = [];
this.successfull = [];
}
}
export const RolloutStore = new RolloutStoreClass();
I get the issue on the clearQueue Function exactlly on this. queue
Error is:
In the "never []" type, the following properties of the "Observable Array " type are missing: "spliceWithArray, clear, replace, remove, toJSON".
You either need to make your queue (and other fields) regular array, all observability will still work.
Or use .clear() method inside clearQueue:
private clearQueue(): void {
this.queue.clear();
// ...
}
One more thing: when you use makeAutoObservable you don't need to explicitly mark action's observable's and so on, you can just drop all the decorators:
class Foo {
// Don't need explicit observable decorator
public queue: IDeploymentProject[] = [];
// ...
constructor() {
makeAutoObservable(this);
}
// You can remove action decorator
private clearQueue(): void {
this.queue = [] ;
this.inProcess = [];
this.failed = [];
this.successfull = [];
}
}

trying to find an Element on DOM with typescript

I m totally newbie in typescript. I m just trying to find an element with a selector. whatever i tried findElement() method is always turning undefined. Where am i doing wrong ?
Any help greatly appreciated !
var cdnBasePath: any = this.findElement('meta[name="cdnBasePath"]').attr('content');
public findElement(selector: any) {
if (angular.isDefined(this.$window.jQuery)) {
return this.$document.find(selector);
} else {
return angular.element(this.$document.querySelector(selector));
}
}
In order for "this" to be defined you have to declare your findElement as a instance method of some class. Like this:
class SomeClass
{
public SomeMethod()
{
var cdnBasePath: any = this.findElement('meta[name="cdnBasePath"]').attr('content');
}
public findElement(selector: any)
{
if (angular.isDefined(this.$window.jQuery)) {
return this.$document.find(selector);
} else {
return angular.element(this.$document.querySelector(selector));
}
}
}
Otherwise you can have it as static method:
class UI
{
public static findElement(selector: any)
{
if (angular.isDefined(this.$window.jQuery)) {
return this.$document.find(selector);
} else {
return angular.element(this.$document.querySelector(selector));
}
}
}
var cdnBasePath: any = UI.findElement('meta[name="cdnBasePath"]').attr('content');
Or just drop 'this' and have it as global function (I do not recommend this)
function findElement(selector: any)
{
if (angular.isDefined(this.$window.jQuery)) {
return this.$document.find(selector);
} else {
return angular.element(this.$document.querySelector(selector));
}
}
var cdnBasePath: any = findElement('meta[name="cdnBasePath"]').attr('content');
Hope this helps.

Paging ListBox with ReactiveUI and Caliburn.Micro

I'm trying to implement a paging mechanism for a listbox using Caliburn.Micro.ReactiveUI with a call to EF using ".Skip(currentPage).Take(pageSize)". I'm new to ReactiveUI and Reactive in general. I'm sure this is supposed to be easy.
I've got a single "SearchParameters" class which I needs to be observed and the search function needs to execute when any of the properties on the SearchParameters object changes.
You can see from the commented-out code that I've tried to define the class as a ReactiveObject as well. The current implementation though is with CM's PropertyChangedBase. The individual properties are bound textboxes in my view using CM's conventions:
public class SearchParameters : PropertyChangedBase
{
private string _searchTerm;
public string SearchTerm
{
get { return _searchTerm; }
set
{
if (value == _searchTerm) return;
_searchTerm = value;
NotifyOfPropertyChange(() => SearchTerm);
}
}
private int _pageSize;
public int PageSize
{
get { return _pageSize; }
set
{
if (value == _pageSize) return;
_pageSize = value;
NotifyOfPropertyChange(() => PageSize);
}
}
private int _skipCount;
public int SkipCount
{
get { return _skipCount; }
set
{
if (value == _skipCount) return;
_skipCount = value;
NotifyOfPropertyChange(() => SkipCount);
}
}
//private string _searchTerm;
//public string SearchTerm
//{
// get { return _searchTerm; }
// set { this.RaiseAndSetIfChanged(ref _searchTerm, value); }
//}
//private int _pageSize;
//public int PageSize
//{
// get { return _pageSize; }
// set { this.RaiseAndSetIfChanged(ref _pageSize, value); }
//}
//private int _skipCount;
//public int SkipCount
//{
// get { return _skipCount; }
// set { this.RaiseAndSetIfChanged(ref _skipCount, value); }
//}
}
"SearchService" has the following method which needs to execute when any one of SearchParameter's values change:
public async Task<SearchResult> SearchAsync(SearchParameters searchParameters)
{
return await Task.Run(() =>
{
var query = (from m in _hrEntities.Departments select m);
if (!String.IsNullOrEmpty(searchParameters.SearchTerm))
{
searchParameters.SearchTerm = searchParameters.SearchTerm.Trim();
query = query.Where(
x => x.Employee.LastName.Contains(searchParameters.SearchTerm) || x.Employee.FirstName.Contains(searchParameters.SearchTerm)).Skip(searchParameters.SkipCount).Take(searchParameters.PageSize);
}
return new SearchResult
{
SearchTerm = searchParameters.SearchTerm,
Matches = new BindableCollection<DepartmentViewModel>(query.Select(x => new DepartmentViewModel{ Department = x }).Skip(searchParameters.SkipCount).Take(searchParameters.PageSize))
};
});
}
Here's how I've tried to wire all of this up in MainViewModel's ctor and where Rx gets hazy for me:
public class MainViewModel : ReactiveScreen
{
private SearchParameters _searchParameters;
public SearchParameters SearchParameters
{
get { return _searchParameters; }
set
{
if (value == _searchParameters) return;
_searchParameters = value;
NotifyOfPropertyChange(() => SearchParameters);
}
}
{
public void MainViewModel()
{
var searchService = new SearchService();
//default Skip and PageSize values
SearchParameters = new Services.SearchParameters { SkipCount = 0 , PageSize = 10};
var searchParameters = this.ObservableForProperty(x => x.SearchParameters)
.Value()
.Throttle(TimeSpan.FromSeconds(.3));
var searchResults = searchParameters.SelectMany(parameters => searchService.SearchAsync(parameters));
var latestMatches = searchParameters
.CombineLatest(searchResults,
(searchParameter, searchResult) =>
searchResult.SearchTerm != searchParameter.SearchTerm
? null
: searchResult.Matches)
.Where(matches => matches != null);
_departmentViewModels = latestMatches.ToProperty(this, x => x.DepartmentViewModels);
searchParameters.Subscribe(x => Debug.WriteLine(x));
}
}
In the above example the call to SearchAsync doesn't execute. It seems that changes to SearchParameter's properties aren't being observed.
Can anyone tell me what I'm doing wrong here?
Here's how I ended up doing this although I'd be interested in hearing other solutions if anyone has suggestions. I'm not sure if this is the best way but it works:
First, I defined a computed property in my SearchParameters class that returns a string and reevaluates anytime CurrentPage, SkipCount and PageSize are updated from the View:
public string ParameterString
{
get { return String.Format("SearchTerm={0}|SkipCount={1}|PageSize={2}", SearchTerm, SkipCount, PageSize); }
}
Next, in my MainViewModel ctor I simply observe the computed rather than attempting to react to SearchTerm, SkipCount and PageSize individually (which my original question was asking how to do):
var searchTerms = this
.ObservableForProperty(x => x.SearchParameters.ParameterString)
.Value()
.Throttle(TimeSpan.FromSeconds(.3));
var searchResults = searchTerms.SelectMany(parameters => SearchService.SearchAsync(parameters));
var latestMatches = searchTerms
.CombineLatest(searchResults,
(searchTerm, searchResult) =>
searchResult.SearchTerm != searchTerm
? null
: searchResult.Matches)
.Where(matches => matches != null);
Finally, in my SearchService I parse the parameter string to get the current values:
var parameters = searchParameters.Split('|');
var searchTerm = "";
var skipCount = 0;
var pageSize = 0;
foreach (var parameter in parameters)
{
if (parameter.Contains("SearchTerm="))
{searchTerm = parameter.Replace("SearchTerm=", "");}
else if (parameter.Contains("SkipCount="))
{ skipCount = Convert.ToInt32(parameter.Replace("SkipCount=", "")); }
else if (parameter.Contains("PageSize="))
{ pageSize = Convert.ToInt32(parameter.Replace("PageSize=", "")); }
}

How to refresh the binding again using mvvmlight

The CurrentSelectedBall is updated whenever I changed its value due to two-way binding.
When I click to update the function UpdateRedBall is called so the redball in the database is updated. But the view list of balls is not updated as the ObservableCollection<RedBall> RedBalls is not changed at all.
How to fix this problem? I guess something needs to be done after _context.SaveChanges();
Also I can not simply do DataGridA.itemsSource = RedBalls to make a hard refresh here as first DataGridA is not accessible in the MainviewModel.
Some of the methods:
public ObservableCollection<RedBall> RedBalls
{
get { return new ObservableCollection<RedBall>(_context.RedBalls.OrderBy(m=>m.DateNumber));}
set
{
_redBalls = value;
RaisePropertyChanged("RedBalls");
}
}
public RedBall CurrentSelectedRedBall
{
get { return _currentSelectedRedBall; }
set
{
_currentSelectedRedBall = value;
RaisePropertyChanged("CurrentSelectedRedBall");
}
}
private void UpdateRedBall()
{
if (CurrentSelectedRedBall != null)
{
var ballToUpdate = _context.RedBalls.SingleOrDefault(x => x.Id == CurrentSelectedRedBall.Id);
ballToUpdate.DateNumber = CurrentSelectedRedBall.DateNumber;
ballToUpdate.First = CurrentSelectedRedBall.First;
ballToUpdate.Second = CurrentSelectedRedBall.Second;
ballToUpdate.Third = CurrentSelectedRedBall.Third;
ballToUpdate.Fourth = CurrentSelectedRedBall.Fourth;
ballToUpdate.Fifth = CurrentSelectedRedBall.Fifth;
ballToUpdate.Sixth = CurrentSelectedRedBall.Sixth;
}
_context.SaveChanges();
//RedBalls = RedBalls
}
I have a strong dislike for getters that 'do stuff'. It should be impossible for a get to fail. I would consider moving the logic in your RedBalls getter elsewhere. I'm also concerned that you're newing up the RedBalls ObservableCollection on every get as it can cause problems with bindings. Bound ObservableCollections should be instantiated once, then altered using Add, Remove and Clear. Working with them this way should also solve the problem of updating your collection appropriately.
public ObservableCollection<RedBall> RedBalls
{
get { return _redBalls; }
set
{
_redBalls = value;
RaisePropertyChanged("RedBalls");
}
}
public RedBall CurrentSelectedRedBall
{
get { return _currentSelectedRedBall; }
set
{
_currentSelectedRedBall = value;
RaisePropertyChanged("CurrentSelectedRedBall");
}
}
private void UpdateCurrentSelectedRedBall()
{
UpdateRedBall();
var redBalls = _context.RedBalls.OrderBy(m=>m.DateNumber);
if(redBalls != null)
{
RedBalls.Clear();
foreach(RedBall rb in redBalls)
{
RedBalls.Add(rb);
}
}
}
private void UpdateRedBall()
{
if (CurrentSelectedRedBall != null)
{
var ballToUpdate = _context.RedBalls.SingleOrDefault(x => x.Id == CurrentSelectedRedBall.Id);
ballToUpdate.DateNumber = CurrentSelectedRedBall.DateNumber;
ballToUpdate.First = CurrentSelectedRedBall.First;
ballToUpdate.Second = CurrentSelectedRedBall.Second;
ballToUpdate.Third = CurrentSelectedRedBall.Third;
ballToUpdate.Fourth = CurrentSelectedRedBall.Fourth;
ballToUpdate.Fifth = CurrentSelectedRedBall.Fifth;
ballToUpdate.Sixth = CurrentSelectedRedBall.Sixth;
}
_context.SaveChanges();
}
Use something like this: https://gist.github.com/itajaja/7507120 - basically you need to subscribe for PropertyChanged and raise CollectionChanged events from that.

Resources