I have a validate function that I am copying into almost every model. I want to abstract it by extending the base bookshelf.Model object. I'm not sure what the correct way to go about this is in ES6. I'd like to do this without forking bookshelf.
An example model:
import bookshelf from '../bookshelf';
import Checkit from 'checkit';
const Design = bookshelf.Model.extend({
tableName: 'foo',
constructor: function() {
bookshelf.Model.apply(this, arguments); // super()
this.on('saving', this.validate.bind(this));
},
validations: {
barColumn: ['required', 'integer', 'greaterThan:0'],
},
validate: function(model, attrs, options) {
let validations;
if (options.patch === true) {
Object.keys(this.validations).forEach((value, index) => {
if (this.attributes[index] !== undefined) {
validations[index] = value;
}
});
} else {
validations = this.validations;
}
return new Checkit(validations).run(this.toJSON());
}
});
export default Design;
The main bookshelf file is here.
I couldn't work out how to extend Bookshelf so I solved it like this:
import bookshelf from '../bookshelf';
import validate from '../utils/validate';
const Design = bookshelf.Model.extend({
tableName: 'foo',
constructor: function() {
bookshelf.Model.apply(this, arguments); // super()
this.on('saving', validate);
},
validations: {
barColumn: ['required', 'integer', 'greaterThan:0'],
},
});
export default Design;
And the new validate file:
import Checkit from 'checkit';
export default function validate(model, attributes, options) {
let validations;
if (options.patch === true) {
Object.keys(model.validations).forEach((value, index) => {
if (attributes[index] !== undefined) {
validations[index] = value;
}
});
} else {
validations = model.validations;
}
return new Checkit(validations).run(model.toJSON());
};
I think you need Facade or Decorator pattern + some dependency injection.
I used Facade for my own purposes the following way:
class MyFacade {
constructor(legacyLibrary) {
this.legacyLibrary = legacyLibrary;
}
newMethod() {
this.legacyLibrary.doSomething();
this.legacyLibrary.doSomethingElse();
}
}
export default MyFacade;
Idea of decorator is the same, but you should extend your existing class. So all the functionality will remain the same, but you'll have more methods in it.
The good thing about decorator is that you can nest them. Decorate with one, then with another, etc.
Related
Rendering HelloComponent in AppComponent and when the element is removed from DOM by using renderer.removeChild(), HelloComponent's ngOnDestroy method is not firing. So unable to close the subscriptions of Hello Component.
Here is an example stackbliz
Wow, don't you want to destroy them with plain old *ngIf? It would make life so much easier. Anyway, you can use mutation observers.
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Roughly, and crudely, it could look like this:
constructor(private ref: ElementRef) {}
ngOnInit() {
const ref = this.ref;
this.subscription = this.myObservable.subscribe(data => {
console.log(data);
});
const config = { childList: true };
const cb = function(mutationList, observer) {
for (const mutation of mutationList) {
for (const node of mutation.removedNodes) {
if(node === ref.nativeElement) {
console.log("I've just been destroyed");
}
}
}
};
const observer = new MutationObserver(cb);
observer.observe(this.ref.nativeElement.parentNode, config);
}
Stackblitz here:
https://stackblitz.com/edit/angular-wutq9j?file=src/app/hello.component.ts
I created a class to get api.
export default class ProductDetail extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
product : []
}
}
componentDidMount() {
this.getProductRequest();
}
...
then create getProductRequest function:
async getProductRequest() {
let response = await fetch('https: ...
let json = await response.json();
console.log(json);
this.setState({ product : json.data});
}
the console result is:
{id: 225782, title: "test", images: Array(1), price: "1$"}
Now in render i get same result:
render() {
console.log(this.state.product);
return (...
Now I try to read params:
render() {
console.log(this.state.product.title);
return (...
But I get this error:
TypeError: Cannot read property 'title' of underfined
what's the wrong?
Edit: Structure:
export default class ProductDetail extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
product : []
}
}
componentDidMount() {
this.getProductRequest();
}
render() {
console.log(this.state.product.title);
return (
<View> <Text style={styles.name}>title</Text></View>
);
}
async getProductRequest() {
try {
let id = this.props.navigation.state.params.productId;
let response = await
fetch('https://www.example.com/product', {
method : 'POST',
headers : {
'Accept' : 'application/json',
'Content-Type' : 'application/json'
},
body : JSON.stringify({
id
})
});
let json = await response.json();
//json: {"data":{id: 225782, title: "test", images: Array(1), price: "1$"},"status":"success"}
this.setState({ product : json.data});
} catch(error) {
//console.log(error)
}
}
}
...
Because, componentDidMount() re-render after the first execution of rendering. So, when you are putting console.log(this.state.product.title); in the render before return, it doesn't get the title param first time.
After the re-render, the value will be available. So, if you want to check the output put console.log elsewhere or just remove it
Edit
You can call this.getProductRequest(); in componentWillMount() instead of componentDidMount()
componentWillMount() {
this.getProductRequest();
}
let product = JSON.parse(this.state.product
if(product.title){
console.log(product.title)
}
Let with above code. If you are getting string in your state, it may create an issue. Let me know if its work.
As said react official documentation :
componentDidMount() is invoked immediately after a component is mounted (inserted into the tree)
it does mean that first time your render method is unable to read the title of your product (first time that your render method is invoked, this.state.product is still an empty array). I suggest you to check if your array is empty
render() {
if (this.state.product) {
return (
<Text>Loading...</Text>
} else
return (
<View><Text>{this.state.product.title}</Text></View>
)
}
Don't use componentWillMount() because these methods are considered legacy and you should avoid them in new code.
componentWillMount()
If your render function actually does look like you posted, then this can't work. Try chaning your render function to something like this.
render() {
const { product } = this.state
if (!product || !product.title) {
return null
}
return (
<View><Textstyle={styles.name}>product.title</Text></View>
)
}
I have a service that defines an object to be shared across multiple components. I'm setting this object's values in a function that is called during the APP_INITIALIZER and it seems to be ok at first, but when i try to get this object from other components, it's always empty...
In my data.service.ts i have this:
// Object to be shared across components
private loggedUser = {};
setObject(user) {
this.loggedUser = user;
}
getObject() {
return this.loggedUser;
}
// Function that is correctly executed during the APP_INITIALIZER
Init() {
return new Promise<void>((resolve, reject) => {
this.getCurrentUser().subscribe((data: any) => {
// I receive an object with some properties here
this.setObject(data);
resolve();
});
});
}
Then in one of my components i try to get this object with:
ngOnInit() {
var user = this.dataService.getObject();
console.log(user); // It's always empty
}
EDITED:
In my app.module.ts i have a factory that will receive the promise from Init() function:
export function initializeApp1(appInitService: DataService) {
return (): Promise<any> => {
return appInitService.Init();
}
}
providers: [ DataService, { provide: APP_INITIALIZER, useFactory: initializeApp1, deps: [DataService], multi: true} ],
As you can see, my goal is to set an object during the APP_INITIALIZER and being able to share it across components after that.
Is there a way to avoid the duplication of competenceList[competenceKey] (object) in lines :5 and :6, keeping the same object on return without using variable declaration?
const func = ({
entities: { competence: competenceList },
timesheet: { management: { competences: competenceKey } },
}) => ({
employeeKey: competenceList[competenceKey].employee,
payrollEnd: competenceList[competenceKey].competenceEnd,
});
Yes, it is technically possible by using a computed property name:
const func = ({
timesheet: { management: { competences } },
entities: { competence: { [competences]: competence },
}) => ({
employeeKey: competence.employee,
payrollEnd: competence.competenceEnd,
});
but I wouldn't recommend this as it's not really readable. Just write
function func({timesheet, entities}) {
const competence = entities.competence[timesheet.management.compentences];
return {
employeeKey: competence.employee,
payrollEnd: competence.competenceEnd,
};
}
Feels like I'm missing something obvious here - but I can't figure out how to access my JSON data. I have a Container component:
class About extends Component {
componentDidMount(){
const APP_URL = 'http://localhost/wordpress/'
const PAGES_URL = `${APP_URL}/wp-json/wp/v2/pages`
this.props.fetchAllPages(PAGES_URL, 'about')
}
render(){
return (
<div>
<Header/>
<div className="bg">
<div className="home-wrapper">
<h1>AAAAABBBBBOOOOUUUUUT</h1>
<Counter/>
<AboutInfo />
</div>
</div>
<Footer/>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ fetchAllPages }, dispatch)
}
export default connect(null, mapDispatchToProps)(About);
And a Smart component:
class AboutInfo extends Component {
render(){
console.log(this.props.page);
console.log(this.props.page.id);
return (
<div>
<h1>This is ID: {this.props.page.id}</h1>
</div>
)
}
}
const mapStateToProps = ({ page }) => {
return { page }
}
export default connect(mapStateToProps)(AboutInfo);
My action:
export const fetchAllPages = (URL, SLUG) => {
var URLEN;
if(!SLUG){
URLEN = URL
} else {
URLEN = URL + "?slug=" + SLUG
}
return (dispatch) => {
dispatch(fetchRequest());
return fetchPosts(URLEN).then(([response, json]) => {
if(response.status === 200){
if(!SLUG) {
dispatch(fetchPagesSuccess(json))
} else {
dispatch(fetchPageBySlugSuccess(json))
}
} else {
dispatch(fetchError())
}
})
}
}
const fetchPageBySlugSuccess = (payload) => {
return {
type: types.FETCH_PAGE_BY_SLUG,
payload
}
}
My reducer:
const page = (state = {}, action) => {
switch (action.type) {
case FETCH_PAGE_BY_SLUG:
console.log(action.paylod)
return action.payload
default:
return state
}
}
This gives me:
When I console.log(this.props.page) in my AboutInfo component, it prints the object, but when I print console.log(this.props.page.id) it gives me undefined. Why can't I print the JSON content? Thanks!
page is an array and hence this.props.page.id is undefined. You might want to access the first element in array in which case you would do
this.props.page[0].id
but you might also need to add a test, since before the response is available you will be trying to access page[0].id and it might break.
You could instead write
this.props.page && this.props.page[0] && this.props.page[0].id
Getting data from the store is async So you must adding loading varibale on your reducer
class AboutInfo extends Component {
render(){
if(this.props.loading) return (<div>loading</div>);
return (
<div>
<h1>This is ID: {this.props.page.id}</h1>
</div>
);
}
}
const mapStateToProps = ({ page, loading }) => {
return { page, loading }
}
on your action try returing
json.page[0]
That is because page is an array and the id is a property of its 1st element.
So use this.props.page[0].id
If the logged object in your screenshot is the this.props.page then you will need and additional .page as that is also a part of the object this.props.page.page[0].id