I have two hidden input fields that are being populated with the values from a prop when the node-fetch finishes and the values are being set without any issue, but I see the following warning.
Warning: A component is changing a controlled input of type hidden to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
While I understand the differences between controlled and uncontrolled, I can't seem to understand why my component would be creating a conflict between the two. Is there anything clear in my component code that could be causing this?
import React from 'react';
import isEqual from 'lodash/isEqual';
export default class DateFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
startDateValue: '',
endDateValue: ''
};
}
componentDidMount() {
this.setState({
startDateValue: this.props.startDateQuery,
endDateValue: this.props.endDateQuery
});
}
handleChange(input, value) {
this.setState({
[input]: value
})
}
componentWillReceiveProps(nextProps) {
if (!isEqual(this.props, nextProps)){
this.setState({ startDateValue: nextProps.startDateQuery, endDateValue: nextProps.endDateQuery });
}
}
render() {
return (
<div className="col-md-3">
<input type="hidden" name="_csrf" value={this.props.csrf} />
<div className="input-group blog-filter-date-range-picker">
<p>Blog Date Range:</p>
</div>
<div className="input-group blogFilterDatePicker">
<span className="input-group-addon"><i className="glyphicon glyphicon-calendar"></i></span>
<input type="text" name="blogDateRange" className="form-control blogFilterDatePicker" autoComplete="off" />
</div>
<input type="hidden" name="blogStartDate" className="form-control" value={this.state.startDateValue} onChange={e => this.handleChange('startDateValue', e.target.value)} />
<input type="hidden" name="blogEndDate" className="form-control" value={this.state.endDateValue} onChange={e => this.handleChange('endDateValue', e.target.value)} />
</div>
);
}
}
Parent Component:
import React from 'react';
import CatgoryFilter from './SearchFormFilters/CategoryFilter';
import DepartmentFilter from './SearchFormFilters/DepartmentFilter';
import TeamFilter from './SearchFormFilters/TeamFilter';
import TypeFilter from './SearchFormFilters/TypeFilter';
import DateFilter from './SearchFormFilters/DateFilter';
//Activity Feed - Search Form
export default class ActivityFeedSearchForm extends React.Component {
render() {
var clearFilters;
if(this.typeQuery || this.props.categoryQuery || this.props.departmentQuery || this.props.teamQuery || this.props.startDateQuery || this.props.endDateQuery || this.props.typeQuery){
clearFilters = Clear;
}
return (
<div className="row">
<div className="annotation-search-form col-md-10 col-md-offset-1">
<div clas="row">
<form action="/app" method="post" className="annotation-filter-fields">
<DateFilter csrf={this.props.csrf} startDateQuery={this.props.startDateQuery} endDateQuery={this.props.endDateQuery} />
<TypeFilter typeQuery={this.props.typeQuery} />
<CatgoryFilter category={this.props.category} categoryQuery={this.props.categoryQuery} />
<DepartmentFilter department={this.props.department} departmentQuery={this.props.departmentQuery} />
<TeamFilter team={this.props.team} teamQuery={this.props.teamQuery} />
<div className="col-md-1 annotation-filter-section filter-button-container">
<button type="submit" id="annotation-filter-submit">Filter</button>
{clearFilters}
</div>
</form>
</div>
</div>
</div>
)
}
}
Top-level Parent Component:
import React from 'react';
import fetch from 'node-fetch';
import ReactMarkdown from 'react-markdown';
import path from 'path';
import ActivityFeedSearchForm from './ActivityFeedSearchForm/ActivityFeedSearchForm';
import { API_ROOT } from '../config/api-config';
//GET /api/test and set to state
export default class ActivityFeed extends React.Component{
constructor(props, context) {
super(props, context);
this.state = this.context.data || window.__INITIAL_STATE__ || { team: [] };
}
fetchList() {
fetch(`${API_ROOT}` + '/api' + window.location.search, { compress: false })
.then(res => {
return res.json();
})
.then(data => {
this.setState({
startDateQuery: data.startDateQuery,
endDateQuery: data.endDateQuery,
});
})
.catch(err => {
console.log(err);
});
}
componentDidMount() {
this.fetchList();
}
render() {
return (
<div>
<Navigation notifications={this.state.notifications}/>
<ActivityFeedSearchForm csrf={this.state.csrf} category={this.state.category} categoryQuery={this.state.categoryQuery} department={this.state.department} departmentQuery={this.state.departmentQuery} team={this.state.team} teamQuery={this.state.teamQuery} typeQuery={this.state.typeQuery} startDateQuery={this.state.startDateQuery} endDateQuery={this.state.endDateQuery} />
<div className="activity-feed-container">
<div className="container">
<OnboardingInformation onboarding={this.state.onboardingWelcome} />
<LoadingIndicator loading={this.state.isLoading} />
<ActivityFeedLayout {...this.state} />
</div>
</div>
</div>
)
}
};
As you are dealing with only startDateValue and endDateValue in componentWillReceiveProps it’s good to compare them individually instead of whole props. Deep equality check is not happening for whole props and that’s why you get the warning
Change
componentWillReceiveProps(nextProps) {
if (!isEqual(this.props, nextProps)){
this.setState({ startDateValue: nextProps.startDateQuery, endDateValue: nextProps.endDateQuery });
}
}
To
componentWillReceiveProps(nextProps) {
if (this.props.startDateQuery != nextProps.startDateQuery && this.props.endDateQuery != nextProps.endDateQuery){
this.setState({ startDateValue: nextProps.startDateQuery, endDateValue: nextProps.endDateQuery });
}
}
Also you don’t need componentDidMount so remove that part in your code and update constructor code with below one
constructor(props) {
super(props);
this.state = {
startDateValue: this.props.startDateQuery ? this.props.startDateQuery: '',
endDateValue:this.props.endDateQuery ? this.props.endDateQuery: ''
};
}
}
And
Change
<input type="hidden" name="_csrf" value={this.props.csrf} />
To
<input type="hidden" name="_csrf" value={this.props.csrf ? this.props.csrf : "" />
And you are not binding handleChange so either bind it manually in constructor or change it to arrow function like below
Change
handleChange(input, value) {
this.setState({
[input]: value
})
}
To
handleChange = (input, value) => {
this.setState({
[input]: value
})
}
Related
I've got a few check boxes that, onClick, are pushed to state via .concat().
class App extends React.Component {
constructor() {
super()
this.state = {
selectedChoices: []
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(event) {
this.setState({
selectedChoices: [this.state.selectedChoices.concat(event.target.value)]
})
}
render() {
return(
<div>
<div onChange={this.handleClick}>
<input type="checkbox" name="choiceNumber" value="one" />
<input type="checkbox" name="choiceNumber" value="two" />
<input type="checkbox" name="choiceNumber" value="three" />
</div>
<p>{this.state.selectedChoices}</p>
</div>
)
}
}
Naturally, anytime the check box is clicked, regardless if it's .checked or not, that value is pushed to state.
I'm having some trouble finding the best way to go about this.
Above answer is correct but no need to use extra boolean "Flag".
use:
event.target.checked
Below snippet will work!
handleClick(event) {
if (!event.target.checked) {
this.setState({
selectedChoices: this.state.selectedChoices.filter(
(number) => number !== event.target.value
)
});
} else {
this.setState({
selectedChoices: this.state.selectedChoices.concat(event.target.value)
});
}
}
First, you don't have to wrap this.state.selectedChoices.concat(event.target.value) inside an array.
Second, you have to make condition if selected value has been added to selectedChoices you have to remove value from selectedChoices using filter. Else, you push value using concat just like you do.
import React from "react";
export default class App extends React.Component {
constructor() {
super();
this.state = {
selectedChoices: []
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
let flag = false;
for(const i in this.state.selectedChoices) {
if (this.state.selectedChoices[i] === event.target.value) {
flag = true;
break;
}
}
if(flag) {
this.setState({
selectedChoices: this.state.selectedChoices.filter(number => number !== event.target.value)
});
} else {
this.setState({
selectedChoices: this.state.selectedChoices.concat(event.target.value)
});
}
}
render() {
return (
<div>
<div onChange={this.handleClick}>
<input type="checkbox" name="choiceNumber" value="one" />
<input type="checkbox" name="choiceNumber" value="two" />
<input type="checkbox" name="choiceNumber" value="three" />
</div>
<p>{this.state.selectedChoices}</p>
</div>
);
}
}
here you can see how it run https://codesandbox.io/s/affectionate-diffie-0oi4t?file=/src/App.js
What I want to do is to be able to toggle an active class on my elements that are dynamically created, as to be able to change the css for the selected checkbox, giving the impression that a certain filter is selected. I have looked at so many solutions and guides to make this work for my app, but I can't seem to implement it correctly. Any help would be appreciated.
Checkboxes component
import React from 'react';
const Checkbox = (props) => {
const { label, subKey } = props;
const sub1 = `${subKey}1`;
return (
<label htmlFor={sub1} className="check_label">
{label}
<input
type="checkbox"
id={sub1}
checked={props.isChecked}
onChange={props.handleCheck}
onClick={() => console.log(label)}
value={`${label.toLowerCase()}/?search=`}
/>
</label>
);
};
export default Checkbox;
and the Search component that implements checkboxes
import React, { Component } from 'react';
import Checkbox from './Checkbox';
const APIQuery = 'https://swapi.co/api/';
const searchLabels = ['Planets', 'Starships', 'People', 'Species', 'Films', 'Vehicles'];
export default class Searchbutton extends Component {
constructor(props) {
super(props);
this.state = {
endpointValue: '',
searchValue: '',
};
}
/* Funcionality to handle form and state of form */
/* Changes state of value whenever the form is changed, in realtime. */
handleChange(event) {
this.setState({ searchValue: event.target.value });
}
/* Prevents default formsubmit */
handleSubmit(event) {
event.preventDefault();
}
/* Handles state of checkboxes and sets state as to prepend necessary filter for request */
handleCheck(event) {
this.setState({ endpointValue: event.target.value });
if (this.state.endpointValue === event.target.value) {
this.setState({ endpointValue: '' });
}
}
/* Creates the checkboxes dynamically from the list of labels. */
createBoxes() {
const checkboxArray = [];
searchLabels.map(item => checkboxArray.push(
<Checkbox
key={item}
className="madeBoxes"
subKey={item}
endpointValue={this.state.endpointValue}
handleChange={e => this.handleChange(e)}
handleCheck={e => this.handleCheck(e)}
label={item}
/>,
));
return checkboxArray;
}
render() {
return (
<div className="search_content">
<div className="search_wrapper">
<form onSubmit={this.handleSubmit} method="#">
<label htmlFor="searchBar">
<input type="text" id="searchbar" className="search_bar" value={this.state.searchValue} onChange={e => this.handleChange(e)} />
</label>
<div>
<input type="submit" className="search_button" value="May the Force be with you." onClick={() => this.props.searchWithApi(APIQuery + this.state.endpointValue + this.state.searchValue)} />
</div>
</form>
</div>
<div className="checkboxes">
{this.createBoxes(this.labels)}
</div>
<div className="sort_filters">
{' '}
{/* These are options that the user can make in order to sort and filter the results.
The idea is to make it so that changing the value auto-perform a new request */}
{/* For sorting the returned objects based on user choice */}
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid, until href added */}
Choose sort method
<ul className="sorting">
<li className="sort_optn" href="#" value="lexicographical">Alphabetically</li>
<li className="sort_optn" href="#" value="by_added_date">By added date</li>
<li className="sort_optn" href="#" value="by_added_date_rev">By added date reversed</li>
</ul>
</div>
</div>
);
}
}
You don't really have to do it with react. You can reformat your code a little bit and solve it with CSS :checked pseudo-class.
In particular, don't wrap your checkbox within a label, but instead put the label after the input. Check this fiddle for example: https://jsfiddle.net/8c7a0fx5/
You can use the styled-component package. check the example below on how to use it:
import { Component } from 'react'
import { render } from 'react-dom'
import styled from 'styled-components'
const StyledCheckbox = styled.div`
label {
background: ${props => props.active ? 'red': 'white'}
}
`
class MyAwesomeComponent extends Component {
constructor(){
super()
this.state = {
isChecked: false
}
this.handleOnChange = this.handleOnChange.bind(this)
}
handleOnChange = ()=>{
this.setState({
isChecked: !this.state.isChecked,
})
}
render(){
const { isChecked } = this.state
return(
<StyledCheckbox active={isChecked}>
<label>Names</label>
<input type="checkbox" onChange={this.handleOnChange} />
</StyledCheckbox>
)
}
}
render(<MyAwesomeComponent/>, document.getElementById('root'))
Working code on codepen.io
I have multiple checkbox in my React Class B.js:
<input
type="checkbox"
inline={true}
checked={this.props.checkBoxDefaultStatus}
onChange={this.handleCheckBoxClick}
/>
Now the prop checkBoxDefaultStatus is passed from parent class A.js.
A.js
this.state = {
checkBoxDefaultStatus: false
}
handleMultiSelect() {
this.setState({
checkBoxDefaultStatus: true
})
}
render() {
<B checkBoxDefaultStatus={this.state.checkBoxDefaultStatus} />
}
EDIT: Now All my child checkboxes are getting checked when I click on parent checkbox, but the issue is that my child checked boxes checked status does not change when I click on them as they are already set by parent prop. I need some way to maintain this also.
This is the behaviour I want https://stackoverflow.com/a/35218069/6574017
If you want change parent component state inside child component, then you have to pass parent component method to child component as props like below,
<B handleCheckBoxClick={this.handleMultiSelect}/>
Check below working code. I build 2 component for your scenario.
class B extends React.Component {
constructor(){
super();
this.state = {
checkBoxClick : {
1: false,
2: false
}
}
this.handleCheckBoxClick = this.handleCheckBoxClick.bind(this);
}
handleCheckBoxClick(no, event){
//console.log('no', no);
//console.log('event.target.value', event);
var checkBoxClick = this.state.checkBoxClick;
checkBoxClick[no] = !this.state.checkBoxClick[no];
this.setState({
checkBoxClick
});
var alltrue =Object.keys(checkBoxClick).every((k) =>{ return checkBoxClick[k] });
//console.log('alltrue', alltrue);
if(alltrue){
// console.log('alltrue in if : ', alltrue);
this.props.handleMultiSelect();
}
if(this.props.checkBoxDefaultStatus){
this.props.handleMultiSelect();
}
}
render(){
//console.log('this.state.checkBoxClick :', this.state.checkBoxClick);
//console.log('this.props.checkBoxDefaultStatus :', this.props.checkBoxDefaultStatus);
return(
<div>
Child component check-box <br />
<input
type="checkbox"
checked={this.props.checkBoxDefaultStatus ? this.props.checkBoxDefaultStatus : this.state.checkBoxClick[1]}
onChange={(e) => {this.handleCheckBoxClick(1, e.target.checked)}}
/> Bar 1<br />
<input
type="checkbox"
checked={this.props.checkBoxDefaultStatus ? this.props.checkBoxDefaultStatus : this.state.checkBoxClick[2]}
onChange={(e) => {this.handleCheckBoxClick(2, e.target.checked)}}
/> Bar 2<br />
</div>
);
}
}
class A extends React.Component {
constructor() {
super();
this.state = {
checkBoxDefaultStatus: false
}
this.handleMultiSelect = this.handleMultiSelect.bind(this);
}
handleMultiSelect() {
//console.log('aaaa')
this.setState({
checkBoxDefaultStatus: !this.state.checkBoxDefaultStatus
})
}
render() {
//console.log('checkBoxDefaultStatus :', this.state.checkBoxDefaultStatus);
return (
<div>
<input type="checkbox" onClick={() => {this.handleMultiSelect()}} checked={this.state.checkBoxDefaultStatus}/>
Check all<br />
<B checkBoxDefaultStatus={this.state.checkBoxDefaultStatus}
handleMultiSelect={()=>{this.handleMultiSelect()}}
/>
</div>
);
}
}
ReactDOM.render( < A / > , document.getElementById('root'));
<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='root'></div>
Please using checked prop instead of defaultChecked
like this:
<input
type="checkbox"
inline={true}
checked={this.props.checkBoxDefaultStatus}
onChange={this.handleCheckBoxClick}
/>
Its very simple.
Use event.target.checked to know the checkbox status.
Example:
HTML: <input type="checkbox" onClick={(e) => OnCheckboxClick(e)}/>
JS:
const OnCheckboxClick= (e) => {
if(e.target.checked) {
//blah blah
}
else {
//blah blah
}
}
Everything is okay until end of the first step but when i write the "tempPhone" numbers for confirm
[First Step][1]
input area seems like that (input field must be empty after clicked first button) and i didnt figure out that problem. why thats happening?
[1]: https://i.stack.imgur.com/qXYB3.png
[2]: https://i.stack.imgur.com/wRc4W.png
[Problem][2]
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor() {
super();
this.state= {
otpContent: "",
input: "",
tempPhone:"123",
tempPin: "123456",
errorMsg: ""
}
this.handleChange = this.handleChange.bind(this);
this.handlePhoneSubmit = this.handlePhoneSubmit.bind(this);
this.handlePinSubmit = this.handlePinSubmit.bind(this);
}
handleChange(e) {
this.setState({[e.target.name]: e.target.value});
}
phoneInput() {
this.setState(
{
otpContent: <div>
<input type="text" name="input" onChange={this.handleChange}/>
<button onClick={this.handlePhoneSubmit}> Dogrula!</button>
</div>
}
);
}
handlePhoneSubmit() {
if(this.state.input === this.state.tempPhone){
this.setState(
{
input: ''
}
);
this.pinInput();
}
else {
this.setState({
errorMsg: "wrong phone"
});
}
}
pinInput() {
this.setState(
{
input: '',
otpContent: (<div>
<input
type="text" name="input" onChange={this.handleChange}/>
<button onClick={this.handlePinSubmit}> Pin Dogrula!</button>
</div>)
}
);
}
handlePinSubmit() {
if(this.state.input === this.state.tempPin){
this.setSuccess();
}
else {
this.setState({
errorMsg: "wrong pin"
});
}
}
setSuccess() {
this.setState(
{
otpContent: (<div>
<h3>Success!</h3>
</div>)
}
);
}
componentDidMount() {
this.phoneInput();
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Hi</h1>
</header>
<div className="App-intro">
{this.state.otpContent}
{this.state.errorMsg}
</div>
</div>
);
}
}
export default App;
Your handling input logic is a little bit flawed. You are trying to use a controlled input element but at the same time you need to set a value to it that I think which is a bad mix. I am giving an example but I've changed the component logic too much. If it does not suit you just examine this answer as an example.
I don't hold any JSX in the state since I've never seen it (but who knows?), I've changed some rendering content and message logic. I've also use class-fields and arrow functions, so no need to bind them to this.
class App extends React.Component {
state = {
input: "",
tempPhone: "123",
tempPin: "123456",
msg: "",
confirm: false,
}
handleChange = e => this.setState({ [e.target.name]: e.target.value });
handlePhoneSubmit = () => {
if (this.state.input === this.state.tempPhone) {
this.setState({confirm: true, input:""});
}
else {
this.setState({
msg: "wrong phone"
});
}
}
handlePinSubmit = () => {
if (this.state.input === this.state.tempPin) {
this.setState({msg: "Success"});
}
else {
this.setState({
msg: "wrong pin"
});
}
}
phoneInput = () => (
<div>
<input type="text" value={this.state.input} name="input" onChange={this.handleChange} />
<button onClick={this.handlePhoneSubmit}> Dogrula!</button>
</div>
);
pinInput = () => (
<div>
<input
type="text" value={this.state.input} name="input" onChange={this.handleChange} />
<button onClick={this.handlePinSubmit}> Pin Dogrula!</button>
</div>
)
render() {
return (
<div className="App">
<div className="App-intro">
{!this.state.confirm
? this.phoneInput()
: this.pinInput()
}
{this.state.msg}
</div>
</div>
);
}
}
Still, this code can be improved. For example there could be one Input component, we can pass some props to render different input and buttons. But it works according to your needs right now.
You can handle this situation by using a controlled component (docs).
here you need to handle onChange and pass the changed value to input. On button press reset that value
<input
value={this.state.textValue} // whatever value you put in textValue state will be the text appear in input box on UI so
type="text"
onChange={e => this.handleTextChange(e.target.value)}
/>
codesandbox : here
This is Presentation file:
import React, { Component } from 'react'
class CreateZone extends Component {
constructor(){
super()
this.state = {
zone: {
name: '',
zipCodes: []
}
}
}
newZone(event){
let newZone = Object.assign({}, this.state.zone)
newZone[event.target.id] = event.target.value
this.setState({
zone: newZone
})
}
submitZone(event){
this.props.onCreate(this.state.zone)
}
render(){
return(
<div>
<input id="name" onChange={this.newZone.bind(this)} className="form-control" type="text" placeholder="Enter Zone Name" /><br />
<input id="zipCodes" onChange={this.newZone.bind(this)} className="form-control" type="text" placeholder="Enter Zip Code" /><br />
<button onClick={this.submitZone.bind(this)} className="btn btn-danger">Submit Comment</button>
</div>
)
}
}
export default CreateZone
This is Container File:
import React, { Component } from 'react'
import { Zone, CreateZone } from '../presentation'
import { APImanager } from '../../utils'
class Zones extends Component {
constructor(){
super()
this.state = {
zone: {
name: '',
zipCodes: '',
numComments: ''
},
list: []
}
}
componentDidMount(){
console.log('componentDidMount')
APImanager.get('/api/zone', null, (err, response) => {
if(err){
alert('Error in zones: '+err.message)
return
}
this.setState({
list: response.results
})
})
}
submitZone(zone){
let newZone = Object.assign({}, zone)
APImanager.post('/api/zone', newZone, (err, response) => {
if(err) {
alert('ERROR in New Zone: '+err.message)
return
}
console.log('NewZone: '+JSON.stringify(response))
let updatedList = Object.assign([], this.state.list)
updatedList.push(response.result)
this.setState({
list: updatedList
})
})
}
render(){
const listItems = this.state.list.map((zone, i) => {
return (
<li key={i}>
<Zone currentZone={zone} />
</li>
)
})
return(
<div>
<ol>
{listItems}
</ol>
<div>
<CreateZone onCreate={this.submitZone} />
</div>
</div>
)
}
}
export default Zones
React doesn't re-render and console log error is "Cannot read property 'list' of undefined"
It worked fine before I moved form in to presentation file. This is training for me and I would love to understand what is going on
The function submitZone in Zones Component is didn't get the "this" binded. So it was giving "this.state" as undefined. Bind the "this" same as
<button onClick={this.submitZone.bind(this)} className="btn btn-danger">Submit Comment</button>
So replace the line
<CreateZone onCreate={this.submitZone} />
with
<CreateZone onCreate={this.submitZone.bind(this)} />