In my app I have 2 components (Page and Home).
Home extends Page like so:
export default class Home extends Page
and Page just extends Component.
In Page I have a method to get user data from AsyncStorage
async getUser() {
// get from storage
this.setState({user});
}
The above is called on the constructor of Page.
The problem I have is that Home has a method on componentWillMount that relies on this.state.user. Obviously this isn't going to work since the getUser method is async.
Is there a way I can get the user information and only call specific methods once I have that info?
export default class Page extends Component {
async getUser() {
// get from AsyncStorage
this.setState({user});
}
componentWillMount() {
this.getUser();
}
}
export default class Home extends Page {
async foo(user_id) {
this.setState({something});
}
componentWillMount() {
this.foo(this.state.user);
}
render() {
return <Text>{this.state.something}</Text>;
}
}
Is there a way I can get the user information and only call specific
methods once I have that info?
Not in a general sense, but of course there are some solutions. What this comes down to is that you have an async data dependency. You need to write the code in such a way that a call to dependent functions is only made after the data becomes available.
Perhaps the easiest way to do this is to use componentWillUpdate instead of componentDidMount and check if you are receiving the required data:
componentWillUpdate(nextProps, nextState) {
if (nextState.user != null && this.state.user !== nextState.user) {
this.prepareForUser(nextState.user);
}
}
(Note that you can't setState directly in componentWillUpdate, but since your method is async it won't happen until later. You can use componentDidUpdate to chain another setState call.)
Another option (which I like to use) is to use composition instead of inheritance. This makes the life-cycle easier to control through rendering: the parent can only render a child when all the child's dependent data is loaded, then the child does not need to worry about any timing issues related to the initial loading of data and can do whatever it wants from its componentDidMount:
class UserContainer extends Component {
state = {};
componentDidMount() {
this.getUser();
}
async getUser() {
// get user from async store
this.setState({user});
}
render() {
return (
<div>
{ this.state.user ? <UserView user={this.state.user} /> : <Spinner /> }
</div>
);
}
}
class UserView extends Component {
componentDidMount() {
this.props.user != null
}
}
This is pretty much the "container component" pattern.
Related
I'm trying to write test cases around a component that contains my FullCalendar component in Angular.
I've written karma tests using a MockFullCalendar that extends FullCalendar and have been successfully able to mock the getApi().view calls for getting fake active start and active end dates. But when I use the same method for getApi().getResources() and getApi.getResourceById(id) I get this error when running tests
error TS2339: Property 'getResourceById' does not exist on type 'Calendar'.
const calendarResource = this.calendar.getApi().getResourceById(resourceIdAssignedToEvent);
Here are my mock classes
export class MockFullCalendarComponent extends FullCalendarComponent {
getApi(): any {
return {};
}
}
export class MockCalendarApi extends CalendarApi {
getResources(): any {
return [];
}
getResourceById(resourceId: any): any {
return {};
}
}
export class MockCalendar extends MockCalendarApi {
get view(): any {
return {
get activeStart(): any {
return new Date();
},
get activeEnd(): any {
return new Date();
}
};
}
}
The following is how I was able to successfully mock the getApi().view
export class MockFullCalendarComponent extends FullCalendarComponent {
getApi(): any {
return {
view: {
activeStart: new Date(),
activeEnd: new Date()
}
};
}
}
Using the same pattern somehow does not work for the Calendar and CalendarApi classes. I'm not sure if this is because there is a class and an interface of the same name (CalendarApi) in FullCalendar common and only the interface contains these two methods.
I'm using Angular 12 and FullCalendar V5.
I've tried mocking as described above, tried different ways to provide my mock classes to the TestBed for injection.
My test case does not run at all, because these methods aren't detected somehow, none of the existing tests run at all, its almost like the whole initialization of the test case fails at this and I'm not sure how else I can force the spec to use my mock classes that contains mock implementations of those methods.
I want to enable my users to set certain global colors when using the app. Therefor I have created a 'dynamicVariables.css' file:
:root {
--my-color: violet;
}
It is imported in 'global.scss' file:
#import "./theme/dynamicVariables.css";
Also, I've added a colorpicker on one page and I can set the --my-color variable fine from there.
onColorChange(data: any) {
document.documentElement.style.setProperty('--my-color', data);
}
Just when closing the app on my device (I've deployed it with ionic capacitor run android), it resets the css variable, because when I run it again the color is back to its default value.
I'm pretty sure, I have a general misconception here and would be grateful for some clarification. I'm generally new to web development and would be grateful for any help.
Thanks in advance.
just like how Mustafa explained in comments, you need to make these changes outside app "runtime" and in the device's memory, that would stay there even after the app (whether web or native) is closed. for example you can use ionic storage and save your data with keys and values same as your css keys, and load it up whenever the app opens.
Thanks to the responds, I was able to solve the problem with the help of Ionic Storage.
First, I created a Storage Service:
import { Storage } from '#ionic/storage-angular';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class StorageService {
private _storage: Storage | null = null;
constructor(private storage: Storage) {
}
async init() {
const storage = await this.storage.create();
this._storage = storage;
}
public set(key: string, value: any) {
this._storage?.set(key, value);
}
public get(key: string) {
return this._storage?.get(key);
}
}
When starting the app, I run the following code in the app.component.ts
async ngOnInit() {
await this.storageService.init();
let storedPathologicalColor = await this.storageService.get('--my-color');
if (storedPathologicalColor == null)
document.documentElement.style.setProperty('--my-color', getComputedStyle(document.documentElement).getPropertyValue('--my-color'))
else
document.documentElement.style.setProperty('--my-color', storedPathologicalColor);
}
It is important to init() the service from outside. When setting a new css variable, I also set a new key/value pair to the Storage.
Thanks again.
Its possible to use Hoc with context api inside a next page?
I have a next page generated by SSR, and a HOC privateRoute to validate authorization on this page. But for every access, we have a authorization request and its sound's me like a problem.
My idea is to use contexApi to get data one time, and reuse that on auth private route.
Anyone has a minimal exemple about?
Thanks.
I found a solution.
The problem was to use Context inside react class component.
mport React, { Component } from 'react'
import UserContext from './UserContext'
class HomePage extends Component {
static contextType = UserContext
componentDidMount() {
const user = this.context
console.log(user) // { name: 'Tania', loggedIn: true }
}
for more, see https://www.taniarascia.com/using-context-api-in-react/
render() {
return <div>{user.name}</div>
}
}
I have a NestJS application that uses passport, and I'd like to add a #User param decorator to get the user in the request more easily, similarly to this example.
However, I also have a global validation pipe that I apply for all incoming requests (bodies, headers, query string), and apply different validation depending on the declared body/query/header type. For some reason, when using the createParamDecorator() function, NestJS applies the validator to the req.user object. That is wrong however. The passport user is not part of the request contents, so it shouldn't be validated like that.
Short of copy&pasting createParamDecorator()'s source, and stripping out the line that applies the pipes, is there a way to turn off pipes, or at least make my validation pipe ignore non-request related types?
My validation pipe, for reference:
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '#nestjs/common';
import { ValidationService } from './validation.service';
#Injectable()
export class ValidationPipe<T extends unknown> implements PipeTransform {
public constructor(private readonly service: ValidationService) {}
public async transform(value: T, metadata: ArgumentMetadata): Promise<T> {
const className: string | undefined = metadata.metatype && metadata.metatype.name;
if (className) {
const errors = await this.service.validate(value, className);
if (errors && errors.length > 0) {
throw new BadRequestException(errors);
}
}
return value;
}
}
The one thing I don't want is to modify the above (or the service) to blacklist/whitelist types, as those will be changing rapidly as the application evolves, so it would be big pain point to add them to such lists.
After making a PR with a change that was supposed to fix this, it seems for my particular problem, I could use metadata.type, like so:
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '#nestjs/common';
import { ValidationService } from './validation.service';
#Injectable()
export class ValidationPipe<T extends unknown> implements PipeTransform {
public constructor(private readonly service: ValidationService) {}
public async transform(value: T, metadata: ArgumentMetadata): Promise<T> {
if (metadata.type !== 'custom') {
const className: string | undefined = metadata.metatype && metadata.metatype.name;
if (className) {
const errors = await this.service.validate(value, className);
if (errors && errors.length > 0) {
throw new BadRequestException(errors);
}
}
}
return value;
}
}
And the PR in question doesn't even do what I thought it does, so it doesn't really work either.
I've a service registered for Twig and i use its method in my main layout.twig.html to list some things.
Next, in some actions i use the same service to change its state (change some private fields there) and i would like to see those changes in my rendered page. But it looks like Twig invokes the "getter" method to soon, when my data is not yet managed by controller's action.
What is the best practice for such case? Should i somehow use some Events and make my Service kind of event listener?
Example layout code:
<div>{{ myservice.mymethod() }}</div>
Service:
class MyService {
private $myfield = null;
....
public function setMyField($value) {
$this->myfield = $value;
}
public function myMethod() {
if($this->myfield === null) {
return 'not initialized';
} else {
$this->myfield;
}
}
....
Some controller action:
$myservice = $this->container->get('myservice');
$myservice->setMyField('setted in action');
And i always get not initialized on rendered page
I think you have to register this service as a twig extension.
check out this manual: http://symfony.com/doc/current/cookbook/templating/twig_extension.html.