React.js adding component to child - javascript

import FieldSect from "./fieldSect.js"
<div>
<FieldSect />
</div>
--FieldSect.js--
import Field from "./Field.js"
<div>
<Field />
<button onclick={addField}> addField </field>
</div>
--Field.js--
function Field (){
<div>
<label> Test </label>
<input type="text" />
</div>
}
My code works in the part where Field is loaded immediately and is shown correctly. I am really struggling on trying to figure out how to keep adding the <Field /> component under the already existing <Field /> whenever the add button is clicked. I also need to ensure to have the ability to keep adding rather than having a specific number of available
I also cannot use the DOM as I am getting an error telling me to edit the state rather than using DOM.render
End Result should look something like this:
--FieldSect.js--
<div>
<Field />
...<Field /> (Button Add Click)
...<Field /> (Button Add Click)
..
</div>

You should have Fields be part of the state of your parent component, perhaps as an array, this.state = {fields:[{id:1},{id:2},{id:3}]}.
Then, when rendering, you can iterate over the array:
<div>
{this.state.fields.map(f => <Field key={f.id}/>)}
</div>

Its super simple.
Have a default state of fields for example lets say that you want to have 1 field at the beginning
this.state = {
fields: [<Field/>]
}
Now use onClick event for AddClick and a function as follows
handleOnClick=(event)=>this.setState(prevState=>({...prevState, fields: [...prevState.fields, <Field/>]})
And in your render function iterate over fields array
PS: I am not sure what is a Field doing.
What I would do is in render
<div>
{this.state.fields.map(field => <Field {...field}/>)}
</div>
and fields would be the data of a field

If I'm understanding you correctly, you're wanting to add a every time someone clicks a button. If that's accurate, then I would simply add a constructor with a state to either the FieldSect.js or parent file to that (somewhere along the line before Field.js) that creates a trackable state. Something like:
this.state = {
NumberOfFields: 1
};
If you do this ahead of FieldSect.js, then it needs to be passed down in props.
Now you need to set up the onClick function to increment this state. Using this, you can then create a loop in FieldSect.js that creates an array of elements that will be rendered in React.
let array = []
for(let i = 0; i < this.NumberOfFields; i++){
array.push(<Field key={Math.random()}/>)
}
Then, instead of using in FieldSect.js, use {array}.
The reason I added the key attribute to the Field is that all elements that are derived from an iteration like this must have a unique key.

Here you go.
This holds the number of <Fields /> in the state of a controlled component, which can be incremented with the button.
The loop is generated by creating an Array of nbrOfFields length, destructured, and then mapped over.
const Field = () => (
<div>
<label> Test </label>
<input type="text" />
</div>
);
class App extends React.Component {
constructor() {
super();
this.state = {nbrOfFields: 1};
this.addField = this.addField.bind(this);
}
addField() {
this.setState(prevState => ({nbrOfFields: prevState.nbrOfFields + 1}));
}
render() {
return (
<div>
{[...Array(this.state.nbrOfFields)].map((item, i) => (
<Field key={i} />
))}
<button onClick={this.addField}>Add field</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Related

Can value of input box be set to to empty string on clicking submit button when the input is in a stateless functional component?

How to clear the value inside the input in function Admin after I click the "Add" button? Should i use another class based component instead of a functional component?
I have set the value of one of the input box as : value={props.item} and in the this.setState I update the value of item as item:"".
AddInfo(info){
let s = this.state.products;
let obj ={name:""};
obj.name=info.productName;
s.push(obj);
this.setState({
products:s,
item:"" //Here i set the value of item equal to an empty string.
})
console.log(this.state.products);
}
function Admin(props){
let productName="";
return (
<div>
<input type="text" required placeholder="Product Name" onChange={(e)=>{productName=e.target.value}} value={props.item}></input><br/>
<button type="Submit" onClick{(e)=>props.AddInfo({productName})}>Add</button>
</div>
)
}
You have to save your input within a local state of the input function:
AddInfo(info){
let s = this.state.products;
let obj ={name:""};
obj.name=info.productName;
s.push(obj);
this.setState({
products:s,
})
console.log(this.state.products);
}
function Admin(props){
const [productName, setProductName] = useState('');
return (
<div>
<input type="text" required placeholder="Product Name" onChange={(e)=> setProductName(e.target.value) value={productName}></input><br/>
<button type="Submit" onClick{(e)=> {props.AddInfo({productName}); setProductName('')}}>Add</button>
</div>
)
}
This will work for you, since you are not mutating the productName variable anymore but now you are saving it in a local state of that input function.
Hope this helps!
Admin is like a form, and the main decision you have to make is rather you want it to be controlled (info is stored in stated, and state is reflected in the ui), or uncontrolled (info is taken from the dom once 'Add' is clicked.
Since you want to empty the input once 'Add' is clicked it makes sense to make this component controlled.
The next decision is rather you want it to be a functional component, or a class component. In nowadays it doesn't really matter (functional components can now use state with the state hook).
To store state in you functional component use React's useState hook.
function Admin({addInfo}){
const [productName, setProductName] = useState(")
return (
<div>
<input
type="text"
placeholder="Product Name"
onChange={(e)=>{
setProductName(e.target.value)
}
value={prodcutName}>
</input>
<button
onClick{(e)=>{
props.addInfo({productName})
setProductName("") // Will set the input to an empty string
}
>
Add
</button>
</div>
)
}

React: How to add conditionally rendered extra options from child components (2 levels down) into state?

I have created a form as per the sandbox link: https://codesandbox.io/s/rlmv6ojjyn
In the SOW Type section there are three checkboxes. On clicking the checkboxes the user has access to further options (have not created these yet but the first option will require a set of drop-downs, the third option exposes a new checkbox (on clicking the first), which will (on click) will then expose some textarea boxes for the user to submit text.
I have got to this point as per the code in 'SOWType.js' (shown below) which is a child of 'PdfGenFormContainer.js'.
I have an object 'componentList' (inside SOWType.js) which is used with conditional rendering to display the relevant sub-component on relevant checkbox selection)
My question is, how do I include the user selections from these sub-components in state. The object 'componentList' is in the child component so how does all this get communicated to the state in the parent? (so it is all included when the form is submitted)?. Does 'componentList' need to sit in the parent somehow? (how would this work?)
Is my only option to use redux to get this done or can it be simply done in just React?
SOWType.js:
import React from "react";
import ProdSOWExtOptions from "./ExtendedOptions/ProdSOWExtOptions";
import TeradataExtOptions from "./ExtendedOptions/TeradataExtOptions";
import CustomProfExtOptions from "./ExtendedOptions/CustomProfExtOptions";
class SOWType extends React.Component {
componentList = {
ProductSow: <ProdSOWExtOptions type={this.props} />,
"Teradata Customer SOW": <TeradataExtOptions />,
"Custom Professional Services SOW": <CustomProfExtOptions />
};
render() {
// console.log(this.props);
return (
<div className="form-group">
<label htmlFor={this.props.name} className="form-label">
{this.props.title}
<h6>{this.props.subtitle}</h6>
</label>
<div className="checkbox-group">
{this.props.options.map(option => {
return (
<label key={option}>
<input
className="form-checkbox"
name={this.props.setName}
onChange={this.props.controlFunc}
value={option}
checked={this.props.selectedOptions.indexOf(option) > -1}
type={this.props.type}
/>
{option}
{this.props.selectedOptions.indexOf(option) > -1 ? (
<h5>{this.componentList[option]}</h5>
) : (
" "
)}
</label>
);
})}
</div>
</div>
);
}
}
export default SOWType;

How to collect IDs of all checked checkboxes and push values to an array in React

I'm moving an old multiple choice quiz app from Blaze to React.
The app grabs quizzes from the DB and then for each question it loops through the answers and prints a checkbox for each one.
Each of these answers for each of the questions was inside a form, and when the form was submitted I used jQuery to grab the ID of each :checked checkbox. These IDs were then pushed to an array and sent to the server to compare vs the correct answers.
Now that I'm using React, I'm having some difficulty replicating this functionality as using checkboxes isn't the same.
What would be the best way to get the value of the checked checkboxes in to an array?
Here is my code with as much irrelevant data cut out as possible:
Assessment.jsx
class Assessment extends React.Component {
constructor(props) {
super(props);
}
render() {
const { module } = this.props.params;
const { loading, modules, assessment } = this.props;
return (
<div className="Assessment">
<div className="section">
<form onSubmit={ this.submitForm }>
{ assessment.questions.map((question) => {
return <AssessmentQuestion question={question} key={question.number}/>
})}
<button className="btn btn-action btn-block" type="submit">Submit Assessment</button>
</form>
</div>
</div>
)
}
}
AssessmentQuestion.jsx
class AssessmentQuestion extends React.Component {
constructor(props) {
super(props);
}
render() {
const { question } = this.props;
return (
<div className="question" data-number={question.number}>
<p>
{question.number}) {question.question}
</p>
{ question.answers.map((answer) => {
return <div className="checkbox">
<label>
<input type="checkbox" name={question.number} value={answer.letter} id={answer.number, answer.letter}/>
{ answer.answer }
</label>
</div>/>
})}
</div>
)
}
}
As you can see, I am looping through each question and for each question looping through each answer and printing a checkbox. When the user submits the form in the parent 'Assessment.jsx' component, I want to collect the id of each checked checkbox and push that in to an array so I can send to the server for grading.
Any help much appreciated
There are a couple of ways you could solve this. The easier way, which is closer to the solution you had before, is to use React refs.
https://facebook.github.io/react/docs/refs-and-the-dom.html
You could add a ref to the checkboxes, and then filter down to the checked ones.
An alternative (and more "Reacty" approach) would be to manage all this "checked" logic with React state. Basically, your Assesment.jsx class would keep track of the state and then send it to the form on submit.
You create a function to update the state with the questionName and answer, and pass that function as an onClick callback to your AssesmentQuestion class.
Each checkbox would have onclik which would update state, or even better if using redux, dispatch action which would result in state update.
When sending to server only information in state would get used

Change a state in react

I'm learning React and as a learning exercise am trying to do a very basic page where there is a form and you put text in an input box, you click submit and the header changes to what you entered. Here is my code so far:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {header: 'yeaheheh'}
}
changeHeader(e) {
let newHeader = document.getElementById('input').value();
e.preventDefault();
console.log('submitted');
this.setState(newHeader);
}
render() {
return (
<div>
<h1>{this.state.header}</h1>
<form onSubmit={this.changeHeader.bind(this)} className="change-header-form">
<input id="input" type="text" placeholder="Enter Text Here" />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default App;
At first, when I clicked submit, nothing happened and I got an error in the console that says
Uncaught TypeError: Cannot read property 'setState' of null
I then realized I needed to bind the changeHeader function to this which I changed so before I had:
<form onSubmit={this.changeHeader}...
changed it to
<form onSubmit={this.changeHeader.bind(this)}...
After doing this, the error cleared but my header is still not updating.I read that there has been strong suggestions against changing state via setState is bad practice because calling setState() again could potentially alter the changed state. setState is also an asynchronous operation which would also explain why my header isn't changing.
With all that said, then what would be the best way to handle this? From what I understand, props wouldn't make sense either since those values are stored directly in your component and aren't parameters that can't be dynamically updated. I'm having a hard time understanding the relationship between these different data types and how they are handled in the DOM.
You are setting state incorrectly.
More over to get the data from input fields you can either use controlled input elements(via states) or uncontrolled input elements via "ref" which I have used in below example.
In controlled input element you store the value of input element in state and changes to that value is done by calling onChange method and then setting the state via this.setState({}).
Calling setState causes re-rendering to happen and dom gets the updated data based on new state.
Btw "refs" gets you the direct access to dom elements, in similar way $() was used in jquery and should be avoided if possible because it will lead to very hard to manage and predict dom changes.
Also there are cases where use of "refs" is recommended
There are a few good use cases for refs:
Managing focus, text selection, or media playback.
Triggering imperative animations.
Integrating with third-party DOM libraries.
class App extends React.Component {
constructor() {
super();
this.state = {header: 'yeaheheh'};
}
changeHeader = (e) => {
e.preventDefault();
let newHeader = this.textInput.value;
console.log('submitted');
this.setState({header : newHeader});
}
render() {
return (
<div>
<h1>{this.state.header}</h1>
<form onSubmit={this.changeHeader} className="change-header-form">
<input id="input" ref={(input) => { this.textInput = input; }} type="text" placeholder="Enter Text Here" />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('test'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="test">
</div>
Replace this.setState(newHeader); with this.setState({header: newHeader});.
Take a look at this article in the react docs: https://facebook.github.io/react/docs/forms.html#controlled-components.
Basically what you want to do is create another handler for the input. This will be called every time there is a change to the input field and a property in your state will be updated. Then, when you submit the form you can take that new property and "merge" it using setState to become the new header.
JS Bin
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
header: 'yeaheheh',
next: ''
}
this.changeHeader = this.changeHeader.bind(this);
this.updateNext = this.updateNext.bind(this);
}
changeHeader(e) {
e.preventDefault();
this.setState({
header: this.state.next
});
}
updateNext(e) {
this.setState({
next: e.target.value
});
}
render() {
return (
<div>
<h1>{this.state.header}</h1>
<form onSubmit={this.changeHeader} className="change-header-form">
<input id="input" type="text" placeholder="Enter Text Here" onChange={this.updateNext} />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
Maybe this bin will provide a little better context at what I'm trying to describe.
There's the small bug in your code preventing it from working (this.setState(newHeader) -> this.setState({header: newHeader});), but the thing is that your code is not idiomatic for React.
You are supposed to use controlled components instead of grabbing the values from the form's inputs on submit, as you would do with jQuery.
"Controlled component" is a silly name for the pattern where an input's state is mapped to the application state, so an input itself behaves as if it would be kinda "stateless". In your case, you need to have separate component state member for every text input you've got. Input control should look like this:
<input value={ this.state.inputValue }
onChange={ e => this.setState({ inputValue : e.target.value }) }
/>
Now it's bound to your inputValue state member, so you can just take it from the state at any moment you need. On form's submit handler, in your case.
That's it. Your code must be fixed accordingly. Refer to the "controlled components" manual for further details, it's the really important React concept.
You should modified your function like this..
constructor(props) {
super(props);
_that = this;
}
changeHeader = (e) => {
e.preventDefault();
let newHeader = this.textInput.value;
console.log('submitted');
_that.setState({header : newHeader});
}

How to get Material-UI's <TextField/> to return correctly with ref='' like <input/> in ReactJS?

With the following method:
handleClick(event) {
const inputText = this.refs.inputText
console.log(inputText.value.trim())
}
I am trying to get Material-UI's <TextField/> to return the input text correctly with ref like the <input/> can with <button/> triggering it:
<input
className='form-control'
placeholder='Input Text'
ref='inputText'
type='text'
/>
<button
onClick={(event) => this.handleClick(event)}
>
And I attempted the following with <TextField/>, but it returns as undefined. How can I get it to return inputted text correctly like the <input/> above?
<TextField
hint='Enter text'
className='form-control'
ref='inputText'
type='text'
/>
I would suggest this approach:
Set up your textfield with a value and onChange function that are hooked into redux itself, where the onChange function just updates the value.
So you'd have something like this :
<TextField
value={this.props.textFieldValue}
onChange={this.props.textFieldChange}
Where the textFieldChange is an action that simply updates the textFieldValue. Most forms in redux will work something like this. Keep in mind the names i made up for those props and action are just for example. If you have a big form you might want to consider have part of the state tree dedicated to the form itself where you have :
state: {
form: {
textField: ...your textfield value here,
name: ...,
whateverElse: ...
}
};
I like doing this with redux because I can make that architect form part of the state to look like the json payload of wherever I'm sending it to, so there I can just send the form went I want to send it.
Anyways, back to this example. When you click your handleClick now. All you need to do is this to get the value:
handleClick(event) {
console.log(this.props.textFieldValue.trim());
}
Because the textfield is updated with every change, you always have access to it in your state. This also gives you flexibility over the refs approach, because if you use refs you will have a lot harder of a time getting access to that form in other components. With this approach, all the information is on your state so you can access it anytime, as long as you manage your props.
You should use the onChange={} to get the value:
_onChange = (e) => {
console.log(e.target.value);
}
<TextField
onChange={this._onChange}
/>
Here's a better solution than using onchange event, we get directly the value of the input created by material-ui textField :
create(e) {
e.preventDefault();
let name = this.refs.inputText.input.value;
alert(name);
}
constructor(){
super();
this.create = this.create.bind(this);
}
render() {
return (
<form>
<TextField ref="inputText" hintText="" floatingLabelText="Your name" /><br/>
<RaisedButton label="Create" onClick={this.create} primary={true} />
</form>
)}
hope this helps.

Categories