I have a Checkbox component which is simple checkbox under the hood however styled to my needs, as it has custom 'fake element' which acts as original one.
Checkbox
import React, {Component} from 'react';
import FormGroup from 'react-bootstrap/lib/FormGroup';
import '../../../stylesheets/common/forms/Checkbox.scss';
export default class extends Component {
constructor(props) {
super(props);
this.state = {checked: props.checked};
}
toggle() {
const checked = !this.state.checked;
this.setState({checked});
}
/**
* custom prop indicates that we want create custom element to toggle checkbox ie. ImageCheckbox
*
*/
render() {
const {id, className, children, name, custom = false} = this.props;
const toggle = this.toggle.bind(this);
return (
<FormGroup className={`Checkbox ${className}`}>
<input id={id}
name={name}
type="checkbox"
checked={this.state.checked}
onChange={toggle}/>
{!custom && <div className="fake-checkbox" onClick={toggle}/>}
<label htmlFor={id}>{ children }</label>
</FormGroup>
)
}
}
As comment says it in code component allows to create a custom 'fake' element inside label to act as toggle for checkbox.
ImageCheckbox:
import React from 'react';
import Checkbox from '../../common/forms/Checkbox';
import '../../../stylesheets/common/forms/ImageCheckbox.scss';
export default props => (
<Checkbox className="ImageCheckbox" {...props} custom={true}>
<div className="image"
style={{backgroundImage: 'url(' + props.href + ')'}}>
<p>{ props.children }</p>
</div>
</Checkbox>
);
It works how I want but when i click ImageCheckbox I get warning in browser:
warning.js:36 Warning: _class is changing an uncontrolled input of type checkbox 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://facebook.github.io/react/docs/forms.html#controlled-components
Im not sure what I do wrong and should I be concerned about it.
I found answer in Adam's reply React - changing an uncontrolled input.
I've change undefined to false in constructor when prop isn't passed:
constructor(props) {
super(props);
const {checked = false} = props;
this.state = {checked};
}
Related
I am facing issues while updating the values. Initially I am taking the values from the parent class to put into the text box, and then if I want to update the values into the form through the child component it should basically set the state in child component and pass the updated values to the API. But now when I try to change the values in the text box, it only changes one character and doesn't keep track of the state of all the props. How can I solve this? I have tried using the defaultValue it does change the values but it cannot keep track of the state change.
PS: The updateToApi is just a sample function that is using post to update values into the api
my sample project is here
https://codesandbox.io/s/sad-perlman-ukb68?file=/src/parent.js
#class Parent#
import React from "react";
import "./styles.css";
import Child from "./child";
class Parent extends React.Component {
constructor() {
super();
this.state = {
data: {
username: ["mar"],
name: [null]
}
};
}
updateToApi(data) {
var username: data.username;
var name: data.name;
}
render() {
return (
<Child data={this.state.data} updateToApi={this.updateToApi.bind(this)} />
);
}
}
export default Parent;
##class Child##
import React from "react";
import "./styles.css";
import { Button } from "react-bootstrap";
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "",
name: ""
};
}
handleSubmit = e => {
e.preventDefault();
};
handleChange = e => {
const data = { ...this.state };
data[e.currentTarget.name] = e.currentTarget.value;
this.setState({ data });
};
render() {
return (
<>
<form onSubmit={this.handleSubmit}>
<label>
Username:
<input
type="text"
name="username"
value={
this.props.data.username !== "undefined"
? this.props.data.username
: this.state.username
}
onChange={this.handleChange}
/>
</label>
<b />
<label>
Name:
<input
type="text"
name="Name"
value={
this.props.data.name !== "undefined"
? this.props.data.name
: this.state.name
}
onChange={this.handleChange}
/>
</label>
<br />
<Button variant="primary" onClick={this.props.updateToApi} />
</form>
</>
);
}
}
export default Child;
Why do you have 2 separate states? You should get rid of the state in your Child component entirely and only work with the Parent's state. Put HandleChange function in your Parent component also and pass it down through props.
UPD
Well, if you want for changes in your inputs to be visible, you could change the onchange handler in your Child coponent to
handleChange = e => {
this.setState({
[e.currentTarget.name] : e.currentTarget.value });
};
and the Input value just to this.state.username
Though i'm still having hard time to grasp what you are trying to accomplish here. Having 2 separate conditional states for the input fields is just too complicated. Imagine if your app would be a bit more complex? You'd lost yourself to debugging this stuff:
value={ this.props.data.username !== "undefined"
? this.state.username
: this.state.username
}
So here i highly recommend you to reevaluate all your data strucuture and data flow within the app. You should have the least amount of sources of truth within your app. Ideally one. So just use the main state in the Parent component and pass down the props that are required.
import React from 'react'
import Input as InputAnt from 'antd'
class myInput extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
componentDidMount() {
this.inputRef.current.focus();
}
render() {
return (
<InputAnt ref={this.inputRef}/>
)
}
}
export default myInput;
Here the InputAnt does receive focus, but my concern is that : the 'myInput' component can be used by many other components to render an input along with so many other components. So maybe I don't want the input to get focus in all the cases wherever 'myInput' component is used, I only want to focus under particular scenarios, how can that be achieved?
Just pass an additional prop when you are using myinput.
<myInput shouldFocus />
and in the componentDidMount lifecycle hook:
componentDidMount() {
if (this.props.shouldFocus) {
this.inputRef.current.focus();
}
}
and when you want to use myInput but don't want to focus your input just use it as:
<myInput />
I am trying to implement a search bar while using the styled-components library for styling. My issue is that the queried value never changes if I used styled-components. This is my code
import styled from 'styled-components'
import React, from 'react'
const SearchBar = styled.input`
margin-top: 35px;
float: right;
`
class Header extends React.Component {
state = {
query: '',
}
handleNewQuery = () => {
this.setState({
query: this.search.value,
})
console.log(this.search.value);
}
render () {
return (
<SearchBar
placeholder='Search for...'
ref={input => this.search = input}
onChange={this.handleNewQuery}
/>
)
}
}
Which only works if I swap SearchBar with input, otherwise the log prints undefined
The base issue is the the ref that is being created is returning a StyledComponent, not an HTML input element. It simply does not have a value property. The reason it starts working when you removing the styled aspect and simply render an <input />, is then the ref is an actual HTML input element with a value property. Try logging the ref in the change event to see this with first the styled component then a standard input. Either way I'd try approaching it as a Controlled Component using value property and event.target.value instead of attempting to extract the value from a ref.
import React, { Component } from 'react';
import styled from 'styled-components';
import './style.css';
const SearchBar = styled.input`
margin-top: 35px;
float: right;
`;
class Header extends Component {
constructor() {
super();
this.state = {
query: ''
};
}
handleNewQuery = (e) => {
this.setState({
query: e.target.value
})
}
render() {
return (
<div>
<SearchBar
placeholder='Search for...'
onChange={this.handleNewQuery}
value={this.state.query}
/>
</div>
);
}
}
If you absolutely must use a ref with this styled component. You can used the property innerRef which is specific to styled components to access the underlying HTML input element. This would technically give you access the value property. Once again though, the best approach would simply be using a controlled component as described above. The below example is using the newer approach to creating refs, but it would depend on your version of React being used.
<SearchBar
placeholder='Search for...'
onChange={this.handleNewQuery}
value={this.state.query}
innerRef={this.search}
/>
Here is a StackBlitz showing the functionality in action including the innerRef.
Hopefully that helps!
SearchBar should take a value prop instead of using a ref to get the value. Something like this:
<SearchBar value={this.state.search} ... />
I actually prefer the design because it allows me to reuse the component easily with react state or redux state. But I am getting this warning. What do you suggest I do?
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:
in input (created by InventoryDisplay)
in InventoryDisplay (created by Connect(InventoryDisplay))
in Connect(InventoryDisplay)
in PersistGate
import React from 'react'
import {connect} from 'react-redux'
import {mapStatesToProps, mapDispatchToProps} from '../../redux/maps/maps';
class InventoryDisplay extends React.PureComponent{
constructor(props){
super(props)
this.state={
pagnation: true,
firstPage: null,
currentPage: null,
lastPage:null,
data:null,
limit:null,
}
}
componentDidMount(){
//sets the value of the inventory here
this.props.loadInventory();
}
componentDidUpdate(){
console.log(this.props.CurrentInventory)
this.setState({
currentPage:this.props.CurrentInventory.current_page
})
this.setState({
firstPage: this.props.CurrentInventory.from
})
this.setState({
lastPage: this.props.CurrentInventory.last_page
})
this.setState({ data: this.props.CurrentInventory.data })
this.setState({ total: this.props.CurrentInventory.total })
this.setState({
limit: this.props.CurrentInventory.per_page
})
}
render(){
return(
<label>
<span>Items</span>
<input className={'text-center'} type={'text'} size={2}
value={this.state.limit}/>
</label>
)
}
}
export default connect(mapStatesToProps,mapDispatchToProps)(InventoryDisplay);
<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>
in Provider
That is because, the value of your input is this.state.limit but you're not updating it. When the value of input is changed, the value of state is not changing. You have to give an onChange handler so that whenever the value inside your input changes, the value of the state also changes, rerendering the value inside your input. You can do something like
<input className={'text-center'} type={'text'} size={2} onChange={this.onChangeHandler} value={this.state.limit}/>
and write an onChangeHandler that looks something like
onChangeHandler = (e) => {
setState({limit: e.target.value})
}
Read more about controlled componenets in React docs
CONTEXT
I'm trying to get the value of an input field from a stateless component inside another stateless component and then use it to call a method. I'm using rebass for my UI component and doing this in Meteor + Mantra.
I understand that I could do this by using refs if I were using <input> HTML fields and not another stateless component.
PROBLEM
My current code yield preventDefault of undefined, and when removed, the console.log prints out each time the input changes, not on submit. I believe that my state applies to the entire Dashboard component, instead of the stateless Rebass <Input/>, but I do not know how to change this.
import React from 'react';
import {PageHeader, Container, Input,Button} from 'rebass';
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = { websiteUrl: ''};
this.onInputChange = this.onInputChange.bind(this);
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onInputChange(event) {
this.setState({websiteUrl:event.target.value});
}
onFormSubmit() {
event.preventDefault;
const {create} = this.props;
const {websiteUrl} = this.state.websiteUrl;
console.log(this.state.websiteUrl);
create(websiteUrl);
}
render() {
const { error } = this.props;
return (
<div>
<PageHeader
description="Dashboard Page"
heading="Dashboard"
/>
<Container>
<form>
<Input
value={this.state.websiteUrl}
type="text"
buttonLabel="Add Website"
label="Website"
name="add_website"
onChange={this.onInputChange}
/>
<Button
backgroundColor="primary"
color="white"
inverted={true}
rounded={true}
onClick={this.onFormSubmit()}
> Add Website </Button>
</form>
</Container>
</div>
);
}
}
export default Dashboard;
You should pass an event to the onFormSubmit function:
<Button
backgroundColor="primary"
color="white"
inverted={true}
rounded={true}
onClick={(event) => this.onFormSubmit(event)}
...