I have an array of emails (as a part of a bigger model). These are displayed in seperate rows witha remove button for each (the address itself can be updated in the input box directly). Unfortunately I dont know how to do this in react when the inputs are renderd using a map function.
(I am converting a meteor blaze project to meteor react).
Everything renders but how do I attach to change event so I can update my array of emails? onChange + value need to be set somehow.
This is the map function
return this.data.emailGroup.emails.map((email) => {
return (
<div key={email} className="input-group">
<input type="text" className="form-control" onChange={self.handleEmailListChange} value={email}/>
<div className="input-group-btn">
<button type="button"
className="btn btn-default remove-email"><span
className="glyphicon glyphicon-remove"></span></button>
</div>
</div>
);
});
The initial state(which is populated with data from the db:
getInitialState() {
return {
name : '',
description: '',
emails : [],
newEmailAddress : ''
}
},
Upon request here is the render method(it requires a getContent method.The getcontent method is there because in meteor I need to wait for data so in the meantime I need a loading state.
getContent() {
return (
<div className="box box-success">
<div className="box-header with-border">
<h3 className="box-title">List of emails</h3>
</div>
<form role="form" className="form-horizontal">
<div className="box-body">
<p>Below is a list of email addresses which are added to this email group. If
you
want
to add more
you can add them one by one by inputting in the box below or add a list into
the
same box (email
addresses have to be seperated by either a space or ;) then press Add to add
to
the
list. You can edit
the addresses directly as well as remove them.</p>
<div className="input-group">
<input type="text" className="form-control"
value={this.state.newEmailAddress}
onChange={this.handleAddNewEmail}
placeholder="Email addresses seperated by a space or a semicolon ; i.e. test1#test.se;test2#test.se"/>
<span className="input-group-btn">
<button type="button" onClick={this.handleAddNewEmailButton} className="btn btn-info btn-flat add-email">Add</button>
</span>
</div>
<br/>
{this.renderEmail()}
</div>
</form>
</div>
)
},
render()
{
var contentStyle = {
minHeight : '946px'
};
return (
<div className="content-wrapper" style={contentStyle}>
<section className="content-header">
<h1>
{this.data.emailGroup? this.data.emailGroup.name : 'hello'}
</h1>
<small> Created by: Christian Klinton</small>
<br/>
<small> Last updated by: Christian Klinton - 2015-11-12 08:10:11</small>
<ol className="breadcrumb">
<li><i className="fa fa-dashboard"></i> Home</li>
<li>Email groups</li>
<li className="active">{this.data.emailGroup? this.data.emailGroup.name : 'loading'}</li>
</ol>
</section>
<section className="content">
<div className="row">
<div className="col-md-6">
<div className="box box-primary">
<div className="box-header with-border">
<h3 className="box-title">Information</h3>
</div>
<form role="form">
<div className="box-body">
<div className="form-group">
<label htmlFor="inputName">Name</label>
<input type="email" className="form-control" id="name"
onChange={this.handleNameChange}
placeholder="Set the name of the email group" autoComplete="off"
value={this.state.name}/>
</div>
<div className="form-group">
<label>Description</label>
<textarea className="form-control" rows="3" id="description"
placeholder="Enter a description what and how the template is used..."
onChange={this.handleDesriptionChange}
value={this.state.description}
></textarea>
</div>
</div>
</form>
</div>
</div>
<div className="col-md-6">
{this.data.emailGroup? this.getContent() : <p>Loading</p> }
</div>
<div className="form-group">
<div className="col-sm-offset-8 col-sm-4">
<div className="pull-right">
<button className="btn btn-primary">Delete all</button>
<button className="btn btn-primary save">Save</button>
</div>
</div>
</div>
</div>
</section>
</div>
)
}
React requires you to have something unique for every element in the rendered array, it's called a key, and it's an attribute.
If you don't know what to assign to the key, just assign it the array's indexes:
this.props.doors.map((door, index) => (
<div key={index} className="door"></div>
));
Here's the same solution applied to your problem:
return this.data.emailGroup.emails.map((email, index) => {
return (
<div key={index} className="input-group">
<input type="text"
className="form-control"
onChange={self.handleEmailListChange.bind(this, index)} value={email}/>
</div>
);
});
Notice how I bound handleEmailListChange to receive the index of the modified email. If handleEmailListChange accepts an index, it can update the modified email within the state:
handleEmailListChange: function(index, event) {
var emails = this.state.emails.slice(); // Make a copy of the emails first.
emails[index] = event.target.value; // Update it with the modified email.
this.setState({emails: emails}); // Update the state.
}
I believe what you're looking for is something like this:
MyPage = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
// ...
},
render() {
const emails = this.data.emailGroup.emails.map((email) => {
return (
<div key={email} className="input-group">
<input type="text" className="form-control"
onChange={this.handleEmailListChange} value={email} />
<div className="input-group-btn">
<button type="button"
className="btn btn-default remove-email"><span
className="glyphicon glyphicon-remove" /></button>
</div>
</div>
);
});
return <div>
{emails}
</div>
}
});
I changed self to this. Since you're using the ES6 arrow function, there's no need to assign self = this.
You should place your Array.map directly inside your render() function. Just take care that each array element is wrapped inside a parent element (<div> here) and must have a unique key={}
class ArrayMap extends React.Component{
//your functions
handleEmailChanged(key){
// Handle email
}
render(){
return(
<div>
{
this.data.emailGroup.emails.map((email) => {
<div key={email.key}>
<button onClick={this.handleEmailChanged.bind(this,email.key)}/>
</div>
});
}
</div>
);
}
}
Related
I'm pretty new to React & JavaScript in general, and could use some help with a fairly simple thing:
I want to output the input value from my text <input> into my <h4> by hitting the update button. (Or something similar if that doesn't work)
return (
<div>
<div className="top-menu">
<div>
<h1>Progress</h1>
</div>
</div>
<div className="progress-container">
<h4>Progress: 43 %</h4>
<div className="progress-bar"></div>
</div>
<div className="inputs">
<label>Progress (in %)</label>
<div className="input">
<form>
<input id="progress" type="text" placeholder="e.g. 75" />
<button className="button">Update</button>
</form>
</div>
</div>
</div>
);
Thanks so much for your help!
Create some progress state and use the form's onSubmit callback to update state and clear the input value. Render the progress state into the h4 tag.
function App() {
const [progress, setProgress] = useState(43); // <-- progress state
return (
<div>
<div className="top-menu">
<div>
<h1>Progress</h1>
</div>
</div>
<div className="progress-container">
<h4>Progress: {progress} %</h4> // <-- render progress state
<div className="progress-bar"></div>
</div>
<div className="inputs">
<label>Progress (in %)</label>
<div className="input">
<form onSubmit={e => { // <-- form submit callback
e.preventDefault(); // prevent default form action so page doesn't reload
const { value } = e.target.progress;
setProgress(value); // update state
e.target.progress.value = ''; // reset input value
}}>
<input id="progress" type="text" placeholder="e.g. 75"></input>
<button type="submit" className="button">Update</button> // <-- type = submit
</form>
</div>
</div>
</div>
);
}
is there a way to clear the selected value in the react select dropdown menu after choosing an option with a press of a clear button? Thank you.
import Select from 'react-select';
const TransactionDetailsPanel = props => {
const clearQuery = () => {
inputRef.current.value=null;
};
return (
<>
<div className="columns is-gapless is-marginless">
<Select className="column is-3" options={options} onChange={updateSelection}
ref={selectRef} placeholder="Advanced Detail Search" />
<input className="column is-3" type="text"
ref={inputRef} placeholder="Enter query here..."/>
<div className="buttons">
<button className="button" onClick={updateQuery}>Details Search</button>
<button className="button" onClick={clearQuery}>Clear</button>
</div>
</div>
Ok so I am making a "buzzfeed" quiz generator and I am having an issue with a piece of my form. So for each question made there are two different parts, a "Question" and the "Answers". There will always be 4 answers but I want to keep track of which Answers are checked later so I would like to give each answer a unique id "question" + questionNum + "Answer" + answerNum or "question" + questionNum + "Answer" + i So I made this answersGenerator function that is supposed to give me 4 unique answer choice fill ins for the user to input answers for that specific questions (right now it says something about terms and conditions - ignore that I ad just left it like that for now because I was following a tutorial). Here is all my code:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import logo from './logo.svg';
import './App.css';
var questionNum = 1;
var answerNum = 1;
class App extends Component {
constructor(props) {
super(props);
this.state = {
quizTitle: "",
author: "",
question: "",
terms: false,
test: "",
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
console.log(this.state);
}
render() {
return (
<div className="App">
<div>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
<div className="container">
<div className="columns">
<div className="formDiv">
<form className="form" onSubmit={this.handleSubmit}>
<div className="field">
<label className="label">Give your quiz a title.</label>
<div className="control">
<input
className="input"
type="text"
name="quizTitle"
value={this.state.quizTitle}
onChange={this.handleChange}
/>
</div>
</div>
<div className="field">
<label className="label">Who's the Author?</label>
<div className="control">
<input
className="input"
type="text"
name="author"
value={this.state.author}
onChange={this.handleChange}
/>
</div>
</div>
<div className="questions" id="questions">
<div className="question" id={'questionGroup' + questionNum}>
<div className="field">
<label className="label" id={'question' + questionNum}>Question {questionNum}</label>
<div className="control">
<input
className="input"
type="text"
name='question'
value={this.state.question}
onChange={this.handleChange}
/>
</div>
</div>
<div className="answersDiv">
<label className="label">Answers</label>
<div className="field">
<div className="control">
<label className="checkbox">
{this.answersGenerator()}
</label>
</div>
</div>
</div>
</div>
</div>
<div className="field">
<div className="endButtons">
<button id="addQuestionButton"
onClick={addQuestion}>Add Question</button>
<input
type="submit"
value="Submit"
id="submitButton"
className="button is-primary"
/>
</div>
</div>
</form>
</div>
<div className="column is-3">
<pre>
<code>
<p>Quiz Title: {this.state.quizTitle}</p>
<p>Author: {this.state.author}</p>
<p>Question: {this.state.question}</p>
<p>Terms and Condition: {this.state.terms}</p>
<p>Do you test?: {this.state.test}</p>
</code>
</pre>
</div>
</div>
</div>
</div>
);
}
}
function answersGenerator () {
console.log("hello this is answer generator");
var div = document.createElement('div');
div.className = 'answers' + {questionNum};
for (var i = 1; i < 5; i++){
div.innerHTML =
'<div className="field">\
<div className="control">\
<label className="checkbox">\
<label className="label">Answers</label>\
<input\
name="terms"\
type="checkbox"\
id="question'+{questionNum}+'Answer'+{i}+'"\
checked={this.state.terms}\
onChange={this.handleChange}\
/>\
I agree to the{" "}\
terms and conditions\
</label>\
</div>';
document.getElementsByClassName('answersDiv')[0].appendChild(div)
}
}
function addQuestion () {
questionNum++;
console.log(questionNum + "that is the question number");
var div = document.createElement('div');
div.className = "questions" + questionNum;
div.innerHTML =
'<div id="questionGroup'+{questionNum}+'">\
Question '+{questionNum}+'<br />\
<input type="text" name="question'+{questionNum}+'"/><br />\
Answers<br />\
Check the box(es) for the correct answer(s).<br />\
<input type="checkbox" name="question'+{questionNum}+'Answer1Box" />\
<label for="question'+{questionNum}+'Answer1"><input type="text" name="question'+{questionNum}+'Answer1"/></label><br />\
<input type="checkbox" name="question'+{questionNum}+'Answer2Box" id= />\
<label for="question'+{questionNum}+'Answer2"><input type="text" name="question'+{questionNum}+'Answer2"/></label><br />\
<input type="checkbox" name="question'+{questionNum}+'Answer3Box" />\
<label for="question'+{questionNum}+'Answer3"><input type="text" name="question'+{questionNum}+'Answer3"/></label><br />\
<input type="checkbox" name="question'+{questionNum}+'Answer4Box" />\
<label for="question'+{questionNum}+'Answer4"><input type="text" name="question'+{questionNum}+'Answer4"/></label><br />\
</div>';
document.getElementsByClassName('questions')[0].appendChild(div)
}
export default App;
There's a few things to just ignore in here, i just wasn't sure how much info you needed. So the issue is that I get an error when I call my answersGenerator function in my render function. Here's the error Uncaught TypeError: this.answersGenerator is not a function Here's the small piece where that is
<div className="answersDiv">
<label className="label">Answers</label>
<div className="field">
<div className="control">
<label className="checkbox">
{this.answersGenerator()}
</label>
</div>
</div>
</div>
I appreciate the help, if you have any other tips for me other than this issue I am having I am all ears. I am very new at this, being js and React.
UPDATE: I have edited the answersDiv to be just this (I relized it was being called inside a checkbox)
<div className="answersDiv">
<label className="label">Answers</label>
{answersGenerator()}
</div>
I am still getting the same errors though.
You're receiving the error because the answersGenerator() isn't a method of your class App, so, you can't refer to it using this. If you want to do on that way, move you function to inside the component, like this:
class App extends Component {
// your component definition etc..
answersGenerator = () => {
// your function implementation
}
// the rest of your component
}
TIP:
If you dont want to have to always bind the function, like:
this.handleChange = this.handleChange.bind(this);
Declare your method like:
handleChange = (event) => { // the implementation }
Instead of
handleChange(event) { // the implementation }
The () => {}, known as arrow function, does the bind for your.
EDIT
You're developing with React, that supports creating HTML inside your code. So, you can create an array of answers, then, return a div with the answers mapped inside. The map will take each answer and render inside the div. Try to define your method as below:
answersGenerator => () {
console.log("hello this is answer generator");
var answers = []
for (var i = 1; i < 5; i++){
answers.push(
<div className="field">
<div className="control">
<label className="checkbox">
<label className="label">Answers</label>
<input
name="terms"
type="checkbox"
id=`question${questionNum}Answer${i}`
checked={this.state.terms}
onChange={this.handleChange}
/>
I agree to the{" "}
terms and conditions
</label>
</div>
);
}
return <div className=`answer${questionNum}`> {answers.map(answer => answer)} </div>
}
At first glance it looks like you haven't defined your answersGenerator() function within your App class.
Thus, this.answersGenerator() doesn't exist.
Try just using answersGenerator() without the this.
I have an observable array that contains a list of object that I want to filter through based on a user input. If the user searches a word that appears in the array in two different places then the filter function should return the title of both objects and delete or hide all other objects in the array that did not match the input from the user. I must use knockout js to preform this feature which is still new to me. Currently my filter function checks to see if the user input is included in a title of one of the objects within the array and if it is not then it removes the object. However, this not providing me what I need as it does not accurately filter the list.
My ViewMode
var viewModel = function() {
var self = this;
self.filter = ko.observable('');
self.locationList = ko.observableArray(model);
self.filterList = function(){
return ko.utils.arrayFilter(self.locationList(), function(location) {
if(location.title == self.filter()){
return location.title
}
else if( location.title.includes(self.filter()) ){
return location.title
}
else{
return self.locationList.remove(location)
}
});
};
}
The View
<section class="col-lg-2 sidenav">
<div class="row">
<div class="col-lg-12">
<div class="input-group">
<input data-bind="textInput: filter"
type="text" class="form-control" placeholder="Filter Places"
aria-describedby="basic-addon2" id="test">
<button data-bind="click: filterList id="basic-addon2">
<i class="glyphicon glyphicon-filter"></i>
Filter
</button>
</div>
</div>
<div class="col-lg-12">
<hr>
<div data-bind="foreach: locationList">
<p data-bind="text: $data.title"></p>
</div>
</div>
</div>
</section>
The answer to the question can be found here answered by Viraj Bhosale
ViewModel
var viewModel = function() {
var self = this;
self.filter = ko.observable('');
self.locationList = ko.observableArray(model);
self.filterList = ko.computed(function(){
return self.locationList().filter(
function(location){
return (self.filter().length == 0 || location.title.toLowerCase().includes(self.filter().toLowerCase()));
}
);
});
}
View
<main class="container-fluid">
<div class="row">
<section class="col-lg-2 sidenav">
<div class="row">
<div class="col-lg-12">
<div class="input-group">
<input data-bind="textInput: filter, valueUpdate: 'keyup'"
type="text" class="form-control" placeholder="Filter Places"
aria-describedby="basic-addon2" id="test">
</div>
</div>
<div class="col-lg-12">
<hr>
<div data-bind="foreach: filterList">
<p data-bind="text: $data.title"></p>
</div>
</div>
</div>
</section>
<section class="col-lg-10" id="map"></section>
</div>
I'm having trouble in wanting to create multiples of the same html that I render in a certain class. For example, I have a div that might look like this
[Header goes here, is a input field] [Dropdown]
[TextArea]
[Submit]
[Add another field]
On add another field, I would like to clone this view and be able to add as many again.
Here's what I have so far:
var UpdateForm = React.createClass({
handleSubmit : function(e) {
e.preventDefault();
var title = React.findDOMNode(this.refs.title).value.trim();
var date = React.findDOMNode(this.refs.date).value.trim();
var body = React.findDOMNode(this.refs.body).value.trim();
if(!title||!date || !body ) {
return;
}
this.props.onSubmit({title:title, date : date, body : body});
React.findDOMNode(this.refs.title).value = '';
React.findDOMNode(this.refs.date).value = '';
React.findDOMNode(this.refs.body).value = '';
//React.findDOMNode(this.refs.sub).value = '';
},
render: function() {
return(
<div id = "This is what I want to duplicate on each button click">
<form className="form-horizontal" onSubmit={this.handleSubmit}>
<div class="form-control">
<label className="col-sm-0 control-label ">Title:</label>
<div class="col-sm-5">
<input className = "form-control" type = "text" placeholder="Title...." ref = "title"/>
</div>
</div>
<div class="form-control">
<label className="col-sm-0 control-label ">Date:</label>
<div class="col-sm-5">
<input className = "form-control" type = "text" placeholder="Date...." ref = "date"/>
</div>
</div>
<div className="form">
<label htmlFor="body" className="col-sm-0 control-label">Report Body:</label>
<div className="column">
<textarea className = "ckeditor" id = "ckedit" ref = "body"></textarea>
</div>
</div>
<div class="form-group">
<label className="col-sm-0 control-label"></label>
<div class="col-sm-5">
<input type = "submit" value="Save Changes" className = "btn btn-primary" />
</div>
</div>
</form>
<div className="btn-group">
<button type="button" className="btn btn-danger">Assign to</button>
<button type="button" className="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span className="caret"></span>
<span className="sr-only">Toggle Dropdown</span>
</button>
<ul className="dropdown-menu">
{
this.props.forms.map(function(form){
return (
<li>{form}</li>
)})}
</ul>
<button className="btn btn-success btn-block" onClick={this.addInputField}>Add Subform</button>
</div>
</div>
);
}
});
What I think I need to add:
addDiv: function(e) {
e.preventDefault();
//have a array of divs?
//push a the same div into it?
//then set state of that array?
}
I know in jquery I could just write a function that appends this markup whenever I hit a button, but i don't know how to think about it here at all.
I think what you want is to have a button which add another header-date-body-Component to the form, which should then also be submitted, right?
If so then you need to think more in components. Have one Component which handles the form. Have one around that which handles adding other forms.
<ReportDialog>
<ReportForm>
<ReportForm>
<button onClick={this.addReport}>Add another</button>
</ReportDialog>
To accomplish the multiple ReportForms you need to think about the data in your component, which are reports (I assume). So you need a state in ReportDialog which keeps track of your reports. so at the start of the app you have one report:
getInitialState: function () {
return {
reports: [{ title: '', body: '', date: new Date() }]
};
}
So in addReport you then need to change the state and add another report. To have these reports rendered you already used map, but this time you need to loop over the reports in your component and return a ReportForm for each report.