The code below is from React, which updates the DOM dynamically. I used the tutorial by Facebook react but did not understand the whole code, i.e which part of the code executes when and how it triggers the rest of the parts in the code. Please kindly help me in understanding the code.
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText) {
return <li>{itemText}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
var TodoApp = React.createClass({
getInitialState: function() {
return {items: [], text: ''};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
});
React.renderComponent(<TodoApp />, mountNode);
The above code is used to dynamically update the DOM structure. This code is referred from http://facebook.github.io/react/ so please help in knowing the work process of the code.
Thanks, that's a very good question. Here's a rough overview of what is happening behind the scenes:
Initialization
It all starts with this line:
React.renderComponent(<TodoApp />, mountNode);
This instantiate the TodoApp component which calls:
TodoApp::getInitialState()
then, it renders the TodoApp component
TodoApp::render()
which in turns instantiate a TodoList
TodoList::render()
At this point, we have everything we need in order to render the initial markup
<div>
<h3>TODO</h3>
<ul></ul> <!-- <TodoList> -->
<form>
<input value="" />
<button>Add #1</button>
</form>
</div>
It is stringified and added inside of mountNode via innerHTML
OnChange
Then let's say you're going to enter some text in the input, then
TodoApp::onChange
is going to be called, which is going to call
TodoApp::setState
and in turn will call
TodoApp::render
again and generate the updated DOM
<div>
<h3>TODO</h3>
<ul></ul> <!-- <TodoList> -->
<form>
<input value="sometext" />
<button>Add #1</button>
</form>
</div>
What's happening at this point is that React is going to do a diff between the previous DOM and the current one.
<div>
<input
- value=""
+ value="sometext"
Only the value of the input changed, so React is going to just update this particular attribute in the real DOM.
You can find more general explanation on React official page.
Generally the react lifecycle can be described by the following stages (which can repeat multiple times once the components is created):
Initializing values (only once):
constructor(){ ... }
Mounting, if you need to add something after initial rendering (only once):
componentDidMount(){...}
Re-rendering functions, variables and components
myArrowFunction = () => {
...
this.setState({...})
...
}
Updating:
componentDidUpdate()}{...}
shouldComponentUpdate(){...}
Unmounting:
componentWillUnmount(){...}
Rendering happens here
render(){...}
Related
i'm creating a simple react website that's supposed to do some calculations and find out Joules of my input values after the calculations...right now the input values are already preset but i will remove the value="" from my <input> later.
here is the .JSX component file that's the issue...one of the components.
import React, { Component } from 'react';
import Atom_icon from './cartridges.png';
class Joule_calc extends Component {
render(){
return (
<div className='Joule_div'>
<h3 style={{color:"white", textAlign:"center"}}>JOULE CALCULATOR</h3>
<label className='lab1'>WEIGHT=/GRAMS</label><br></br>
<input className='weight_inp' type='text' value="2" />
<label className='lab2'>SPEED=M/S</label><br></br>
<input className='speed_inp' type='text' value="5" />
<button className='count_button' onClick={this.Create_response}>CALCULATE</button>
<h1 className='Result_joule'></h1>
</div>
)
}
Create_response(){
console.log("creating response...")
let sum = document.createElement("h1")
sum.className = 'Result_joule'
sum.textContent = "678"
let div_panel = document.getElementsByClassName("Joule_div")
div_panel.append('Result_joule')
}
Returned_values(){
let weight_val = document.getElementsByClassName("weight_inp")[0].value;
let speed_val = document.getElementsByClassName("speed_inp")[0].value;
let final_calculation = weight_val * speed_val
return final_calculation
}
}
export default Joule_calc
so when i run my code i get
Uncaught TypeError: div_panel.append is not a function
at Create_response (Joule_calc_window.jsx:31:1)
i don't get why i can't append my new element to the div. it says it's not a function so what's the solution then? i'm new to React and web so probably it's just a noobie thing.
also i tried directly creating a h1 inside the 'Joule_div' like this.
<h1 className='Result_joule'>{"((try returning here from one of these methods))"}</h1>
but that of course failed as well. So would appreciate some help to get what's going on. i'm trying to add a number after the button click that's in h1 and in future going to be a returned number after calculating together the input values in a method.i imagine that something like
MyMethod(){
value = values calculated
return value
}
and later grab it with this.MyMethod
example
<h1>{this.MyMethod}</h1>
this is a example that of course didn't work otherwise i wouldn't be here but at least gives you a clue on what i'm trying to do.
Thank you.
You don't leverage the full power of react. You can write UI with only js world thanks to JSX. State changes triggering UI update.
I may miss some specificaiton, but fundamental code goes like the below. You should start with function component.
// Function component
const Joule_calc = () =>{
// React hooks, useState
const [weight, setWeight] = useState(0)
const [speed, setSpeed] = useState(0)
const [result,setResult] = useState(0)
const handleCalculate = () =>{
setResult(weight*speed)
}
return (
<div className="Joule_div">
<h3 style={{ color: 'white', textAlign: 'center' }}>JOULE CALCULATOR</h3>
<label className="lab1">WEIGHT=/GRAMS</label>
<br></br>
<input className="weight_inp" type="text" value={weight} onChange={(e)=>setWeight(parseFloat(e.target.value))} />
<label className="lab2">SPEED=M/S</label>
<br></br>
<input className="speed_inp" type="text" value={speed} onChange={(e)=>setSpeed(parseFloat(e.target.value))} />
<button className="count_button" onClick={handleCalculate}>
CALCULATE
</button>
<h1 className='Result_joule'>{result}</h1>
</div>
)
}
export default Joule_calc;
div_panel is an collection of array which contains the classname ["Joule_div"]. so first access that value by using indexing . and you should append a node only and your node is "sum" not 'Result_joule' and you should not use textcontent attribute because you will be gonna definitely change the value of your result as user's input value
Create_response(){
console.log("creating response...")
let sum = document.createElement("h1")
sum.className = 'Result_joule'
//sum.textContent = "678"
let div_panel = document.getElementsByClassName("Joule_div")
div_panel[0].append('sum')
}
if any problem persists , comment below
my code:
import "./styles.css";
export default function App() {
getUserValue = (e) => {
let value = e.target.value;
console.log(value)
return value
}
let userInputValue = getUserValue
return (
<div>
<div>
<h4>Sign Up</h4>
</div>
<div>
<div>
<p>Username</p>
<input onChange = {getUserValue}/>
</div>
<div >
<p>Password</p>
<input/>
</div>
</div>
<div>
<button onClick = {console.log(userInputValue)}>Submit</button>
</div>
<div>
<button>
Close
</button>
</div>
</div>
);
}
code sandbox: https://codesandbox.io/s/dazzling-sea-my5qm?file=/src/App.js:0-720
I'm trying to store the returned value of "getUserValue" function to "userInputValue" variable so I can log the input the user made and use it in different functions. I can't get it to work though, when I console log the variable hoping to get the returned result after I made an input I don't get anything, as if the button doesn't work.
I'm trying to store the returned value of "getUserValue" function to "userInputValue" variable so I can log the input the user made and use it in different functions.
You'd do that by making the input state in your component. In a function component like yours, that means using the useState hook (or various libraries like Redux that have alternative ways of doing it).
Here's a simple example, but you can find lots of more complex ones by searching:
const { useState } = React;
function Example() {
// Call the hook to get the current value and to
// get a setter function to change it. The default
// value ("" in this example) is only used the first
// time you call the hook in this component's lifecycle
const [userInput, setUserInput] = useState("");
// Handle changes in the input by setting state.
// Note that this function is recreated every time your
// component function is called to update. That's mostly
// fine, but there are times you might want to optimize
// that.
const onChange = (event) => {
setUserInput(event.currentTarget.value);
};
// Handle clicks on the button that show' the current input.
const onClick = () => {
console.log(`The current userInput is "${userInput}"`);
};
// Return the rendering information for React
return <div>
{/* Provide the value and hook the "change" (really "input") event */}
<input type="text" value={userInput} onChange={onChange} />
<input type="button" onClick={onClick} value="Show Current" />
</div>;
}
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
I don't understand how elements are created in React.
I have some code below where the goal is to create elements on a form submit using a value from a refs - so for every submit in a form, it creates a new <h1> tag with the content of the textbox inside of it. A sample of what I'm trying to do looks like:
...
addHeader(e) {
e.preventDefault();
const newHeader = this.refs.post.value;
var newpost = React.createElement("h1", {
type: "text",
value: newHeader
});
}
...
render() {
return (
<div className="form-section">
{ newPost }
<form onSubmit={this.addHeader.bind(this)}>
<input id="input-post" type="text" placeholder="Post Here" ref="post" />
<input type="submit" value="Submit" />
</form>
<button className="form-section__submit" onClick={this.clearFields.bind(this)}>Clear All</button>
</div>
);
}
Basically my thinking is in my addHeader() function I'm assigning a variable of newPost to the method and calling it within my component. This code is causing 2 errors:
33:9 warning 'newpost' is assigned a value but never used no-unused-vars
49:13 error 'newPost' is not defined no-undef
What I don't understand, is (from what I can see) I am assigning a value to that variable and also using it in the component that I am rendering... along with that, I don't understand this error message. How can something be assigned a value but be undefined at the same time...? Is it because it's in the wrong scope? How do I declare where the new element is rendered specifically in the component?
I read the documentation but it doesn't give a clear answer as to how to control where in the component the new element is rendered.
Made some changes to your code. You're going to want to initialize component state in your constructor. In your addHeader method you will use this.setState to update the state of the component with a new posts value including the value of this.input. I changed your ref on the input an actual ref. You take the element and store on this. Every time you add a new post you will get a new <h1> with the value of the textarea.
...
addHeader(e) {
e.preventDefault();
this.setState((prevState, props) => {
return { posts: [ ...prevState.posts, this.input.value ] };
});
}
...
render() {
const { posts } = this.state;
return (
<div className="form-section">
{ posts.map( text => <h1>{ text }</h1> ) }
<form onSubmit={this.addHeader.bind(this)}>
<input id="input-post" type="text" placeholder="Post Here" ref={ el => this.input = ref } />
<input type="submit" value="Submit" />
</form>
<button className="form-section__submit" onClick={this.clearFields.bind(this)}>Clear All</button>
</div>
);
}
As an aside: Binding functions in the render method of react components will cause a performance hit. There is no need to re-bind the this context of the function on every render. this.clearFields.bind(this) should become this.clearFields and you will need to add this.clearFields = this.clearFields.bind(this) to your constructor. You do not need to bind functions that are not used as callbacks.
You're going to want to do the same thing for this.addHeader.bind(this).
I have created a helper function that creates elements dynamically within my component when I click a button. However it's not displaying half of the html I'm trying to append to the parent div.
It adds the label correctly as html, but the rest is just in plain text. Can anyone see why?
The function used to dynamically create content:
function addElement(parentId, elementTag, elementId, html) {
let parentElement = document.getElementById(parentId);
let elementToAdd = document.createElement(elementTag);
elementToAdd.setAttribute('id', elementId);
elementToAdd.innerHTML = html;
parentElement.appendChild(elementToAdd);
}
My function within my component:
static addMatch() {
let html = "<div className=\"form-group\"><label className=\"control-label\">Add Match</label>" +
"<DatePickerselected={this.state.startDate}onChange={this.handleChange.bind(this)}/></div>";
addElement('fixture-parent', 'newMatch', uuid(), html);
}
My full react component is below:
import React, {Component} from "react";
import DatePicker from "react-datepicker";
import {addElement} from "../../helpers/DynamicElementsHelper";
import moment from "moment";
const uuid = require('uuid/v1');
require('react-datepicker/dist/react-datepicker.css');
class Fixtures extends Component {
constructor() {
super();
Fixtures.addMatch = Fixtures.addMatch.bind(this);
this.state = {
startDate: moment()
};
}
handleChange(date) {
this.setState({
startDate: date
});
}
static addMatch() {
let html = "<div className=\"form-group\"><label className=\"control-label\">Add Match</label>" +
"<DatePicker selected={this.state.startDate} onChange={this.handleChange.bind(this)} /></div>";
addElement('fixture-parent', 'newMatch', uuid(), html);
}
render() {
return (
<div className="tray tray-center">
<div className="row">
<div className="col-md-8">
<div className="panel mb25 mt5">
<div className="panel-heading">
<span className="panel-title">Fixtures</span>
<p>A list of fixtures currently on the system, pulled in via ajax from Ratpack</p>
</div>
<div className="panel-body p20 pb10">
<div id="fixture-parent" className="form-horizontal">
<div className="form-group">
<label className="control-label">Add Match</label>
<DatePicker
selected={this.state.startDate}
onChange={this.handleChange.bind(this)}/>
</div>
</div>
</div>
<button onClick={Fixtures.addMatch }>Add Match</button>
</div>
</div>
</div>
</div>
);
}
}
export default Fixtures;
In React, it is recommended to not interact with the DOM directly. Instead, you should modify the JSX depending on data that you have. For your code, instead of adding an HTML tag with data from the state that changes, you should change the state and display information based on that:
addMatch() {
//Add a start date to the list of starting dates
this.setState({
listOfStartDates: [...this.state.listOfStartDates, newDate]
});
}
render(){
//For each starting date, generate a new 'match' as you called them
// note: this is the same as the stringyfied HTML tag OP had in the question
let listOfMatches = this.state.listOfStartDates.map((date)=>{
return (
<div className="form-group">
<label className="control-label">
Add Match
</label>
<DatePicker selected={date} onChange={this.handleChange.bind(this)} />
</div>
);
/* then in here your returned JSX would be just as OP originally had, with the exception that you would have {listOfMatches} where OP used to have the inserted list of HTML tags */
return //...
}
Since the component will re-render every time the state changes, the component will always have as many matches as you have starting dates.
Hope that helps!
Put a space between DatePicker and selected.
"<DatePicker selected={this.state.startDate}onChange={this.handleChange.bind(this)}/></div>";
I have have a Questionnaire object that renders several QuestionnaireOption subclasses. New QuestionnaireOption subclasses are rendered when the state changes in the parent Questionnaire object.
The QuestionnaireOption class maintains state if its "selected" or not.
The Issue: When I change the state in the parent class in order to render new "Option" nodes, the new nodes are assigned the same data-reactid, I expect the Option node to reset its internal state but it isn't assigned a new id and it has contains the wrong state (in this instance, selected is still set to true on a new object despite props being set with new data).
What can I do to work around this issue?
Here's the relevant code:
QuestionnaireOption = React.createClass({
getInitialState: function() {
return {selected: false}
},
handleClick: function(e) {
e.preventDefault();
this.setState({selected: !this.state.selected});
},
render: function() {
var fullClassName = "questionnaireOption " + (this.state.selected? "selected": "unselected");
return (
<div className='questionnaireOptionWrapper large-4 small-4 columns'>
<div className={fullClassName} onClick={this.handleClick}>
<div>{this.props.name}</div>
</div>
</div>
);
}
});
Questionnaire = React.createClass({
getInitialState: function() {
return {currentStage: 0}
},
saveOptionState: function() {
// dump option state into amber.js or localstorage
},
advanceWizard: function() {
this.saveOptionState();
this.setState({currentStage: this.state.currentStage + 1});
},
rewindWizard: function() {
this.saveOptionState();
this.setState({currentStage: this.state.currentStage - 1});
},
seeResults: function() {
console.log(globalOptionState);
},
render: function() {
var currentWizardQuestion = wizardQuestions[this.state.currentStage];
var currentOptionNodes = currentWizardQuestion.options.map(function(option) {
node = (
<QuestionnaireOption
name={option.name}
value={option.value}
/>
);
return node;
});
return (
<div className="questionnaire row">
<div className="questionnaire-question large-8 small-12 columns">
<div className="questionnaire-question-text">
{currentWizardQuestion.text}
</div>
<div className="questionnaire-question-subtext">
{currentWizardQuestion.subtext}
</div>
<div className="row">
{currentOptionNodes}
</div>
<input type="button" value="Back" onClick={this.rewindWizard}
style={this.state.currentStage == 0? {display: "none"}: {}
} />
<input type="button" value="Next" onClick={this.advanceWizard}
style={this.state.currentStage == wizardQuestions.length - 1?
{display: "none"}: {}
} />
<input type="button" value="Finish" onClick={this.seeResults}
style={this.state.currentStage < wizardQuestions.length - 1?
{display: "none"}: {}
} />
</div>
</div>
);
}
});
In your console you have this warning:
Each child in an array should have a unique "key" prop. Check the render method of App. See fb.me/react-warning-keys for more information.
If you don't, you're not using the development build: you should fix that.
React uses two things to determine if something is 'the same' between renders: the component class (e.g. QuestionnaireOption), and the key prop.
If either doesn't match the previous render, react considers it different, and the instance is recreated* and the subtree dom is discarded.
Assuming option.name can be used to determine equality, change your code to this:
var currentOptionNodes = currentWizardQuestion.options.map(function(option) {
var node = (
<QuestionnaireOption
name={option.name}
value={option.value}
key={option.name}
/>
);
return node;
});
For reference, reactid is an implementation detail, and may change or be removed at any time.
* if you just change the order of items, it'll try to just change the order for performance. There's currently a few cases where this doesn't happen, so it shouldn't be relied on.