In Reactjs how do I update the correct value in the parent state with a callback that's been passed to a nested child component? - callback

I've been on this one for days, and all my reading hasn't helped me find a clean solution for this particular case.
Issue
I can send a parent state value and callback down to a nested component, but once the callback is triggered in the child I don't know how I can send the updated value back to the parent so it can update the correct value.
For instance
Parent Component (Has values and the callback)
Child Component (Values and callback is passed here)
Grand Child Component (Values Updated here and callback triggered)
What is SEEMS to cause the Issue
It seems the issue is I need the original key name in order for "setState" to update the correct value in the parent component(or at least it seems that way), but the child component only has original value and new updated value and has no access to the key associated with original value in the parent component.
Important Notes on Best Practice Surrounding this question
-From what I understand it is bad practice to use refs to handle nested situations like this.
-It seems like there is a cleaner solution than sending a prop for the key and another for the value.
-I'm assuming also that flux might provide a solution to this issue but I feel that there is a basic component to component communication technique or principle that I'm missing here.
Here is a bare bones example of what I'm dealing with.
/*All the values need to be updated here so that the inputs can used for calculation and then sent to a component that displays the output*/
var Calculator =
React.createClass({
getInitialState:function(){
return {
value1: "Enter value 1", /*These values are passed to a nested child component, can't figure how to update the right one*/
value2: "Enter value 2",
}
},
update: function(update){
this.setState(
update
);
},
render: function () {
return (
<div>
<h2>Input</h2>
<Input onClick={this.handleClick} update={this.update} value1={this.state.value1} value2={this.state.value2} /> //pass the values here
<h2>Output</h2>
<Output />
</div>
);
},
handleClick: function () {
//want to update the state for the correct value here
}
});
/* A compenent that is a middle layer between the parent and nested child component I'm working with*/
var Input =
React.createClass({
update: function(){
this.props.update();
},
render:function(){
return (
<div>
<p><InputComponent update={this.update} value={this.props.value1} /> / <InputComponent value={this.props.value2}/></p>//passing down values again
<p><ButtonComponent onClick={this.props.onClick} /></p>
</div>
)
}
});
/*This is the child component that gets the value and call back from the top level component. It will get updates to the values and send them back to change state of the parent component.*/
var InputComponent =
React.createClass({
handleChange: function(event) {
this.props.update();
},
render: function() {
return <input type="text" value={this.props.value} onChange={this.handleChange} />; //this props value has no key associated with it. Cant't make update object ie {originalkey:newValue}
}
});
/* This component is triggered to carry out calculations in the parent class.*/
var ButtonComponent =
React.createClass({
render:function(){
return <button onClick={this.handleClick}> {this.props.txt} </button>
},
handleClick: function(){
this.props.onClick();
}
});
/*The inputs will be calculated and turned to outputs that will displayed here.This component doesn't matter for the question so I left it empty*/
var Output =
React.createClass({
});

Here's an example I just put together on jsfiddle.
Instead of putting update in setState, we pass a value to update from the child component and let the parent set its state.
In the parent, we have:
_update: function(val){
this.setState({
msg: val
});
},
render: function() {
return (
<div>
<p>Message: {this.state.msg}</p>
<Child _update={this._update} />
</div>
);
}
And in the child, we have a _handleClick function that calls the parent _update function with values:
_handleClick: function(){
this.props._update(React.findDOMNode(this.refs.text).value);
},
render: function(){
return (
<div>
<input type="text" ref="text" />
<button onClick={this._handleClick}>Update</button>
</div>
);
}

Related

Kendo UI Observable calculated property how to force change notification

In cases using Kendo UI Observable (MVVM) for HTML element binding I occasionally use calculated values, but I can't find a way to force the binding to update when the dependent values change. A simple example:
<div id="test3">
1. <span data-bind="text: addr"></span><br />
2. <span data-bind="text: addr1"></span><br />
3. <span data-bind="text: addr2"></span><br />
</div>
<script>
viewModel = kendo.observable({
addr1: "",
addr2: "",
addr: function () {
return this.addr1 + ' ' + this.addr2;
},
load: function () {
this.set("addr1", "123 Main St");
this.set("addr2", "STE 101");
//need to let view model know to update addr binding
}
});
//to demonstrate the problem of notification bind here
kendo.bind($("#test3"), viewModel);
viewModel.load();
//if I bind here it works, of course
//kendo.bind($("#test3"), viewModel);
</script>
I have a work-around for this where I set the 'addr' element directly, but that defeats the point of data binding.
Change your addr function to use the 'get' function to retrieve the values:
addr: function () {
return this.get("addr1") + ' ' + this.get("addr2");
},
The 'get' function is part of the kendo observable framework. When used like this, kendo will then know to refresh the bound value from your function when any of the values within it, accessed via 'get', are 'set' elsewhere.

Clearing form input in Meteor

I have tried multiple options that I have found on SO and elsewhere for clearing form inputs, all listed below in the code, but nothing seems to work. Is there anything specific about this form that would determine which one I should use?
<template name="CompanyAdd">
<div>
<form class="form-inline">
<div class="form-group">
{{> inputAutocomplete settings=companySettings id="companyAdd" name="companyAdd" class="input-xlarge" autocomplete="off" placeholder="Add Company"}}
</div>
<button type="submit" class="btn btn-default company-add">Add</button>
</form>
</div>
</template
Template.CompanyAdd.events({
'submit form': function(e) {
e.preventDefault();
var selection = $(e.target).find('[id=companyAdd]').val();
var company = {
ticker: selection
};
if(Companies.findOne({ticker:selection})) {
console.log("Do nothing");
} else {
Meteor.call('companyAdd', company, function(error, result) {
});
}
//event.target.reset();
//e.target.reset();
//target.text.value = '';
//template.find("form").reset();
//document.getElementById("companyAdd").reset();
}
});
Given that you have
var selection = $(e.target).find('[id=companyAdd]').val();
That is the input you want to clear and that - I assume - works, I would do:
var field = $(e.target).find('[id=companyAdd]');
var selection = field.val();
...
field.val('')
Otherwise if you wish to reset all form, go for #JeremyK`s #reset.
Your second attempt:
e.target.reset();
should work fine. If it is not working, check if there are any errors in the console and report back here.
The handler function receives two arguments: event, an object with
information about the event, and template, a template instance for the
template where the handler is defined.
In your code above you define your handler like this:
'submit form': function(e) {
You have named the event argument e, and discarded the template argument.
e is has information about the event
e.target is the form element (The event was defined on 'submit form')
e.target.reset succeeds because reset is a valid function to call on a form.
Briefly, your other attempts failed because:
event.target.reset(); event is not defined or passed in, at least not with the name event (you used e)
target.text.value = ''; target is an undefined variable
template.find("form").reset(); this fails because template is undefined. If you change your handler definition to receive the template variable, this will work (change 'submit form': function(e) to 'submit form': function(e, template)
document.getElementById("companyAdd").reset(); This fails because the element with the id companyAdd is the input element, not the form, so .reset() is undefined. You could change this to document.getElementById("companyAdd").text.value = ''

How do I use React and forms to get an array of checked checkbox values?

I am trying to build a filter for my portfolio website. Checkboxes that let you pick a technology (react, redux, jquery etc.) to display a piece of work(s) that contain(s) that/those technologies. So every time the user clicks on a box, I want to add the value (JavaScript, Redux, React etc.) to an array that I use in another function to check against my portfolio pieces and filter out what isn't there.
I am finding this very difficult and I think it should be quite simple. Can someone point me in the right direction? Is there a way to simply have a function trigger (onChange callback?) that reads the checked/unchecked status of my form input elements and then updates my state array accordingly? Can I get the status of all the checkboxes simply in React? Do I need to have individual state of checked/unchecked for my checkboxes?
It seems that jQuery makes it pretty possible with selectors with:
$('input[type="checkbox"]:checked').each(function () {}
If you don't care about the order and you just want to append the items to the array as they appear we could definitely do exactly what you suggest in your question. On the change event of the checkbox check if the box is checked or or unchecked (event.target.checked returns true if checked or false if unchecked) and handle the array logic accordingly. this is a simple representation of how that could work:
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Portfolio extends Component {
constructor() {
super()
// initialize your options array on your state
this.state = {
options: []
}
}
onChange(e) {
// current array of options
const options = this.state.options
let index
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(+e.target.value)
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(+e.target.value)
options.splice(index, 1)
}
// update the state with the new array of options
this.setState({ options: options })
}
render() {
return (
<main className='portfolio'>
<form>
<div className="input-group">
<label>cb1</label>
<input type="checkbox" value={1} onChange={this.onChange.bind(this)} />
</div>
<div className="input-group">
<label>cb2</label>
<input type="checkbox" value={2} onChange={this.onChange.bind(this)} />
</div>
<div className="input-group">
<label>cb3</label>
<input type="checkbox" value={3} onChange={this.onChange.bind(this)} />
</div>
</form>
<div className="selected-items">
{this.state.options.map(number =>
<p key={number}>item: {number}</p>
)}
</div>
</main>
)
}
}
if you DO care about order, if you can append numerical values to the array like I did in this example you could easily give your checkboxes sorted numerical values and you could sort the array before updating your state so it's always in a certain order regardless of the order they are checked.
onChange(e) {
// current array of options
const options = this.state.options
let index
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(+e.target.value)
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(+e.target.value)
options.splice(index, 1)
}
// sort the array
options.sort()
// update the state with the new array of options
this.setState({ options: options })
}
Here's how I'm doing it:
// util.js
import getPath from 'lodash/get';
import setIn from 'lodash/fp/set';
export function linkMultiCheck(name, value) {
return {
checked: getPath(this.state, name, []).includes(value),
onChange: ev => {
let values = getPath(this.state, name, []);
if(ev.target.checked) {
values = [...values, value];
} else {
values = values.filter(v => v !== value);
}
this.setState(setIn(name, values));
},
}
}
// form.js
<ul>
{options.branches.map(branch => (
<li key={branch.id} className="checkbox">
<label>
<input type="checkbox" name={this.id} {...this::linkMultiCheck('formData.branchIds',branch.id)}/>
{branch.id}
</label>
</li>
))}
</ul>
i.e., if a checkbox is checked, append it to the current array of values. If it's unchecked, filter it out.
I'm using lodash here so that we can set deeply nested state values using dot notation.

How to retrieve the stored value from goinstant

I'm doing something wrong. I'm attempting to get the stored value I have in goinstant. I have a person room with a userName. The value the alert function displays is "[object Object]". Here is my code: (I left out the scripts intentionally). I provided a quick screen shot of my person data on goInstant for reference http://screencast.com/t/BtLqfrorg
<h2>Angular JS Test</h2>
<div ng-app="testapp" data-ng-controller="personCtrl">
<input type="text" ng-model="userName" />{{ userName }}
<button type="submit" id="save" name="save" >Save</button>
<script>
var testApp = angular.module('testapp', ['goangular']);
testApp.config(function($goConnectionProvider) {
$goConnectionProvider.$set('https://goinstant.net/<mykey>/test');
});
testApp.controller('personCtrl', function($scope, $goKey) {
// $goKey is available
$scope.userName = $goKey('/person/userName').$sync();
alert($scope.userName);
});
</script>
</div>
Your example would indicate that you expect $scope.userName to be a primitive value (a string). It is in fact, a model. Models provide a simple interface for updating the state of your application, and in GoAngular, that state is persisted to your GoInstant App auto-magically.
You can find more documentation on the GoAngular Model here. I thought a working example might help, so I've created a Plunker. Let's work through the script.js:
angular
.module('TestThings', ['goangular'])
.config(function($goConnectionProvider) {
$goConnectionProvider.$set('https://goinstant.net/mattcreager/DingDong');
})
.controller('TestCtrl', function($scope, $goKey) {
// Create a person model
$scope.person = $goKey('person').$sync();
// Observe the model for changes
$scope.$watchCollection('person', function(a, b) {
console.log('model is', a.$omit()); // Log current state of person
console.log('model was', b.$omit()); // Log the previous state of person
});
// After 2000 ms set the userName property of the person model
setTimeout(function() {
$scope.person.$key('userName').$set('Luke Skywalker');
}, 2000);
// Set the userName property of the person model
$scope.person.$key('userName').$set('Darth Vader');
});

Enforcing Case Insensitive uniqueness on fields at a given nesting level using angularjs

<FORM>
<DIV class="outer-class">
<INPUT class="toValidate" type = "text"/>
<INPUT class="somethingElse" type= "text"/>
<INPUT class="toValidate" type ="text"/>
</DIV>
<DIV class="outer-class">
<INPUT class="toValidate" type = "text"/>
<INPUT class="somethingElse" type= "text"/>
<INPUT class="toValidate" type ="text"/>
</DIV>
<INPUT type="submit"/>
</FORM>
My question is: How do I ensure that for the form to be valid, the nested toValidates have a unique value but only within the same outer div?
I am guessing this logic should go in an OuterClassDirective, but I can't seem to figure out what the logic should look like?
Any advice would be appreciated.
Thanks!
What about this. Your outerClassDirective should have a controller, which will store used values in an array. It will transclude the input fields in its body. Your toValidate directive requires outerClassDirective and adds the model value to the array, making it invalid if exists.
Here is a try (untested):
app.directive('outerClass', function() {
var values = [];
var valid = true;
return {
template: '<div ng-transclude></div>',
transclude: true,
replace: true,
require: 'ngModel',
controller: function() {
this.addValue: function(value) {
valid = values.indexOf(value) > -1;
values.push(value);
};
},
link: function(scope, elm, attrs, ctrl) {
ctrl.$setValidity('toValidate', valid)
}
};
});
app.directive('toValidate', function() {
return {
require: '^outerClass',
link: function(scope, elm, attrs, ctrl) {
ctrl.addValue(attrs.value);
}
}
};
});
The 'tabs' and 'pane' directives on the Angular home page solve a similar issue -- the child 'pane' directives need to communicate with the parent 'tabs' directive.
Define a controller on the outerclass directive, and then define a method on the controller (use this not $scope). Then require: '^outerclass' in the toValidate directive. In the toValidate link function, you can $watch for value changes and call the method on the outerclass controller to pass the value up. Do the validation in the outerclass directive.
See also 'this' vs $scope in AngularJS controllers.