render() method is not correctly called after props update - redux

I'm doing a very simple react+redux application where I've a reducer called goals and a container called GoalsContainer.
From App Container I call the action goal for load the initial goals from a local db(indexedDB)
dispatch(loadGoals(currentDate));
This call the loadGoals from the goals actions:
export function loadGoals(currentDate = new Date()){
return dispatch => {
var goals = getGoalsFromDB(normalizeDate(currentDate)); // with this I get an array from the db
dispatch(setLoadGoals(goals));
}
}
function setLoadGoals(goals) {
return {
type: types.LOAD_GOALS,
goals
};
}
And then in my reducer I've this:
export default function goals(state = [], action) {
switch(action.type) {
case types.LOAD_GOALS:
return action.goals; // here I set the state of the goal reducer with the array passed via action
default:
console.log('Im here');
return state;
}
}
and this is my GoalsContainer(read the comments in code):
class GoalsContainer extends React.Component {
render() {
if (this.props.goals != undefined) {
console.log('ok called the render'); // in chrome console shows it
console.log(this.props.goals); // in chrome console shows correctly the goals loaded
console.log(this.props.goals.length); // it say 2
if (this.props.goals.length > 0) { // here fails...
console.log('good');
console.log(this.props.goals);
var goalsView = <div>There are goals</div>
}
else {
console.log('why go here?'); // go here
console.log(this.props.goals);
var goalsView = <div>No goals</div>
}
} else {
var goalsView = <div>Undefined</div>
}
return (
<div id="goals-main">
{goalsView}
</div>
);
}
}
GoalsContainer.propTypes = propTypes;
function mapStateToProps(state) {
const { goals, environment } = state;
const { currentDate } = environment;
return {
goals,
currentDate
}
}
export default connect(mapStateToProps)(GoalsContainer);
The problem is that when it does the if check, it fails(like if there are 0 goals), but in chrome console show correctly the goals array...
Then if I force with some workaround the render(), all works correctly.
What I've done wrong ?

You didn't mention if you use https://github.com/gaearon/redux-thunk or not. To use reducer returning function you should definitely install it.

It's hard to follow all of the parts of your code from random gists. What happens if you change your GoalsContainer to be;
class GoalsContainer extends React.Component {
render() {
console.log(this.props.goals);
return (
<div id="goals-main">
{(this.props.goals.length >= 1)?<div>There are goals</div>:<div>Nope!</div>}
</div>
);
}
}
What gets logged to the console?

Related

Aurelia: How to handle a async request in a view?

I have a dotnet core api that returns a FileContentResult..
return new FileContentResult(bytes, contentType)
{
FileDownloadName = Path.GetFileName(request.Filename)
};
Via postman I can read out the image perfectly fine. Now I want to read the image, via the aurelia fetch client, and show it in my html view. This is my function to retrieve the image from the api.
public image(filename: string) {
return this.http.fetch(AppConfiguration.base_url + 'assets/image',
{
method: 'post',
body: json({
filename: filename
})
});
}
I've tried to convert the blob in the response with this value converter. But I can't get that to work
Converter:
export class BlobToUrlValueConverter {
public toView(blob) {
return URL.createObjectURL(blob);
}
}
Viewmodel:
export class Dashboard {
public blob: any;
constructor(
public assets_service: AssetsService
) { }
async attached() {
let response = await this.assets_service.image('test.png');
this.blob = response.blob();
}
}
View
<div if.bind="blob">
${ blob | blobToUrl }
</div>
I'm not sure this is the right approach. Also not sure how handle the async request part of it either. What is the best way to get that image response to show in the html view? Lets say via a img tag?
I was close. Here is how I got the image to show.
Viewmodel:
export class Dashboard {
public url: string;
constructor(
public assets_service: AssetsService
) { }
async attached() {
let blob = await this.assets_service.image('test.png')
.then(response => response.blob());
this.url = URL.createObjectURL(blob);
}
}
View:
<div if.bind="url">
<img src.bind="url">
</div>
EDIT:
Found a better solution using parts written above:
The exported function that does the call (for reusability on both ts and html sides):
export function image_request(filename: string): Promise<Response> {
let http = new Http();
return http.fetch(<your-url-that-fetches-the-image>,
{
method: 'post',
body: json({
filename: filename
})
});
}
Value converter that uses above function
import { image_request } from './AssetsRequests';
export class ImageRequestValueConverter {
public toView(filename: string) {
return image_request(filename);
}
}
The important and most awesome part of the solution. Many thanks to http://www.sobell.net/aurelia-async-bindings/
for getting me on my way. You can override the binding behaviour. You can use this override to process async
Promise in a view in combination with a value converter.
export class AsyncImageBindingBehavior {
public bind(binding, source): void {
binding.originalupdateTarget = binding.updateTarget;
binding.updateTarget = (target) => {
// When we have a promise
if (typeof target.then === 'function') {
// Set temp value to loading so we know its loading
binding.originalupdateTarget('Loading...');
// Process the promise
target
.then(response => response.blob())
.then(blob => binding.originalupdateTarget(
URL.createObjectURL(blob)
));
}
else {
binding.originalupdateTarget(target);
}
};
}
unbind(binding) {
binding.updateTarget = binding.originalupdateTarget;
binding.originalupdateTarget = null;
}
}
Finally the view is very simple
<img src="${ 'test.png' | imageRequest & asyncImage }">

Recursive query to specific depth

I have a tree structure which I'm trying to query to a specific depth. I'm new to relay so not sure about if I'm going about this the right way or even if its possible.
My code is currently looking like this:
class TreeRoot extends React.Component {
render() {
var container = this.props.treeRoot;
return (
<div>
<ViewNode viewNode={container.root} maxDepth={10} expand={true}/>
</div>
);
}
}
class ViewNode extends React.Component {
render() {
var vn = this.props.viewNode;
return (
<div>
<div>{vn.type} {vn.widget} {vn.mode}</div>
<ViewNodeList viewNode={vn} maxDepth={this.props.maxDepth-1}/>
</div>
);
}
}
ViewNode = Relay.createContainer(ViewNode, {
initialVariables:{
maxDepth:1,
expand:false
},
fragments: {
viewNode: (variables) => Relay.QL`
fragment on ViewNode{
id
type
widget
mode
viewNodes #include(if: $expand){
${ViewNode.getFragment("viewNode", {maxDepth:(variables.maxDepth -1),expand:(variables.maxDepth > 0)}).if(variables.expand)}
}
}`,
}
});
class ViewNodeList extends React.Component {
render() {
const vn = this.props.viewNode;
if (!vn.viewNodes){
return (<div></div>);
}
return (
<div>
{vn.viewNodes.map((el, i)=> {
return <ViewNode key={i} viewNode={el} maxDepth={this.props.maxDepth} expand={this.props.maxDepth > 0}></ViewNode>
})
}
</div>
);
};
}
TreeRoot = Relay.createContainer(TreeRoot, {
fragments: {
root: () => Relay.QL`
fragment on TreeRoot{
id
name
root{
${ViewNode.getFragment('viewNode',{maxDepth:10,expand:true})}
}
}
`,
}
}
);
The significant bit being the way I'm trying to control the recursion in the ViewNode component's viewNode fragment. It is attempting to recurse down while decrementing the 'maxDepth' variable and using the 'maxDepth' to calculate the value of the 'expand' variable. Whether to continue recursing is based on the 'expand' var.
Currently this retrieves the root and the first level of children but doesn't recurse as desired. Is what I'm trying to do possible? If it is am I on the right track or going about this in completely the wrong way?
The typical pattern is to create a fragment for the content and then nest the elements in the query. E.g.
fragment ViewContent on ViewNode {
name
}
query ViewQuery {
root {
viewNode {
...ViewContent
viewNode {
...ViewContent
viewNode {
...ViewContent
}
}
}
}
}

Data in one component wont bind to array in injectable

I have following issue:
I have one component, in which I am calling:
this.users = UsersInj.getUsersCollection()
In UsersInj, I have:
#Injectable()
export class UsersInj{
public users:any = [];
constructor(private _http:Http){
this.getUsers().subscribe(
success=>{
this.users = success.json();
},
error =>{
console.log('error')
}
)
}
getUsers(){
return this._http.get('/api/user');
}
getUsersCollection(){
console.log('GET USERS COLLECTION :',this.users);
return this.users;
}
}
However, this.users.length in my component is always 0. Any ideas?
UPDATE
It works when I pack this.users in UsersInj in an object.
PLNKR
In the plunker you copy the values (references) once when TheContent is created.
export class TheContent {
name: any;
constructor(public nameService: NameService) {
console.log("content started");
this.info = nameService.info
this.names = nameService.names;
}
changeMyName() {
this.nameService.change();
}
}
In NameService you assign a new array to this.names.
this.names = success.json();
this.names in TheContent and this.names in NameService are now not connected anymore.
What you probably want to do is
change(){
this.info.name = "Jane";
this.http.get('https://api.github.com/repos/vmg/redcarpet/issues?state=closed').subscribe(success=>{
while(this.names.length > 0) {
this.names.pop();
}
this.names.push.apply(this.names, success.json());
console.log('names: ' + this.names);
});
}
or alternatively copy the new array to TheContent again.
In Angular using an Observable that allows interested parties to subscribe to changes and pass the new value with the notification is the preferred way. See also https://stackoverflow.com/a/35568924/217408

How to use aurelia-validate with a object properties to validate?

I'm using aurelia-validate and my validation works fine if I use variables, but I need it to validate properties of an object rather than a variable:
Here's what works:
import {Validation} from 'aurelia-validation';
import {ensure} from 'aurelia-validation';
import {ItemService} from './service';
export class EditItem {
static inject() {
return [Validation, ItemService];
}
#ensure(function(it){
it.isNotEmpty()
.hasLengthBetween(3,10);
})
name = '';
#ensure(function(it){
it.isNotEmpty()
.hasMinLength(10)
.matches(/^https?:\/\/.{3,}$/) //looks like a url
.matches(/^\S*$/); //no spaces
})
url = '';
constructor(validation, service) {
this.validation = validation.on(this);
this.service = service;
}
activate(params){
return this.service.getItem(params.id).then(res => {
console.log(res);
this.name = res.content.name; //populate
this.url = res.content.url;
});
}
update() {
this.validation.validate().then(
() => {
var data = {
name: this.name,
url: this.url
};
this.service.updateItem(data).then(res => {
this.message = "Thank you!";
})
}
);
}
}
Here's what I'm trying to do (but doesn't work)...also I'm not sure if it's better to keep the properties on the class or have a property called this.item which contains the properties (this is the typical angular way):
import {Validation} from 'aurelia-validation';
import {ensure} from 'aurelia-validation';
import {ItemService} from './service';
export class EditItem {
static inject() {
return [Validation, ItemService];
}
#ensure(function(it){
it.isNotEmpty()
.hasLengthBetween(3,10);
})
this.item.name; //no assignment here should happen
#ensure(function(it){
it.isNotEmpty()
.hasMinLength(10)
.matches(/^https?:\/\/.{3,}$/) //looks like a url
.matches(/^\S*$/); //no spaces
})
this.item.url; //no assignment?
constructor(validation, service) {
this.validation = validation.on(this);
this.service = service;
this.item = null;
}
activate(params){
return this.service.getItem(params.id).then(res => {
console.log(res);
this.item = res.content; //populate with object from api call
});
}
update() {
this.validation.validate().then(
() => {
var data = {
name: this.item.name,
url: this.item.url
};
this.service.updateItem(data).then(res => {
this.message = "Thank you!";
})
}
);
}
}
Can someone give me some guidance here on how to use a validator against an existing object (for an edit page)?
The validation works in all kinds of situations, but using the #ensure decorator can only be used to declare your rules on simple properties (like you found out).
Hence...
Option a: replace the ensure decorator with the fluent API 'ensure' method, this supports 'nested' or 'complex' binding paths such as:
import {Validation} from 'aurelia-validation';
import {ItemService} from './service';
export class EditItem {
static inject() {
return [Validation, ItemService];
}
constructor(validation, service) {
this.validation = validation.on(this)
.ensure('item.url')
.isNotEmpty()
.hasMinLength(10)
.matches(/^https?:\/\/.{3,}$/) //looks like a url
.matches(/^\S*$/)
.ensure('item.name')
.isNotEmpty()
.hasLengthBetween(3,10);
this.service = service;
this.item = null;
}
activate(params){
return this.service.getItem(params.id).then(res => {
console.log(res);
this.item = res.content; //populate with object from api call
});
}
update() {
this.validation.validate().then(
() => {
var data = {
name: this.item.name,
url: this.item.url
};
this.service.updateItem(data).then(res => {
this.message = "Thank you!";
})
}
);
}
}
Note: you can set up your validation even before item is set. Cool, no?
Option b: Since the validation rules are specific to the item, you could move your validation rules inside your item class using the #ensure decorator inside that class instead.
You can then set up validation in your VM after you've retrieved the item: this.validation = validation.on(this.item); or, your service can set up the validation when it returns your item to your VM and make it an intrinsic part of the model: item.validation = validation.on(item);
Option a is easiest and seems to match your experience. Option b is more maintainable, as the validation rules for your model will live on the model, not on the view-model. However if you go with option b, you might have to adjust your HTML a bit to make sure validation hints appear.
Use the .on method of the validator to apply your rules to object properties.
The example below is called after I retrieve an object named stock, it validates that the quantity is not empty and is numeric only. Hope this helps...
let stock = {
name: 'some name'
minimumQuantity: '1'
};
applyRules() {
ValidationRules
.ensure((m: EditStock) => m.minimumQuantity)
.displayName("Minimum Quantity")
.required()
.withMessage(`\${$displayName} cannot be blank.`)
.matches( /^[0-9]*$/)
.withMessage(`\${$displayName} must be numeric only.`)
.on(this.stock);
}

React rendering recursion stops without error

I've encountered a problem with rendering some elements in React.
(I use ImmutableJS)
renderComponents: function(components) {
if(components.isEmpty()) return [];
var table = [];
components.map(function(component) {
table.push(<ComponentTableElement key={ component.get('id') } data={ component } />);
if(component.has('children')) {
var children = component.get('children');
table.concat(this.renderComponents(children));
}
});
return table;
},
As I looked for error, I found that this.renderComponents(children) doesn't return anything at all and the code somehow stops.
I mean before that line everything works ok, but then after this line, when i try to console.log something, it doesn't show up. And it doesn't even reach return table.
So what is wrong with that code?
In the context of the function you pass to map, this refers to the window object, not to the current component instance, so this.renderComponents is undefined when you try to call it.
components.map(function(component) {
this === window;
});
You can pass a value to use as this in the body of your function as the second parameter of Array::map.
components.map(function(component) {
table.push(<ComponentTableElement key={ component.get('id') } data={ component } />);
if(component.has('children')) {
var children = component.get('children');
// here, `this` refers to the component instance
table.concat(this.renderComponents(children));
}
}, this);
If you're using ES6, you can also use fat-arrow functions, which are automatically bound to this.
components.map((component) => {
table.push(<ComponentTableElement key={ component.get('id') } data={ component } />);
if(component.has('children')) {
var children = component.get('children');
// here, `this` refers to the component instance
table.concat(this.renderComponents(children));
}
});

Resources