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

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.

Related

building a basic search bar Material-UI

I want to build a really basic search bar with a search icon (similar to the one on Material-UI ) & invoke a function with the current value of the search field whenever the user hits enter or click on the search enter. I am new to Material-UI & I'm struggling to find my way through the different text fields elements.
I currently have this code
import Input from '#material-ui/core/Input';
class ...somecode
...somecode
constructor(props) {
super(props);
this.state = {
resources: [],
value: '',
};
}
handleChange(event) {
console.log(event.target.value);
this.setState({ value: event.target.value });
}
search(/* access value upons enter/ search icon click */) { <--------------------------
}
...some code
return (
<form id="search">
<Input type="text" value={value} onChange={(event) => { this.handleChange(event); }} placeholder="Search..." autoFocus fullWidth />
</form>
);
p.s: I had a really hard time fiddling around with all the APIs and options available in the input suite (I highly suggest an explanation of how they are related in the docs)

Play framework + scala: Process checkbox list and selected items

I have a list of items I want to show as checkboxes, and I have a list of the items the user actually has selected previously and that I have stored in the database. So what I need is to show all the items but only mark as checked the selected items:
[ ]item1
[ ]item2
[x]item3
[x]item4
Here is my mapping:
def formMapping: Mapping[Data] = {
mapping(
"selectedItems" -> play.api.data.Forms.list(String)
)(Data.apply)(Data.unapply)
}
And my html:
#(theForm: Form[DataForView])
#for((itemValue, itemName) <- allItems) {
<input type="checkbox" name="selectedItems" value="#itemValue"/>#itemName<br/>
}
I'm using the name "selectedItems" so it maps to the form field. I though about using the #checkbox helper but then I would only see the selectedItems, not all items.
I've considered other options, for instance, eliminating "selectedItems" and having a list of all items with a Boolean that indicates if it is selected or not. But that means that I would have to cross the items with the selectedItems and I was hoping there would be a better way.
Any ideas?
UPDATE
I've tried this approach:
#for(item <- allItems) {
#if(theForm("selected").contains(#item.value)) {
<input type="checkbox" name="selected" value="#item.value" CHECKED/>#item.name<br/>
} else {
<input type="checkbox" name="selected" value="#item.value"/>#item.name<br/>
}
}
But theForm("selected") is something like selected[0], selected[1], ... I can't match it to the form field, which is a list of Strings.
of the top of my head you could do something like this:
#(theForm: Form[DataForView], selectedItems: Set[String])
#for((itemValue, itemName) <- allItems) {
#if(selectedItems.contains(#itemValue)) {
<input type="checkbox" name="selectedItems" value="#itemValue" CHECKED/>#itemName<br/>
} else {
<input type="checkbox" name="selectedItems" value="#itemValue"/>#itemName<br/>
}
}
Where the selectedItems is the set of selected elements.

How to change input value in redux

I am making a file manager app based on react-redux, and I meet problem with input.
For example, my code:
PathForm.js:
export default class PathForm extends Component {
render() {
const { currentPath, handleSubmit } = this.props;
console.log('PathFormPathFormPathForm', this.props)
return (
<div className="path-box">
<form onSubmit={handleSubmit}>
<div>
<input type="text" className="current-path-input" placeholder="input path" value={currentPath} />
</div>
<button className="go-btn" type="submit">Go</button>
</form>
</div>
);
}
}
Explorer.js:
class Explorer extends Component {
goPath(e) {
e.preventDefault()
// fake function here, because I have to solve the input problem first
console.log('PathForm goPath:',this.props)
let {targetPath , actions} = this.props
swal(targetPath)
}
render() {
const { node, currentPath , actions} = this.props
console.log('Explorer.render:',this.props)
return (
<div className='explorer-container'>
<PathForm currentPath={currentPath} handleSubmit={this.goPath.bind(this)}/>
<FileListOperator />
<FileListView fileList={node && node.childNodes} actions ={actions} />
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return {
node: state.tree[state.tree.currentPath],
currentPath: state.tree.currentPath
};
}
function mapDispatchToProps(dispatch) {
console.log('mapDispatchToProps')
return {
actions: bindActionCreators(NodeActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Explorer);
Feature I want:
I have a PathForm, it need show path from two way:
user click a file path from left tree view, Explorer get this path as currentPath, then pass to PathForm, and show currentPath in input
user directly type a path to the PathForm's input, PathForm call handleSubmit(Explorer's function) to change the currentPath
Additional:I want to keep PathForm as a stateless component
The problem:
I'd like use PathForm as a stateless form, so I don't want connect it to store, but I need it change input by currentPath. But if I set value={currentPath}, user can not type anything else.
change to <input type="text" onChange={this.changeValue} value={this.getValue()}/> allow user type string in this input, but can not use props currentPath passed by Explorer
The only way I can imagine is connect this form to store which I don't want. I'd like Explorer to dispatch all actions and pass props.
Tried with some package
I found the input not act as my thought, so I tried the two popular package:
redux-form
It create a form need so much code, and official doc not say how to render this form with parent props,
I try to pass props and handleSubmit to it, not work. After I see
React + Redux - What's the best way to handle CRUD in a form component?
and How to wire up redux-form bindings to the form's inputs
I found I can't do that, it define some function overwrite mine, this behave is not good for me(I have to change the handlerSubmit function name, but it still not work), and it connect to the store. So I turn to formsy-react
formsy-react
It still need so much code, though it provide some mixin, but I still have to write a custom text input with changeValue function myself(changeValue is no need in most situation when writing normal html jquery app).Then I found the problem that PathForm can not use props currentPath passed by Explorer...
Probably Worked solution(but I don't tend to use):
connect PathForm to store, add another state inputPathValue for this input. Use inputPathValue interact with currentPath
After above, I found use input/form is super in-convenient in react....
Does it mean I have to connect PathForm to stroe?
Any other way to solve my problem?
There are uncontrolled(not set value) and controlled(set value) input in reactjs.
controlled not allow user input, but uncontrolled does.
Solution:
Need use uncontrolled input(no value attribute).
Select input element and set the value when currentPath change.
Bad way:
code:
export default class PathForm extends Component {
changeCurrentPath(path) {
const pathInput = document.querySelector('.current-path-input')
if (pathInput){
pathInput.value = path
this.lastPath = path
}
}
render() {
const { currentPath, handleSubmit } = this.props;
console.log('PathFormPathFormPathForm', this.props)
this.changeCurrentPath(currentPath)
return (
<div className="path-box">
<form onSubmit={handleSubmit}>
<div>
<input type="text" className="current-path-input" placeholder="input path" />
</div>
<button className="go-btn" type="submit">Go</button>
</form>
</div>
);
}
}
Good way:
use componentWillReceiveProps to set props and rel to select element
1.use form submit
export default class PathForm extends Component {
constructor(props) {
super(props)
// can not find `this` if not bind
this.handleSubmit = this.handleSubmit.bind(this)
}
componentWillReceiveProps(nextProps) {
if (nextProps.currentPath !== this.props.currentPath) {
this.setInputValue(nextProps.currentPath)
}
}
getInputValue() {
return this.refs.pathInput.value
}
setInputValue(val) {
this.refs.pathInput.value = val
}
handleSubmit(e){
e.preventDefault()
this.props.handleSubmit(this.getInputValue())
}
render() {
return (
<div className="path-box">
<form onSubmit={this.handleSubmit}>
<input className="current-path-input"
defaultValue={this.props.currentPath}
ref="pathInput" />
<button className="waves-effect waves-light btn" type="submit">Go</button>
</form>
</div>
);
}
}
2.use button click
export default class PathForm extends Component {
constructor(props) {
super(props)
// can not find `this` if not bind
this.handleGoClick = this.handleGoClick.bind(this)
this.handleKeyUp = this.handleKeyUp.bind(this)
}
componentWillReceiveProps(nextProps) {
if (nextProps.currentPath !== this.props.currentPath) {
this.setInputValue(nextProps.currentPath)
}
}
getInputValue() {
return this.refs.pathInput.value
}
setInputValue(val) {
this.refs.pathInput.value = val
}
handleKeyUp(e) {
if (e.keyCode === 13) {
this.handleGoClick()
}
}
handleGoClick(e) {
e.preventDefault()
this.props.handleSubmit(this.getInputValue())
}
render() {
return (
<div className="path-box">
<form >
<input className="current-path-input"
defaultValue={this.props.currentPath}
onKeyUp={this.handleKeyUp}
ref="pathInput" />
<button className="waves-effect waves-light btn" type="submit" onClick={this.handleGoClick}>Go</button>
</form>
</div>
);
}
}
If you really don't want the state in Redux, you can instead store the state on the component with setState. Directly accessing the input is strongly discouraged. You should track the state of the input on the component. Add an onChange handler to the input, store the state and handle componentWillReceiveProps where you decide what to do with new incoming props.

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?

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>
);
}

Angularjs check if section in the form is valid

I want to check my angular form validity with a little tweak,
I have a form builded dynamically with directives involved, Now the form has more than one page to it, so i play with ng-hide/ng-show when i move from page to page...
All i want to do is to check the validity of the first chunk of form inputs, for example:
I have 3 pages, 3 questions in every 'page', before the user can go to the next page, it should check for validation on the three inputs, and only than! he can move to the next one...
on my form tag i have 'novalidate' so i must do all the validations myself...
What you're after is ng-form
You can't nest HTML <form></form> tags but you can with ng-form to split your form into sections.
i.e.
<form name="myForm">
<ng-form name="subForm1">
<input name="txtField1" type="text" ng-model="Field1" ng-maxlength="50" required>
</ng-form>
<ng-form name="subForm2">
<input name="txtField2" type="text" ng-model="Field2" ng-maxlength="10" required>
</ng-form>
<button type="button1" ng-disabled="subForm1.$invalid" ng-click="doSomething() ">Button 1</button>
<button type="button1" ng-disabled="subForm2.$invalid" ng-click="doSomething()" >Button 2</button>
<button type="button3" ng-disabled="myForm.$invalid" ng-click="doSomething()" >Button 3</button>
</form>
In this instance button1 and button2 are disabled on parts of the form where as button3 is disabled based on the whole forms input
Source: https://docs.angularjs.org/api/ng/directive/ngForm
You can use the Angular's form element property $dirty, or you could check if the element you want to validate has the class ng-dirty.
If you'd like, read more here, it explains how to use and check this.
Angular JS has some pretty features which you can take advantage of especially the class .ng-valid and .ng-invalid. As the user fills your form, angular dose a real time update on the state of form fields by changing the classList to correspond to the current state of the form.
Any for field that is has been altered and does not pass the Angular validation will have a class .ng-invalid well all classes that passed the validation will have .ng-valid. While ng-pristine indicates that the form have not been modified ng-dirty tells you that the form has been modified. Not that ng-pristine and ng-dirty cannot be used to ascertain the validity of the field.
Meanwhile for your case I have created a CodePen
angular.module("paged", [])
.controller("FormCtrl", function($scope) {
$scope.form = {page: 1};
$scope.canShow = function(i) {
return (i === $scope.form.page);
};
$scope.submit = function(form) {
alert("Form Submitted", form);
};
$scope.gotoPage = function(pageTo) {
var show = false;
var els = document.getElementsByTagName("input"); //Just with input only to keep it simple
for (var i = 0; i < els.length; i++) {
if (els[i].hasAttribute("data-page") && els[i].getAttribute("data-page") == pageTo - 1) {
if (els[i].classList.contains("ng-invalid")) {
show = false;
break;
}
}
show = true;
}
if (show) {
$scope.form.page = pageTo;
if (pageTo == 4) {
$scope.submit($scope.form);
}
}
}
});
to show you how this can done. As someone will rightfully say, there may ways to kill a rat. I think this is one of them