I learn React and I faced some problems. I thought I understand controlled components, but well, it seems otherwise. Could you explain me, why after onChange event I receive props for rest Input components? If I want add label that depends on this.pops.name it gets messy (because of props I see in console.log). I would be grateful for explanation.
import React, { Component } from "react";
class Input extends Component {
handleChange = (e) => {
this.props.onInputChange(e);
};
chooseLabel = (props) => {
let { name } = props;
console.log(name);
if (name === "cost") {
return (
<label className="label" htmlFor={name}>
How much do you earn per hour?
</label>
);
} else if (name === "salary") {
return (
<label className="label" htmlFor={name}>
How much do you want to spend?
</label>
);
} else {
return null;
}
};
render() {
console.log("this.props.nevVal", this.props.newVal);
console.log("this.props.name", this.props.name);
return (
<div>
{this.chooseLabel(this.props)}
<input
className="Input"
value={this.props.newVal}
name={this.props.name}
placeholder={this.props.name}
onChange={this.handleChange}
/>
</div>
);
}
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
salary: "",
cost: "",
tip: ""
};
}
handleInputChange = (e) => {
console.log("E.target.name", e.target.name);
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<div className="App">
<Input
name="salary"
newVal={this.state.salary}
onInputChange={this.handleInputChange}
/>
<Input
name="cost"
newVal={this.state.cost}
onInputChange={this.handleInputChange}
/>
<Input
name="tip"
newVal={this.state.tip}
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
Link to Codesandbox.io
When one of the inputs is changed, you update the state of your App component. And when a state of a component is updated, it re-renders, and all its children too. So all the inputs are re-rendered (and their newVal are logged to the console), even if you change the value of a single input.
I tried to illustrate it in the following snippet :
class Input extends React.Component {
handleChange = (e) => {
this.props.onInputChange(e);
};
chooseLabel = (props) => {
let { name } = props;
// console.log(name);
if (name === "cost") {
return (
<label className="label" htmlFor={name}>
How much do you earn per hour?
</label>
);
} else if (name === "salary") {
return (
<label className="label" htmlFor={name}>
How much do you want to spend?
</label>
);
} else {
return null;
}
};
render() {
console.log(`${this.props.name} input renderd`)
return (
<div>
{this.chooseLabel(this.props)}
<input
className="Input"
value={this.props.newVal}
name={this.props.name}
placeholder={this.props.name}
onChange={this.handleChange}
/>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
salary: "",
cost: "",
tip: ""
};
}
handleInputChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
console.log("App component and all its children rendered :")
return (
<div className="App">
<Input
name="salary"
newVal={this.state.salary}
onInputChange={this.handleInputChange}
/>
<Input
name="cost"
newVal={this.state.cost}
onInputChange={this.handleInputChange}
/>
<Input
name="tip"
newVal={this.state.tip}
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Was that your question ?
Related
How can I do a 2 way binding of a variable from the parent component (Form.js), such that changes occurred in the child component (InputText.js) will be updated in the parent component?
Expected result: typing values in the input in InputText.js will update the state of Form.js
In Form.js
render() {
return (
<div>
<InputText
title="Email"
data={this.state.formInputs.email}
/>
<div>
Value: {this.state.formInputs.email} // <-- this no change
</div>
</div>
)
}
In InputText.js
export default class InputText extends React.Component {
constructor(props) {
super(props);
this.state = props;
this.handleKeyChange = this.keyUpHandler.bind(this);
}
keyUpHandler(e) {
this.setState({
data: e.target.value
});
}
render() {
return (
<div>
<label className="label">{this.state.title}</label>
<input type="text" value={this.state.data} onChange={this.handleKeyChange} /> // <-- type something here
value: ({this.state.data}) // <-- this changed
</div>
)
}
}
You can manage state in the parent component itself instead of managing that on child like this (lifting state up):
In Form.js
constructor(props) {
super(props);
this.handleKeyChange = this.keyUpHandler.bind(this);
}
keyUpHandler(e) {
const { formInputs } = this.state;
formInputs.email = e.target.value
this.setState({
formInputs: formInputs
});
}
render() {
// destructuring
const { email } = this.state.formInputs;
return (
<div>
<InputText
title="Email"
data={email}
changed={this.handleKeyChange}
/>
<div>
Value: {email}
</div>
</div>
)
}
In InputText.js
export default class InputText extends React.Component {
render() {
// destructuring
const { title, data, changed } = this.props;
return (
<div>
<label className="label">{title}</label>
<input type="text" value={data} onChange={changed} />
value: ({data})
</div>
)
}
}
You can also make your InputText.js a functional component instead of class based component as it is stateless now.
Update: (How to reuse the handler method)
You can add another argument to the function which would return the attribute name like this:
keyUpHandler(e, attribute) {
const { formInputs } = this.state;
formInputs[attribute] = e.target.value
this.setState({
formInputs: formInputs
});
}
And from your from you can send it like this:
<input type="text" value={data} onChange={ (event) => changed(event, 'email') } />
This assumes that you have different inputs for each form input or else you can pass that attribute name also from parent in props to the child and use it accordingly.
You would need to lift state up to the parent
parent class would look something like
onChangeHandler(e) {
this.setState({
inputValue: e.target.value // you receive the value from the child to the parent here
})
}
render() {
return (
<div>
<InputText
title="Email"
onChange={this.onChangeHandler}
value={this.state.inputValue}
/>
<div>
Value: {this.state.inputValue}
</div>
</div>
)
}
children class would look something like
export default class InputText extends React.Component {
constructor(props) {
super(props);
this.state = props;
}
render() {
return (
<div>
<label className="label">{this.state.title}</label>
<input type="text" value={this.state.value} onChange={this.props.onChange} />
value: ({this.state.value})
</div>
)
}
}
You can simply pass a callback from Form.js to InputText and then call that callback in InputText on handleKeyChange
I'm using react-select to render two types of "job" options. From the dropdown the user is able to select either of the options. Based on the option selected they should render different divs that contain more input values. The input values are dependent on the selectedOption. I have two class components with render methods that should display the different inputs corresponding to the jobType
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedOption: null
};
this.onChange = this.onChange.bind(this);
this.handleChange = this.handleChange.bind(this);
}
onChange = e => {
this.set({ [e.target.name]: e.target.value });
console.log([e.target.value]);
};
handleChange = selectedOption => {
this.setState({ selectedOption });
console.log("Option selected: ", selectedOption);
};
render() {
const { selectedOption } = this.state;
return (
<div>
<fieldset>
<legend>Parameters</legend>
<label htmlFor="jobType" style={{ display: "block" }}>
jobType:
</label>
<div>
<Select
value={selectedOption}
onChange={this.handleChange}
options={options}
placeholder="Select a jobType..."
isSearchable={options}
/>
</div>
</fieldset>
<div>
{selectedOption === "Batch" ? (
<BatchParams />
) : selectedOption === "Streaming" ? (
<StreamingParams />
) : null}
</div>
</div>
);
}
}
So let's say when the Streaming option is selected from the dropdown the following class component should be rendered:
class StreamingParams extends React.Component {
constructor(props) {
super(props);
this.state = {
maxHits: 1
};
this.handleMaxHitsChange = this.handleMaxHitsChange.bind(this);
}
handleMaxHitsChange = e => {
this.set({ [e.target.name]: e.target.valu });
};
render() {
return (
<div>
<label>maxHits:</label>
<input
type="number"
name="maxHits"
onChange={this.handleMaxHitsChange}
placeholder="1"
min="1"
step="1"
required
/>
</div>
);
}
}
I can't quite get the additional input boxes to render, not sure if I'm taking the correct approach here. I have included a codesandbox with my issue.
So it looks like the value that is getting set in this.state.selectedOption is an object with a value and a label field.
So you need to key off of those. Instead of doing this
{selectedOption === "Batch" ? (
<BatchParams />
) : selectedOption === "Streaming" ? (
<StreamingParams />
) : null}
Do this
{selectedOption && selectedOption.value === "batch" ? (
<BatchParams />
) : selectedOption && selectedOption.value === "streaming" ? (
<StreamingParams />
You should opt for value instead of label in case of localization or translations.
Fork
https://codesandbox.io/s/ll5p30nx5q
Only the ternary operator has to be changed: find an updated codesandbox here.
In the ternary operators you will have to check this.state.selectedOption.value as that is where your option value is stored.
{!this.state.selectedOption ?
null :
this.state.selectedOption.value === "batch" ? (
<BatchParams />
) : (
<StreamingParams />
)
}
Also you will have to first check for null as that is the initial value you get for this.state.selectedOption when no element in the select has been set.
The issue was that you have to take the value in the selectedOptions and assign that value to the selectedOption in the event handler setState. You were trying to assign an object and therefore the issue. Have made the changes in the sandbox also. Hope it helps.
import React from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
import "./styles.css";
const options = [
{ value: "batch", label: "Batch" },
{ value: "streaming", label: "Streaming" }
];
class BatchParams extends React.Component {
constructor(props) {
super(props);
this.state = {
batchNumber: 1
};
this.handleBatchNumChange = this.handleBatchNumChange.bind(this);
}
handleBatchNumChange = e => {
this.set({ [e.target.name]: e.target.valu });
};
render() {
return (
<div>
<label>batchNumberSize:</label>
<input
type="number"
name="batchNumberSize"
onChange={this.handleBatchNumChange}
placeholder="1"
min="1"
step="1"
required
/>
</div>
);
}
}
class StreamingParams extends React.Component {
constructor(props) {
super(props);
this.state = {
maxHits: 1
};
this.handleMaxHitsChange = this.handleMaxHitsChange.bind(this);
}
handleMaxHitsChange = e => {
this.set({ [e.target.name]: e.target.valu });
};
render() {
return (
<div>
<label>maxHits:</label>
<input
type="number"
name="maxHits"
onChange={this.handleMaxHitsChange}
placeholder="1"
min="1"
step="1"
required
/>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedOption: null
};
this.onChange = this.onChange.bind(this);
this.handleChange = this.handleChange.bind(this);
}
onChange = e => {
this.set({ [e.target.name]: e.target.value });
console.log([e.target.value]);
};
handleChange =( {value}) => {
console.log(value);
this.setState({ selectedOption: value });
};
render() {
const { selectedOption } = this.state;
return (
<div>
<fieldset>
<legend>Parameters</legend>
<label htmlFor="querySchemaName" style={{ display: "block" }}>
jobType:
</label>
<div>
<Select
value={selectedOption}
onChange={this.handleChange}
options={options}
placeholder="Select a jobType..."
isSearchable={options}
/>
</div>
</fieldset>
<div>
{selectedOption === "batch" && <BatchParams /> }
{selectedOption === "streaming" && <StreamingParams />}
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Example code:
const menu = [
{type: "home", visSelector: someSelector},
{type: "accounts", visSelector: anotherSelector}
]
const filterMenuItems = (state, menu) =>
menu.filter(i => i.visSelector(state))
const mapStateToProps = state => {
menuItems: filterMenuItems(state, menu)
}
What is the right way to memoize filterMenuItems?
visSelector are already memoized. Unfortunately, result of this filtration rerender component every time.
Provided the items are pure component, they won't all be re-rendered every time. Only the ones you're adding back after having previously removed them.
For instance, here we start out with all four children shown. If you uncheck one of the checkboxes, two of the children disappear, but no children are re-rendered. It's only if you add them back that they're re-rendered.
const Child = props => {
console.log("render", props.name);
return <div>{props.name}</div>
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.children = [
<Child key={0} name="zero" />,
<Child key={1} name="one" />,
<Child key={2} name="two" />,
<Child key={3} name="three" />
];
this.state = {
showOdds: true,
showEvens: true
};
}
evensClick = event => {
this.setState({showEvens: event.currentTarget.checked});
};
oddsClick = event => {
this.setState({showOdds: event.currentTarget.checked});
};
render() {
const {showEvens, showOdds, counter} = this.state;
return <div>
<label>
<input type="checkbox" onChange={this.evensClick} checked={showEvens} />
Show evens
</label>
<label>
<input type="checkbox" onChange={this.oddsClick} checked={showOdds} />
Show odds
</label>
<div>
{this.children.filter((child, index) => index % 2 == 0 ? showEvens : showOdds)}
</div>
</div>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.development.js"></script>
I don't think there's a way to prevent them being re-rendered when being added back.
Alternatively, you could use CSS to manager their visibility, which would prevent re-rendering them:
const Child = props => {
console.log("render", props.name);
return <div className={props.index % 2 == 0 ? "even" : "odd"}>{props.name}</div>
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.children = [
<Child index={0} key={0} name="zero" />,
<Child index={1} key={1} name="one" />,
<Child index={2} key={2} name="two" />,
<Child index={3} key={3} name="three" />
];
this.state = {
showOdds: true,
showEvens: true
};
}
evensClick = event => {
this.setState({showEvens: event.currentTarget.checked});
};
oddsClick = event => {
this.setState({showOdds: event.currentTarget.checked});
};
render() {
const {showEvens, showOdds, counter} = this.state;
const cls = (showEvens ? "" : "hide-evens ") + (showOdds ? "" : "hide-odds");
return <div>
<label>
<input type="checkbox" onChange={this.evensClick} checked={showEvens} />
Show evens
</label>
<label>
<input type="checkbox" onChange={this.oddsClick} checked={showOdds} />
Show odds
</label>
<div className={cls}>
{this.children}
</div>
</div>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
.hide-evens .even {
display: none;
}
.hide-odds .odd {
display: none;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.development.js"></script>
Array.filter() returns a new array on each call. You might want to try and memoize filterMenuItems based on your application requirements.
In the example below filterMenuItems, returns a new array only when state or menu arguments change:
import { createSelector } from 'reselect';
const filterMenuItems = createSelector(
state => state,
(state, menu) => menu,
(state, menu) => menu.filter(i => i.visSelector(state))
);
I want to print a new <ul> list of <li> movies.
I don't see any list nor elements.
I also get a warning:
index.js:2178 Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/docs/forms.html#controlled-components
in input (at index.js:54)
in label (at index.js:52)
in form (at index.js:51)
in div (at index.js:50)
in Movie (at index.js:70)
This is my code:
class Movie extends React.Component {
constructor(props) {
super(props);
this.state = {value: '',
list: [],
checked: true
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.addMovie = this.addMovie.bind(this);
this.listMovies = this.listMovies.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();
this.addMovie();
}
addMovie(value){
this.setState({ list: [...this.state.list, value] });
console.log(...this.state.list);
}
listMovies(){
return(
<ul>
{this.state.list.map((item) => <li key={this.state.value}>{this.state.value}</li>)}
</ul>
);
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Movie name:
<input name="movieName" type="text" value={this.state.movieName} onChange={this.handleChange} />
Favorite?
<input name="favorite" type="checkbox" checked={this.state.favorite} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
<button onClick={this.listMovies}>
List Movies
</button>
</div>
);
}
}
ReactDOM.render(
<Movie />,
document.getElementById('root')
);
I would really want to print only my Favorites movies
I'm guessing you want a simple movies list with favorites. Not the best one but working code:
import React from 'react';
import { render } from 'react-dom';
class App extends React.Component {
state = {
favorite: false,
movieName: "",
movies: [],
filter: true,
};
handleChange = (event) =>
event.target.name === "favorite"
? this.setState({ [event.target.name]: event.target.checked })
: this.setState( { [ event.target.name]: event.target.value } );
handleSubmit = ( event ) => {
event.preventDefault();
this.setState({
movies: [...this.state.movies, {name: this.state.movieName, favorite: this.state.favorite }]
});
}
listFavoriteMovies = () => (
<ul>
{this.state.movies
.filter( movie => movie.favorite )
.map( movie => <li>{movie.name}</li>)}
</ul>
);
listAllMovies = () => (
<ul>
{this.state.movies
.map(movie => <li>{movie.name}</li>)}
</ul>
);
changeFilter = () =>
this.setState( prevState => ( {
filter: !prevState.filter,
}))
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Movie name:
<input name="movieName" type="text" onChange={this.handleChange} />
Favorite?
<input name="favorite" type="checkbox" onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
<p>Showing only favorite movies.</p>
<ul>
{
this.state.filter
? this.listFavoriteMovies()
: this.listAllMovies()
}
</ul>
<button onClick={this.changeFilter}>Click toggle for all/favorites.</button>
</div>
);
}
}
render(<App />, document.getElementById('root'));
if you initially pass undefined or null as the value prop, the
component starts life as an "uncontrolled" component. Once you
interact with the component, we set a value and react changes it to a
"controlled" component, and issues the warning.
In your code initialise movieName in your state to get rid of warning.
For more information check here
I have been playing around with reactjs, and building some basic forms all works good but then it became complicated when i tried to break into smaller compoments
i have a simple component that has a form in it called XMLParser, and it has 2 smaller components inside called XMLInput and XMLResult.
XMLResult is pretty straight forward it just passes the value to as the props, but couldnt figure out what is the best approact to use XMLInput i couldnt get the binding to work with the child component, thanks for any pointers.
function EppResult(props) {
const resultXML = props.xmlData;
return <div style={styles.child}>
<textarea style={styles.outputBox} name="resultXML" value={resultXML} readOnly/>
</div>;
}
class EppInput extends Component{
render(){
return <textarea
style={styles.inputBox}
placeholder="Enter XML here!"
name="xmlData"
value={this.props.value}
onChange={this.props.handleInputChange}
/>;
}
}
class XMLParser extends Component {
constructor(props) {
super(props);
this.state = {xmlData : ""};
this.state = {resultXML : ""};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log("do submit");
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<div style={styles.container}>
<form onSubmit={this.handleSubmit}>
<div style={styles.container}>
<div style={styles.child}>
<XMLInput value={this.state.xmlData} onChange={this.handleInputChange} />
</div>
<div style={styles.child}>
<div style={styles.formContainer}>
<button style={styles.formElement}>Run!</button>
</div>
</div>
<XMLResult xmlData={this.state.resultXML}/>
</div>
</form>
</div>
);
}
}
I'm seeing a number of problems:
EppInput is the sub-component name, but XMLInput is used in the main component
You pass an onChange prop, but refer to it as
this.props.handleInputChange -- it should be
this.props.onChange
Just a note--I don't know where the styles object is, but I'm going
to assume it's available to all the components
I haven't tested this, but here's a basic cleanup, with some alterations to see some other ways of doing things:
// stateless functional component
const XMLResult = ({ xmlData }) => (
<div style={styles.child}>
<textarea
style={styles.outputBox}
name="resultXML"
value={xmlData}
readOnly
/>
</div>
);
// stateless functional component
// props are passed directly to the child element using the spread operator
const XMLInput = (props) => (
<textarea
{...props}
style={styles.inputBox}
placeholder="Enter XML here!"
name="xmlData"
/>
);
class XMLParser extends Component {
constructor(props) {
super(props);
this.state = { xmlData: "", resultXML: "" };
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log("do submit");
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<div style={styles.container}>
<form onSubmit={this.handleSubmit}>
<div style={styles.container}>
<div style={styles.child}>
<XMLInput
value={this.state.xmlData}
onChange={this.handleInputChange}
/>
</div>
<div style={styles.child}>
<div style={styles.formContainer}>
<button style={styles.formElement}>Run!</button>
</div>
</div>
<XMLResult xmlData={this.state.resultXML} />
</div>
</form>
</div>
);
}
}