React immutable List return undefined item after push method - immutability

I'm new in React. I tried to build an sample application look like Todos app and using immutable Store
// My Store
import {List } from 'immutable';
constructor() {
this.id = '';
this.name = '';
this.todos = List();
}
addTodo(todo){
this.setState({
todos: this.todos.push(toto)
});
}
// My component
import Immutable from 'immutable';
import UUID from 'node-uuid';
var newTodo = Immutable.fromJS({ id: UUID.v4(), name: '', content:'' });
...
When I pushed new todo
addTodo(newTodo)
and: console.log(this.todos)
--> It always returned
Object[undefined]
Anyone can explain me the reason why it always returns undefined object in List?
Thank in advance

Related

How to get data stored as subject rxjs

I am working on displaying the details of event clicked. I have stored all the events inside an array.
When the user clicks on the event then its id is passed which checks inside the array and it passes the result into service.
showDetail(id){
let obj = this.events;
let newArr = Object.values(obj);
let result = newArr.filter(function(el) {
return el["id"] == id;
});
this.articleService.sendMessage(result);
let url = `/article/${id}`;
this.router.navigate([url]);
}
service
private detailSubject = new Subject<any>();
sendMessage(formData: any) {
this.detailSubject.next({formData});
}
getMessage(): Observable<any> {
return this.detailSubject.asObservable();
}
Now in my article/id page.
I am not being able to retrieve this passed array.
I have following code
ngOnInit() {
this.articleService.getMessage().subscribe(
res => {
this.loadArticleDetail(res["formData"]);
},
error => {
console.log("Error loading data");
}
);
}
this.articleService.sendMessage(result); // <-- Subject.next()
let url = `/article/${id}`;
this.router.navigate([url]); // <-- Subject.subscribe() after Subject.next(), so value already emitted
You already added BehaviorSubject tag. So use it. Also, getMessage(): Observable<any> { doesnt do anything except returns Observable. Feels redundant:
private detailSubject = new BehaviorSubject<any>(null);
message$ = this.detailSubject.asObservable();
sendMessage(formData: any) {
this.detailSubject.next({formData});
}
And
ngOnInit() {
this.articleService.message$.subscribe(...

Admin on rest - implementing aor-realtime

I'm having a real hard time understanding how to implement aor-realtime (trying to use it with firebase; reads only, no write).
The first place I get stuck: This library generates a saga, right? How do I connect that with a restClient/resource? I have a few custom sagas that alert me on errors, but there is a main restClient/resource backing those. Those sagas just handles some side-effects. In this case, I just don't understand what the role of the client is, and how it interacts with the generated saga (or visa-versa)
The other question is with persistence: Updates stream in and the initial set of records is not loaded in one go. Should I be calling observer.next() with each update? or cache the updated records and call next() with the entire collection to-date.
Here's my current attempt at doing the later, but I'm still lost with how to connect it to my Admin/Resource.
import realtimeSaga from 'aor-realtime';
import { client, getToken } from '../firebase';
import { union } from 'lodash'
let cachedToken
const observeRequest = path => (type, resource, params) => {
// Filtering so that only chats are updated in real time
if (resource !== 'chat') return;
let results = {}
let ids = []
return {
subscribe(observer) {
let databaseRef = client.database().ref(path).orderByChild('at')
let events = [ 'child_added', 'child_changed' ]
events.forEach(e => {
databaseRef.on(e, ({ key, val }) => {
results[key] = val()
ids = union([ key ], ids)
observer.next(ids.map(id => results[id]))
})
})
const subscription = {
unsubscribe() {
// Clean up after ourselves
databaseRef.off()
results = {}
ids = []
// Notify the saga that we cleaned up everything
observer.complete();
}
};
return subscription;
},
};
};
export default path => realtimeSaga(observeRequest(path));
How do I connect that with a restClient/resource?
Just add the created saga to the custom sagas of your Admin component.
About the restClient, if you need it in your observer, then pass it the function which return your observer as you did with path. That's actually how it's done in the readme.
Should I be calling observer.next() with each update? or cache the updated records and call next() with the entire collection to-date.
It depends on the type parameter which is one of the admin-on-rest fetch types:
CRUD_GET_LIST: you should return the entire collection, updated
CRUD_GET_ONE: you should return the resource specified in params (which should contains its id)
Here's the solution I came up with, with guidance by #gildas:
import realtimeSaga from "aor-realtime";
import { client } from "../../../clients/firebase";
import { union } from "lodash";
const observeRequest = path => {
return (type, resource, params) => {
// Filtering so that only chats are updated in real time
if (resource !== "chats") return;
let results = {}
let ids = []
const updateItem = res => {
results[res.key] = { ...res.val(), id: res.key }
ids = Object.keys(results).sort((a, b) => results[b].at - results[a].at)
}
return {
subscribe(observer) {
const { page, perPage } = params.pagination
const offset = perPage * (page - 1)
const databaseRef = client
.database()
.ref(path)
.orderByChild("at")
.limitToLast(offset + perPage)
const notify = () => observer.next({ data: ids.slice(offset, offset + perPage).map(e => results[e]), total: ids.length + 1 })
databaseRef.once('value', snapshot => {
snapshot.forEach(updateItem)
notify()
})
databaseRef.on('child_changed', res => {
updateItem(res)
notify()
})
const subscription = {
unsubscribe() {
// Clean up after ourselves
databaseRef.off();
// Notify the saga that we cleaned up everything
observer.complete();
}
};
return subscription;
}
};
}
};
export default path => realtimeSaga(observeRequest(path));

ng2: reserve Original value if validation failed

I am trying to force the user to fill in the description when they update an item. It validates, shows error message when validation fails, stops running execution and doesn't update an item.
Please see the series of screenshot below:
However, my item is still updated even if the validation fails. It seems to me that since an object is reference in the memory, it's still updated even if it doesn't run updateTodo() method from the Todoservice.
Is it because I am just hardcoding my items just for the testing? I am very new to Angular and I don't want to implement webAPIs at this point yet.
I tried to use Object.assign({}, copy) in getTodoItem(id: number) to clone and decouple my todoItem from the list but the error message showing that it's not observable.
How can I preserve the values of Objects in the list if the validation fails? In real life application, Since we retrieve the data from the database (or webapi cache) whenever index/list component is navigated, this problem shouldn't occur. Is my assumption right?
todoService.ts
import { Itodo } from './todo'
const TodoItems: Itodo[] = [
{ todoId: 11, description: 'Silencer' },
{ todoId: 12, description: 'Centaur Warrunner' },
{ todoId: 13, description: 'Lycanthrope' },
{ todoId: 14, description: 'Sniper' },
{ todoId: 15, description: 'Lone Druid' }
]
#Injectable()
export class TodoService {
getTodoItems(): Observable<Itodo[]> {
return Observable.of(TodoItems);
}
getTodoItem(id: number): Observable<Itodo> {
return this.getTodoItems()
.map((items: Itodo[]) => items.find(p => p.todoId === id));
//let copy = this.getTodoItems()
// .map((items: Itodo[]) => items.find(p => p.todoId === id));
//return Object.assign({}, copy);
}
addNewTodo(model: Itodo): number {
return TodoItems.push(model); // return new length of an array
}
updateTodo(model: Itodo) : number {
let idx = TodoItems.indexOf(TodoItems.filter(f => f.todoId == model.todoId)[0]);
return TodoItems.splice(idx, 1, model).length; // return the count of affected item
}
}
todo-edit.component.ts -- EditItem() is the main
import { Subscription } from 'rxjs/Subscription';
import { Itodo } from './todo'
import { TodoService } from './todo.service';
#Component({
moduleId: module.id,
templateUrl: "todo-edit.component.html"
})
export class TodoEditComponent implements OnInit, OnDestroy {
todoModel: Itodo;
private sub: Subscription;
Message: string;
MessageType: number;
constructor(private _todoService: TodoService,
private _route: ActivatedRoute,
private _router: Router) {
}
ngOnInit(): void {
this.sub = this._route.params.subscribe(
params => {
let id = +params['id'];
this.getItem(id);
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
getItem(id: number) {
this._todoService.getTodoItem(id).subscribe(
item => this.todoModel = item,
error => this.Message = <any>error);
}
EditItem(): void {
this.todoModel.description = this.todoModel.description.trim();
if (!this.todoModel.description) {
this.Message = "Description must not be blank.";
this.MessageType = 2;
return;
}
console.log('valid: update now.');
let result = this._todoService.updateTodo(this.todoModel);
if (result > 0) {
this.Message = "An Item has been updated";
this.MessageType = 1;
}
else {
this.Message = "Error occured! Try again.";
this.MessageType = 2;
}
}
}
Working Solution
Object.assign it's the right method to use. I was using it wrongly in the service to clone it. You need to use it in your component, not in the service.
getItem(id: number) {
//Object.assign clone and decouple todoModel from the ArrayList
this._todoService.getTodoItem(id).subscribe(
item => this.todoModel = Object.assign({}, item),
error => this.Message = <any>error);
}
Validation does not prevent updating items, it just checks actual values for validity. You should create copy of object for editing to be able to rollback changes. You can use Object.assign({}, item) or JSON.parse(JSON.stringify(...)).

Angular 2 HTTP Progress bar

Is there currently a way within Angular 2 to retrieve the progress (i.e. percentage done) of an ajax call, using the angular2/http module?
I use the following code to make my HTTP calls:
let body = JSON.stringify(params);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
this.http.post(url, body, options)
.timeout(10000, new Error('Timeout exceeded during login'))
.toPromise()
.then((res) => {
...
}).catch((err) => {
...
});
The goal is to write a synchronisation system. The post will return a lot of data, and I want to give the user an indication on how long the syncing will take.
Currently (from v. 4.3.0, when using new HttpClient from #ngular/common/http) Angular provides listening to progress out of the box. You just need to create HTTPRequest object as below:
import { HttpRequest } from '#angular/common/http';
...
const req = new HttpRequest('POST', '/upload/file', file, {
reportProgress: true,
});
And when you subscribe to to request you will get subscription called on every progress event:
http.request(req).subscribe(event => {
// Via this API, you get access to the raw event stream.
// Look for upload progress events.
if (event.type === HttpEventType.UploadProgress) {
// This is an upload progress event. Compute and show the % done:
const percentDone = Math.round(100 * event.loaded / event.total);
console.log(`File is ${percentDone}% uploaded.`);
} else if (event instanceof HttpResponse) {
console.log('File is completely uploaded!');
}
});
More info here.
You could leverage the onprogress event provided by XHR (see this plunkr: http://plnkr.co/edit/8MDO2GsCGiOJd2y2XbQk?p=preview).
This allows to get hints about the progress of the download. This isn't supported out of the box by Angular2 but you can plug it by extended the BrowserXhr class:
#Injectable()
export class CustomBrowserXhr extends BrowserXhr {
constructor(private service:ProgressService) {}
build(): any {
let xhr = super.build();
xhr.onprogress = (event) => {
service.progressEventObservable.next(event);
};
return <any>(xhr);
}
}
and override the BrowserXhr provider with the extended:
bootstrap(AppComponent, [
HTTP_PROVIDERS,
provide(BrowserXhr, { useClass: CustomBrowserXhr })
]);
See this question for more details:
Angular2 / RxJS - updating variable after getting data from Http observable
When you make http cals in angular2, it returns an Observable of type Response, this response is created inside class called XHRConnection where all the magic happens.
The XHRConnection builds the response by listening to XMLHttpRequest's load event, this means it will return one response at the end of the request.
Now to be able to alter this behavior we need to make our connection class listen to the progress event.
So we need to create custom Connection class, to handle the response as we see fit.
I did it this way,
Take note that my php API returns multi response in a single request and this responses are plain strings.
my_backend.ts
import {Injectable} from "angular2/core";
import {Observable} from "rxjs/Observable";
import {Observer} from "rxjs/Observer";
import {Connection,ConnectionBackend} from "angular2/src/http/interfaces";
import {ReadyState, RequestMethod, ResponseType} from "angular2/src/http/enums";
import {ResponseOptions} from "angular2/src/http/base_response_options";
import {Request} from "angular2/src/http/static_request";
import {Response} from "angular2/src/http/static_response";
import {BrowserXhr} from "angular2/src/http/backends/browser_xhr";
import {Headers} from 'angular2/src/http/headers';
import {isPresent} from 'angular2/src/facade/lang';
import {getResponseURL, isSuccess} from "angular2/src/http/http_utils"
export class MyConnection implements Connection {
readyState: ReadyState;
request: Request;
response: Observable<Response>;
constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {
this.request = req;
this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
let _xhr: XMLHttpRequest = browserXHR.build();
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
// save the responses in array
var buffer :string[] = [];
// load event handler
let onLoad = () => {
let body = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText;
//_xhr.respons 1 = "Loading data!"
//_xhr.respons 2 = "Loading data!Ready To Receive Orders."
// we need to fix this proble
// check if the current response text contains the previous then subtract
// NOTE: I think there is better approach to solve this problem.
buffer.push(body);
if(buffer.length>1){
body = buffer[buffer.length-1].replace(buffer[buffer.length-2],'');
}
let headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
let url = getResponseURL(_xhr);
let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
let state:number = _xhr.readyState;
if (status === 0) {
status = body ? 200 : 0;
}
var responseOptions = new ResponseOptions({ body, status, headers, url });
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
let response = new Response(responseOptions);
//check for the state if not 4 then don't complete the observer
if(state !== 4){
//this will return stream of responses
responseObserver.next(response);
return;
}
else{
responseObserver.complete();
return;
}
responseObserver.error(response);
};
// error event handler
let onError = (err: any) => {
var responseOptions = new ResponseOptions({ body: err, type: ResponseType.Error });
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
};
if (isPresent(req.headers)) {
req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
}
_xhr.addEventListener('progress', onLoad);
_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
_xhr.send(this.request.text());
return () => {
_xhr.removeEventListener('progress', onLoad);
_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();
};
});
}
}
#Injectable()
export class MyBackend implements ConnectionBackend {
constructor(private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions) {}
createConnection(request: Request):MyConnection {
return new MyConnection(request, this._browserXHR, this._baseResponseOptions);
}
}
And in the app.component.ts
import {Component, provide} from 'angular2/core';
import {HTTP_PROVIDERS,XHRBackend} from 'angular2/http';
import {MyBackend} from './my_backend';
#Component({
selector: 'my-app',
providers: [
HTTP_PROVIDERS,
MyBackend,
provide(XHRBackend, {useExisting:MyBackend})
]
.
.
.
Now calling http.get will return a steam of responses.
#Bartek Chichoki's answer is correct but it was not working for my case.
Adding observe: 'events' did the trick for me
const req = new HttpRequest('POST', '/upload/file', file, {
reportProgress: true,
observe: 'events'
});
Hope it helps
I strongly recomend using this
https://www.npmjs.com/package/angular-progress-http
otherwise messing around with xhr will make you miss sessions cookies and other stuffs
besides it'll be more portable and way easier to implement

only allow children of a specific type in a react component

I have a Card component and a CardGroup component, and I'd like to throw an error when CardGroup has children that aren't Card components. Is this possible, or am I trying to solve the wrong problem?
For React 0.14+ and using ES6 classes, the solution will look like:
class CardGroup extends Component {
render() {
return (
<div>{this.props.children}</div>
)
}
}
CardGroup.propTypes = {
children: function (props, propName, componentName) {
const prop = props[propName]
let error = null
React.Children.forEach(prop, function (child) {
if (child.type !== Card) {
error = new Error('`' + componentName + '` children should be of type `Card`.');
}
})
return error
}
}
You can use the displayName for each child, accessed via type:
for (child in this.props.children){
if (this.props.children[child].type.displayName != 'Card'){
console.log("Warning CardGroup has children that aren't Card components");
}
}
You can use a custom propType function to validate children, since children are just props. I also wrote an article on this, if you want more details.
var CardGroup = React.createClass({
propTypes: {
children: function (props, propName, componentName) {
var error;
var prop = props[propName];
React.Children.forEach(prop, function (child) {
if (child.type.displayName !== 'Card') {
error = new Error(
'`' + componentName + '` only accepts children of type `Card`.'
);
}
});
return error;
}
},
render: function () {
return (
<div>{this.props.children}</div>
);
}
});
For those using a TypeScript version.
You can filter/modify components like this:
this.modifiedChildren = React.Children.map(children, child => {
if (React.isValidElement(child) && (child as React.ReactElement<any>).type === Card) {
let modifiedChild = child as React.ReactElement<any>;
// Modifying here
return modifiedChild;
}
// Returning other components / string.
// Delete next line in case you dont need them.
return child;
});
Use the React.Children.forEach method to iterate over the children and use the name property to check the type:
React.Children.forEach(this.props.children, (child) => {
if (child.type.name !== Card.name) {
console.error("Only card components allowed as children.");
}
}
I recommend to use Card.name instead of 'Card' string for better maintenance and stability in respect to uglify.
See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
One has to use "React.isValidElement(child)" along with "child.type" if one is working with Typescript in order to avoid type mismatch errors.
React.Children.forEach(props.children, (child, index) => {
if (React.isValidElement(child) && child.type !== Card) {
error = new Error(
'`' + componentName + '` only accepts children of type `Card`.'
);
}
});
You can add a prop to your Card component and then check for this prop in your CardGroup component. This is the safest way to achieve this in React.
This prop can be added as a defaultProp so it's always there.
class Card extends Component {
static defaultProps = {
isCard: true,
}
render() {
return (
<div>A Card</div>
)
}
}
class CardGroup extends Component {
render() {
for (child in this.props.children) {
if (!this.props.children[child].props.isCard){
console.error("Warning CardGroup has a child which isn't a Card component");
}
}
return (
<div>{this.props.children}</div>
)
}
}
Checking for whether the Card component is indeed a Card component by using type or displayName is not safe as it may not work during production use as indicated here: https://github.com/facebook/react/issues/6167#issuecomment-191243709
I made a custom PropType for this that I call equalTo. You can use it like this...
class MyChildComponent extends React.Component { ... }
class MyParentComponent extends React.Component {
static propTypes = {
children: PropTypes.arrayOf(PropTypes.equalTo(MyChildComponent))
}
}
Now, MyParentComponent only accepts children that are MyChildComponent. You can check for html elements like this...
PropTypes.equalTo('h1')
PropTypes.equalTo('div')
PropTypes.equalTo('img')
...
Here is the implementation...
React.PropTypes.equalTo = function (component) {
return function validate(propValue, key, componentName, location, propFullName) {
const prop = propValue[key]
if (prop.type !== component) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
};
}
You could easily extend this to accept one of many possible types. Maybe something like...
React.PropTypes.equalToOneOf = function (arrayOfAcceptedComponents) {
...
}
static propTypes = {
children : (props, propName, componentName) => {
const prop = props[propName];
return React.Children
.toArray(prop)
.find(child => child.type !== Card) && new Error(`${componentName} only accepts "<Card />" elements`);
},
}
I published the package that allows to validate the types of React elements https://www.npmjs.com/package/react-element-proptypes :
const ElementPropTypes = require('react-element-proptypes');
const Modal = ({ header, items }) => (
<div>
<div>{header}</div>
<div>{items}</div>
</div>
);
Modal.propTypes = {
header: ElementPropTypes.elementOfType(Header).isRequired,
items: React.PropTypes.arrayOf(ElementPropTypes.elementOfType(Item))
};
// render Modal
React.render(
<Modal
header={<Header title="This is modal" />}
items={[
<Item/>,
<Item/>,
<Item/>
]}
/>,
rootElement
);
To validate correct children component i combine the use of react children foreach and the Custom validation proptypes, so at the end you can have the following:
HouseComponent.propTypes = {
children: PropTypes.oneOfType([(props, propName, componentName) => {
let error = null;
const validInputs = [
'Mother',
'Girlfried',
'Friends',
'Dogs'
];
// Validate the valid inputs components allowed.
React.Children.forEach(props[propName], (child) => {
if (!validInputs.includes(child.type.name)) {
error = new Error(componentName.concat(
' children should be one of the type:'
.concat(validInputs.toString())
));
}
});
return error;
}]).isRequired
};
As you can see is having and array with the name of the correct type.
On the other hand there is also a function called componentWithName from the airbnb/prop-types library that helps to have the same result.
Here you can see more details
HouseComponent.propTypes = {
children: PropTypes.oneOfType([
componentWithName('SegmentedControl'),
componentWithName('FormText'),
componentWithName('FormTextarea'),
componentWithName('FormSelect')
]).isRequired
};
Hope this help some one :)
Considered multiple proposed approaches, but they all turned out to be either unreliable or overcomplicated to serve as a boilerplate. Settled on the following implementation.
class Card extends Component {
// ...
}
class CardGroup extends Component {
static propTypes = {
children: PropTypes.arrayOf(
(propValue, key, componentName) => (propValue[key].type !== Card)
? new Error(`${componentName} only accepts children of type ${Card.name}.`)
: null
)
}
// ...
}
Here're the key ideas:
Utilize the built-in PropTypes.arrayOf() instead of looping over children
Check the child type via propValue[key].type !== Card in a custom validator
Use variable substitution ${Card.name} to not hard-code the type name
Library react-element-proptypes implements this in ElementPropTypes.elementOfType():
import ElementPropTypes from "react-element-proptypes";
class CardGroup extends Component {
static propTypes = {
children: PropTypes.arrayOf(ElementPropTypes.elementOfType(Card))
}
// ...
}
An easy, production friendly check. At the top of your CardGroup component:
const cardType = (<Card />).type;
Then, when iterating over the children:
React.children.map(child => child.type === cardType ? child : null);
The nice thing about this check is that it will also work with library components/sub-components that don't expose the necessary classes to make an instanceof check work.
Assert the type:
props.children.forEach(child =>
console.assert(
child.type.name == "CanvasItem",
"CanvasScroll can only have CanvasItem component as children."
)
)
Related to this post, I figured out a similar problem I had. I needed to throw an error if a child was one of many icons in a Tooltip component.
// icons/index.ts
export {default as AddIcon} from './AddIcon';
export {default as SubIcon} from './SubIcon';
...
// components/Tooltip.tsx
import { Children, cloneElement, isValidElement } from 'react';
import * as AllIcons from 'common/icons';
...
const Tooltip = ({children, ...rest}) => {
Children.forEach(children, child => {
// ** Inspired from this post
const reactNodeIsOfIconType = (node, allIcons) => {
const iconTypes = Object.values(allIcons);
return iconTypes.some(type => typeof node === 'object' && node !== null && node.type === type);
};
console.assert(!reactNodeIsOfIconType(child, AllIcons),'Use some other component instead...')
})
...
return Children.map(children, child => {
if (isValidElement(child) {
return cloneElement(child, ...rest);
}
return null;
});
}

Resources