Flutter Redux Middleware - redux

I'm completely new to redux and it's beginning to make sense. I'm trying to use the middleware to keep the reducer a pure function but it's giving me an error I don't understand. I'm following the flutter architecture samples for redux
void main() {
final store = Store<AppState>(
appReducer,
initialState: AppState.loading(),
middleware: createStoreFlashCardsMiddleware(),
);
runApp(new MyApp(store));
}
//the middleware
List<Middleware<AppState>> createStoreFlashCardsMiddleware() {
final loadFlashCards = _createLoadFlashCardsMiddleware();
final saveFlashCards = _createSaveFlashCardsMiddleWare();
return [
TypedMiddleware<AppState, FetchFlashCardsAction>(loadFlashCards),
TypedMiddleware<AppState, AddFlashCardAction>(saveFlashCards),
TypedMiddleware<AppState, ClearCompletedAction>(saveFlashCards),
TypedMiddleware<AppState, ToggleAllAction>(saveFlashCards),
TypedMiddleware<AppState, UpdateFlashCardAction>(saveFlashCards),
TypedMiddleware<AppState, FetchCardsSucceededAction>(saveFlashCards),
TypedMiddleware<AppState, DeleteFlashCardAction>(saveFlashCards),
];
}
Middleware<AppState> _createSaveFlashCardsMiddleWare() {
return (Store store, action, NextDispatcher next) async {
// YOUR LOGIC HERE
// After you do whatever logic you need to do,
// call this Redux built-in method,
// It continues the redux cycle.
next(action);
};
}
Middleware<AppState> _createLoadFlashCardsMiddleware() {
return (Store store, action, NextDispatcher next) async {
next(action);
};
}
The error is:
error: The argument type 'List<(Store<AppState>, dynamic, (dynamic) → void) → void> (C:\flutter\bin\cache\pkg\sky_engine\lib\core\list.dart)' can't be assigned to
the parameter type 'List<(Store<AppState>, dynamic, (dynamic) → void) → void> (C:\flutter\bin\cache\pkg\sky_engine\lib\core\list.dart)'. (argument_type_not_assignable at [line])

Related

why does this vuelidate function not work within setup, but only outside, within methods?

I'm using vuelidate with the composition API and I don't understand why the v$.validate() works correctly when I put within methods, after setup, but not within setup.
So this works:
setup() {
// inspired from
// https://vuelidate-next.netlify.app/#alternative-syntax-composition-api
const state = reactive ({
// the values of the form that need to pass validation, like:
name: ''
})
const rules = computed (() => {
return {
// the validation rules
}
const v$ = useVuelidate(rules, state)
return {
state,
v$
}
},
methods: {
async submitForm () {
const result = await this.v$.$validate()
// either result: validation succeeded : axios post call
// or failure and error messages show up.
}
}
but, this doesn't work:
setup() {
const state = reactive ({
// the values of the form that need to pass validation, like:
name: ''
})
const rules = computed (() => {
return {
// the validation rules
}
const v$ = useVuelidate(rules, state)
const submitForm = async () {
// **ERROR : Uncaught (in promise) TypeError: v$.$validate is not a function**
const result = await v$.$validate()
// either result: validation succeeded : axios post call
// or failure and error messages show up.
}
return {
state,
v$,
submitForm
}
}
That's a bit of a pain, because I use a composable for the axios call where the state is an argument. Would be easier to keep the entire code in one place.
Composition API
useVuelidate returns a computed, so you need to use .value when accessing any of it's properties, like $error, $validate inside the setup function.
In the template it is unwrapped for you.

Handling errors with redux-toolkit

The information about the error in my case sits deeply in the response, and I'm trying to move my project to redux-toolkit. This is how it used to be:
catch(e) {
let warning
switch (e.response.data.error.message) {
...
}
}
The problem is that redux-toolkit doesn't put that data in the rejected action creator and I have no access to the error message, it puts his message instead of the initial one:
While the original response looks like this:
So how can I retrieve that data?
Per the docs, RTK's createAsyncThunk has default handling for errors - it dispatches a serialized version of the Error instance as action.error.
If you need to customize what goes into the rejected action, it's up to you to catch the initial error yourself, and use rejectWithValue() to decide what goes into the action:
const updateUser = createAsyncThunk(
'users/update',
async (userData, { rejectWithValue }) => {
const { id, ...fields } = userData
try {
const response = await userAPI.updateById(id, fields)
return response.data.user
} catch (err) {
if (!err.response) {
throw err
}
return rejectWithValue(err.response.data)
}
}
)
We use thunkAPI, the second argument in the payloadCreator; containing all of the parameters that are normally passed to a Redux thunk function, as well as additional options: For our example async(obj, {dispatch, getState, rejectWithValue, fulfillWithValue}) is our payloadCreator with the required arguments;
This is an example using fetch api
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const getExampleThunk = createAsyncThunk(
'auth/getExampleThunk',
async(obj, {dispatch, getState, rejectWithValue, fulfillWithValue}) => {
try{
const response = await fetch('https://reqrefs.in/api/users/yu');
if (!response.ok) {
return rejectWithValue(response.status)
}
const data = await response.json();
return fulfillWithValue(data)
}catch(error){
throw rejectWithValue(error.message)
}
}
)
Simple example in slice:
const exampleSlice = createSlice({
name: 'example',
initialState: {
httpErr: false,
},
reducers: {
//set your reducers
},
extraReducers: {
[getExampleThunk.pending]: (state, action) => {
//some action here
},
[getExampleThunk.fulfilled]: (state, action) => {
state.httpErr = action.payload;
},
[getExampleThunk.rejected]: (state, action) => {
state.httpErr = action.payload;
}
}
})
Handling Error
Take note:
rejectWithValue - utility (additional option from thunkAPI) that you can return/throw in your action creator to return a rejected response with a defined payload and meta. It will pass whatever value you give it and return it in the payload of the rejected action.
For those that use apisauce (wrapper that uses axios with standardized errors + request/response transforms)
Since apisauce always resolves Promises, you can check !response.ok and handle it with rejectWithValue. (Notice the ! since we want to check if the request is not ok)
export const login = createAsyncThunk(
"auth/login",
async (credentials, { rejectWithValue }) => {
const response = await authAPI.signin(credentials);
if (!response.ok) {
return rejectWithValue(response.data.message);
}
return response.data;
}
);

I have a Dart problem when I use map in a stream

I create a simple stream, add a few elements and listen to the stream. But there is a problem when I use the map on the stream. For simplicity I just map the value to the same value (I guess?).
When I try to run the program I get an map-error:
Uncaught Error: TypeError: Instance of '_MapStream<String, dynamic>': type '_MapStream<String, dynamic>' is not a subtype of type 'Stream<String>'
import 'dart:async';
void main() async {
StreamController controller = StreamController<String>.broadcast();
final StreamTransformer transformer = StreamTransformer<String, String>.fromHandlers(
handleData: (data, EventSink sink) {
sink.add(data);
}
);
Stream stream = controller.stream;
stream
.map((value) => value) // <-- Problem in this line
.transform(transformer)
.listen(
(data) {
print('listen: $data');
},
onError: (err) => print(err));
controller.add('foo');
controller.add('baa');
controller.close();
}
Your problem is you are forcing non-generic types from generic types. If you write the code like this where you are not use explicit typing:
import 'dart:async';
void main() async {
final controller = StreamController<String>.broadcast(); // <-- fixed here
final StreamTransformer transformer =
StreamTransformer<String, String>.fromHandlers(
handleData: (data, EventSink sink) {
sink.add(data);
});
final stream = controller.stream; // <-- fixed here
stream
.map((value) => value) // <-- Problem in this line
.transform(transformer)
.listen((data) {
print('listen: $data');
}, onError: (err) => print(err));
controller.add('foo');
controller.add('baa');
controller.close();
}
Dart will then automatically determine that the type is StreamController<String> and Stream<String> and not StreamController and Stream which you are enforcing.
When you are removing the generic part of the type you are also removing Dart's ability to guess the type for all methods you are calling.
If using explicit typing you should write:
import 'dart:async';
void main() async {
StreamController<String> controller = StreamController<String>.broadcast();
final StreamTransformer transformer =
StreamTransformer<String, String>.fromHandlers(
handleData: (data, EventSink sink) {
sink.add(data);
});
Stream<String> stream = controller.stream;
stream
.map((value) => value) // <-- Problem in this line
.transform(transformer)
.listen((data) {
print('listen: $data');
}, onError: (err) => print(err));
controller.add('foo');
controller.add('baa');
controller.close();
}

How to test asynchonous functions using sinon?

I have a class called PostController, and I trying to test the following function create:
class PostController {
constructor(Post) {
this.Post = Post;
}
async create(req, res) {
try {
this.validFieldRequireds(req);
const post = new this.Post(req.body);
post.user = req.user;
...some validations here
await post.save();
return res.status(201).send(message.success.default);
} catch (err) {
console.error(err.message);
const msg = err.name === 'AppError' ? err.message :
message.error.default;
return res.status(422).send(msg);
}
}
My test class is:
import sinon from 'sinon';
import PostController from '../../../src/controllers/posts';
import Post from '../../../src/models/post';
describe('Controller: Post', async () => {
it.only('should call send with sucess message', () => {
const request = {
user: '56cb91bdc3464f14678934ca',
body: {
type: 'Venda',
tradeFiatMinValue: '1',
... some more attributes here
},
};
const response = {
send: sinon.spy(),
status: sinon.stub(),
};
response.status.withArgs(201).returns(response);
sinon.stub(Post.prototype, 'save');
const postController = new PostController(Post);
return postController.create(request, response).then(() => {
sinon.assert.calledWith(response.send);
});
});
});
But I'm getting the following error:
Error: Timeout of 5000ms exceeded. For async tests and hooks, ensure
"done()"
is called; if returning a Promise, ensure it resolves.
(D:\projeto\mestrado\localbonnum-back-end\test\unit\controllers\post_spec.js)
Why?
Most probably it's because misuse of sinon.stub.
You've
sinon.stub(Post.prototype, 'save');
without telling what this stub will do, so in principle this stub will do nothing (meaning it returns undefined).
IDK, why you don't see other like attempt to await on stub.
Nevertheless, you should properly configuture 'save' stub - for example like this:
const saveStub = sinon.stub(Post.prototype, 'save');
saveStub.resolves({foo: "bar"});

Create reducer about user state

I'm trying to apply reflux/ngrx on my current front-end project.
I want to take advantage of this in order to change a slight functionality: Change current user related tasks in order to use a single user state.
Current user related tasks: Currently, I'm using an traditional model in order to achieve user login process... UserService is able to check user credentials. Once it's been checked I store user information on an AppService:
export class LoginComponent implements OnInit {
private fb: FormBuilder;
private form:FormGroup;
private commty: UsersService;
private router: Router;
private appState: AppState;
private alerts: Array<Object>;
constructor()
{
this.alerts = [];
}
ngOnInit():void {
this.form = this.fb.group({
user: ['', Validators.required],
passwd: ['', Validators.minLength(6)]
});
}
public checkPasswd():void {
this.clearAlerts();
this.commty.checkPasswd(this.form.value.mail, this.form.value.passwd)
.subscribe(
(result: any) => {
this.appState.user = result;
this.router.navigate(['/app']);
},
(error: any) => {
this.addAlert(error.message);
}
);
}
private addAlert(message: string): void {
this.alerts.push({type: 'danger', msg: message});
}
public closeAlert(index): void {
this.alerts.splice(index, 1);
};
private clearAlerts(): void {
this.alerts.splice(0, this.alerts.length);
}
}
I'm a bit confused about how to move this code in order to use reflux/ngrx. I'ce read a bit about this topic, nevertheless I'm not quite able to figure out how to move my code. Up to now, I've created an single Store and User interfaces:
store.interface.ts:
export interface IStore {
user: IUser
sources: ISourceRedux;
}
user.interfcae.ts:
export interface IUser {
id: string;
name: string;
username: string;
customer: string;
}
The next step I think I need to do is to create reducers. This step is which I don't quite understand how build this code. Up to now
user.initialstate.ts:
export function initialUserState(): IUser {
return {
id: '',
name: '',
username: '',
customer: '',
sources: []
};
};
user.reducer.ts
export class User {
private static reducerName = 'USER_REDUCER';
public static reducer(user = initialUserState(), {type, payload}: Action) {
if (typeof User.mapActionsToMethod[type] === 'undefined') {
return user;
}
return User.mapActionsToMethod[type](user, type, payload);
}
// ---------------------------------------------------------------
// tslint:disable-next-line:member-ordering
private static mapActionsToMethod = {};
}
Which reducers I should create in order to:
Check credentials.
If credentials are right get this user and update User state store.
If credentials are wrong inform the process has failed.
Perhaps I'm merging concepts... I need some lights...
EDIT
public connect(user: string, currentPasswd: string, extraHttpRequestParams?: any): Observable<UserDTO> {
return this.checkPasswdWithHttpInfo(id, currentPasswd, extraHttpRequestParams)
.map((response: Response) => {
if (response.status === 204) {
return undefined;
} else {
return response.json();
}
}).catch((error: any) => {
if (error.status >= 500) {
return Observable.throw(new Error(error.status));
}
else { //if (error.status >= 400) {
const body = error.json() || '';
const code = body.error || JSON.stringify(body);
const message = body.message || JSON.stringify(body);
return Observable.throw(ApiError.create(code, message));
}
});
}
Ok so this is the next question of your "Integrate ngrx into my code" =).
What you're looking for is : https://github.com/ngrx/effects
The idea behind effects is that an effect let you catch an Action, do side effect (API call or whatever) and you can then dispatch another Action (often success or error).
Flow example to connect a user :
--| [from component] Dispatch action USER_CONNECT
--| [from user.effect.ts]
----| Catch action ofType('USER_CONNECT')
----| Do what you need to do (API call for ex)
----| When the response comes back :
------| If success : Dispatch USER_CONNECT_SUCCESS
------| If error : Dispatch USER_CONNECT_ERROR
Of course when you dispatch either USER_CONNECT_SUCCESS or USER_CONNECT_ERROR you can pass additional data in the payload (for example user information or the error).
Here's a full example :
#Injectable()
export class UsersEffects {
constructor(
private _actions$: Actions,
private _store$: Store<IStore>,
private _userService: UserService,
) { }
#Effect({ dispatch: true }) userConnect$: Observable<Action> = this._actions$
.ofType('USER_CONNECT')
.switchMap((action: Action) =>
this._userService.connect(action.payload.username, action.payload.password)
.map((res: Response) => {
if (!res.ok) {
throw new Error('Error while connecting user !');
}
const rslt = res.json();
return { type: 'USER_CONNECT_SUCCESS', payload: rslt };
})
.catch((err) => {
if (environment.debug) {
console.group();
console.warn('Error catched in users.effects.ts : ofType(USER_CONNECT)');
console.error(err);
console.groupEnd();
}
return Observable.of({
type: 'USER_CONNECT_ERROR',
payload: { error: err }
});
})
);
}
You can take a look into my project Pizza-Sync were I did something similar (except that I don't catch in case of error and do not dispatch if there's an error).

Resources