I want to implement an X icon inside Input component that will clear the input field. I can easily do it if I control the state. But is it actually possible with stateless component?
I use react-semantic-ui, their stateful components have auto controlled state.
So I want to create an input that can be used like this:
//Controlled
class App extends React.Component {
state = {
value:''
}
onChange = (event, props) => {
this.setState({value: props.value});
}
onClearInput = () => {
this.setState({value: ''});
}
render() {
return (
<MyInput
clearable
value={this.state.value}
onChange={this.onChange}
onClearInput={this.onClearInput}
/>
)
}
}
Or
// Uncontrolled
class App extends React.Component {
onChange = (event, props) => {
doSomething(props.value);
}
render() {
return (
<MyInput
clearable
onChange={this.onChange}
/>
)
}
}
In the second example, clearable feature will not work because we're not controlling the value.
MyInput can be implemented like this:
import React from 'react';
import { Input } from 'semantic-ui-react';
import ClearIcon from './ClearIcon';
function MyInput(props) {
const prepareProps = {...props};
if (props.clearable) {
prepareProps.icon=<ClearIcon onClick={props.onClearInput} />;
delete prepareProps.clearable;
}
delete prepareProps.onClearInput;
return (
<div className="my-input">
<Input {...prepareProps} />
</div>
);
}
...etc.
My problems:
clearable feature must work in both controlled and uncontrolled manner.
clearable feature should not require a handler. It would be nice to just provide a prop and handle the render and behavior of the X button under the hood.
I don't see any way to make this work. Any ideas?
Allowing the user of your component to set the value via props and still being able to clear the input can be easily achieved, e.g. like this:
class MyInput extends React.Component {
constructor(props) {
super(props);
this.state = {value: props.value || ''};
}
handleChange = event => {
const { onChange } = this.props;
this.setState({ value: event.currentTarget.value });
onChange && onChange(event);
};
handleClear = () => {
const { onClearInput } = this.props;
this.setState({ value: "" });
onClearInput && onClearInput();
};
render() {
const { value } = this.state;
const { clearable, onChange, ...inputProps } = this.props;
const clearIcon = clearable && <ClearIcon onClick={this.handleClear} />;
return (
<div className="my-input">
<Input value={value} icon={clearIcon} onChange={this.handleChange} {...inputProps} />
</div>
);
}
}
You could even make it more composable by using an hoc or render props as proposed by #pkuzhel.
Look at this codesandbox example to see it in action.
#Andrey
Would you try this below code? and let me know if that resolves your issue.
import React, { Component } from 'react';
import { Input, Button } from 'semantic-ui-react'
class App extends Component {
clear = () => {
console.log(this.inputRef.target.value);
this.inputRef.target.value = '';
}
render() {
return (
<div className="App">
<Input placeholder='Search...' onChange={(input) => {input.persist(); this.inputRef = input}} />
<Button onClick={this.clear}>Clear</Button>
</div>
);
}
}
Related
This may seem kind of basic but I'm just learning how to use React. Currently what I have going is when I type in the input field and submit, the system console logs my 'search' input. What I'm trying to do is pass my 'search' data from my child component to the parent. Looking for any tips or leads to the right direction.
This is what I have for my child component:
export default class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ''
};
}
onChange = event => {
this.setState({ search: event.target.value });
};
onSubmit = event => {
const { search } = this.state;
event.preventDefault();
console.log(search);
};
render() {
return (
<div className='search-bar'>
<form onSubmit={this.onSubmit}>
<input
className='search'
type='text'
placeholder='Search'
onChange={this.onChange}
search={this.props.search}
value={this.state.searchinput}
parentCallback={this.onChange}
></input>
</form>
<FontAwesomeIcon className='search-icon' icon={faSearch} />
</div>
);
}
}
And in my Parent component (nothing much at the moment)
export default class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ''
};
}
searchUpdate = search => {
console.log(search);
};
render() {
console.log(this.props.search);
return (
<div className='container'>
<SearchBar/>
</div>
);
}
}
Generally to pass data from child component to Parent Component, you can pass a reference of a function as props to child component from parent component and call that passed function from child component with data.
You can do something like this:
export default class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ''
};
}
onChange = event => {
this.setState({ search: event.target.value });
};
onSubmit = event => {
const { search } = this.state;
event.preventDefault();
console.log(search);
this.props.passSearchData(search);
};
render() {
return (
<div className='search-bar'>
<form onSubmit={this.onSubmit}>
<input
className='search'
type='text'
placeholder='Search'
onChange={this.onChange}
search={this.props.search}
value={this.state.searchinput}
parentCallback={this.onChange}
></input>
</form>
<FontAwesomeIcon className='search-icon' icon={faSearch} />
</div>
);
}
In parent component:
export default class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ''
};
}
searchUpdate = search => {
console.log(search);
this.setState({ ...state, search: search })
};
render() {
console.log(this.props.search);
return (
<div className='container'>
<SearchBar passSearchData={this.searchUpdate} />
</div>
);
}
The simplest way would be to pass a function from parent to child:
// in parent component
const setSearchValue = (search) => {
// setState to search
this.setState({search});
}
render (){
return <>
<SearchBar onsearch={this.setSearchValue} />
</>
}
// in child component
// change your searchUpdate
searchUpdate = () => {
const {onsearch} = this.state;
// function call to pass data to parent
this.props.onsearch(onsearch)
}
Just have a function that is passed as a prop to the child component. Let child component do the handle change part and pass the value back to the parent and then do whatever you want to with the value
Code sandbox: https://codesandbox.io/s/react-basic-example-vj3vl
Parent
import React from "react";
import Search from "./Search";
export default class Parent extends React.Component {
searchUpdate = search => {
console.log("in parent", search);
};
render() {
console.log(this.props.search);
return (
<div className="container">
<Search handleSearch={this.searchUpdate} />
</div>
);
}
}
Child
import React from "react";
export default class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ""
};
}
onChange = event => {
this.setState({ search: event.target.value }, () => {
console.log("in child", this.state.search);
this.props.handleSearch(this.state.search);
});
};
onSubmit = event => {
const { search } = this.state;
event.preventDefault();
console.log(search);
};
render() {
return (
<div className="search-bar">
<form onSubmit={this.onSubmit}>
<input
className="search"
type="text"
placeholder="Search"
onChange={this.onChange}
search={this.props.search}
value={this.state.searchinput}
/>
</form>
</div>
);
}
}
I need an input field with debounced search and value should be passed from parent component. But it doesn't work when value passed from parent component. What is the right way to implement it?
Codesandbox example https://codesandbox.io/embed/debounce-input-owdwj
Text field with debouncing
class MyTextField extends Component {
search = _.debounce(event => {
this.props.onChange(event.target.value);
}, 300);
handleChangeInput = event => {
event.persist();
this.search(event);
};
render() {
return (
<TextField
value={this.props.value}
placeholder="type"
onChange={this.handleChangeInput}
/>
);
}
}
Parent component storing the value of text field
class Form extends Component {
state = {
value: ""
};
handleChangeInput = value => {
this.setState({
value
});
};
render() {
return (
<div>
<h2>{this.state.value}</h2>
<MyTextField
value={this.state.value}
onChange={this.handleChangeInput}
/>
</div>
);
}
}
The problem here is that you are updating the component only after 300 seconds which will not update the input box also. first, you need to update the search box component whenever there is a keyup and later parent can be informed about the changed after 300 seconds
I have updated your code reference please check it out https://codesandbox.io/embed/debounce-input-gm50t
Declare your debounce function in componentDidMount is everything will be fine.
1) Without state
class MyTextField extends Component {
handleChangeInput = e => {
this.search(e.target.value)
};
componentDidMount() {
this.search =_.debounce((value) => {
this.props.onChange(value);
}, 300)
}
render() {
return (
<TextField
value={this.props.value}
placeholder="type"
onChange={this.handleChangeInput}
/>
);
}
}
export default MyTextField;
2) With state:
class MyTextField extends Component {
state = {
textValue: ''
}
handleChangeInput = e => {
this.setState({
textValue: e.target.value
}, () => { this.search()})
};
componentDidMount() {
this.search =_.debounce(() => {
this.props.onChange(this.state.textValue);
}, 300)
}
render() {
return (
<TextField
value={this.props.value}
placeholder="type"
onChange={this.handleChangeInput}
/>
);
}
}
export default MyTextField;
Hope that helps!!!
I am not able to pass the value from the child to parent component. I am new to react please guide me.
This is Greeting component -
/* eslint-disable react/prop-types */
import React from "react";
const Greeting = props => {
const isLoggedIn = props.isLoggedIn;
const name = props.name;
if (isLoggedIn) {
return <h1> Welcome Back {name}</h1>;
} else {
return <LoginInfo name={name} onChange={props.onChange} />;
}
};
function LoginInfo(props) {
return (
<div>
<h1> Please Login</h1>
<input type="text" value={props.name} onChange={props.onChange} />
</div>
);
}
export default Greeting;
This is login component -
import React, { Component } from "react";
import Greeting from "./Greeting";
import LogoutButton from "./LogoutButton";
import LoginButton from "./LoginButton";
class Login extends Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {
isLoggedIn: false,
name: ""
};
}
handleLoginClick() {
this.setState({ isLoggedIn: true });
}
handleLogoutClick() {
this.setState({ isLoggedIn: false });
}
onChange = e => {
this.setState({
name: e.target.value
});
};
render() {
const isLoggedIn = this.state.isLoggedIn;
const name = this.state.name;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting
isLoggedIn={isLoggedIn}
name={name}
onChange={this.onChange}
/>
{button}
</div>
);
}
}
export default Login;
I am using in my app.
<Login />
In greeting component How whatever user entered i can store in state and display in welcome back line with name.
I think you need to maintain state in your parent component and emit change event from child component and change state in parent.
like
In login component
nameChange = (e) => {
this.setState({
name: e.target.value
})
}
<Greeting isLoggedIn={isLoggedIn} name={this.state.name} nameChange={this.nameChange}/>
and in child
<input
type="text"
name="name"
value={this.props.name}
onChange={this.props.nameChange}
/>
So this is how I would refactor your code for the LogInInfo component to pass the values of the user to your LogIn component
First of all you need a state in LogInInfo since you want to store the values of the user, you will use the state for that
/* eslint-disable react/prop-types */
import React from "react";
const Greeting = props => {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <h1> Welcome Back</h1>;
} else {
return <LoginInfo submitForm={props.handleSubmitForm}/>;
}
};
class LoginInfo extends React.Component {
constructor(props) {
this.state = {
name: ""
}
}
handleChange = (event) => {
this.setState({ name: event.target.value })
}
render(){
return (
<div>
<h1> Please Login</h1>
// e is the event that is passed automatically to the function in onSubmit
<form onSubmit={e => this.props.submitForm(e, this.state.name)}>
<input
type="text"
name="name"
value={name}
onChange={this.handleChange}
/>
<input type="submit" />
</form>
</div>
);
}
}
export default Greeting;
At this point you have the value you want to pass to the parent component LogIn to pass this value up, you need a function in your parent component that would get this value for you and pass it to the child component so it can be used, we will do this via props. We also need a variable in the parent's state that will store this value
import React, { Component } from "react";
import Greeting from "./Greeting";
import LogoutButton from "./LogoutButton";
import LoginButton from "./LoginButton";
class Login extends Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {
isLoggedIn: false,
username: ""
};
}
handleLoginClick() {
this.setState({ isLoggedIn: true });
}
handleLogoutClick() {
this.setState({ isLoggedIn: false });
}
// when you use arrow functions they automatically bind to your component
handleSubmitForm = (e, value) => {
// the following line prevents the page from refreshing when you submit any form
e.preventDefault()
this.setState({ username: value })
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} submitForm={this.submitForm}/>
{button}
</div>
);
}
}
export default Login;
when you click the submit button it should update the state in the LogIn component. Hope this helps
You need to do 2 things:
create a function which will handle changes of the input in Login component, send it to Greeting and then to LoginInfo. the function would look something like:
onChange = (event) => this.setState({event.target.name: event.target.value})
note: event.target.name equals to name prop of input. the function above will work only if the name of the state that stores that input value equals to the input name, in your case name.
send the state that stores the value of the input to the LoginInfo. the input element should look like this:
<input type="text" name="name" value={valueFromLogin} onChange={props.onChangeFromLogin} />
I'm trying to get data from a props.children[Input] to it's parent[Form], to build an all in one form component. I am currently stuck at getting data to Input component to Form component.
This is what i have done so far =>
edit.js =>
export default class Edit extends Component {
constructor(props) {
super(props)
this.state = {
formFields: {
name: '',
email: ''
}
}
}
render() {
return (
<Form
id={ this.props.match.params.id }
onReceiveFormData={ this.onFiledDataChange.bind(this) }
>
<Input
label='name'
name='name'
value='some'
/>
<Input
label='email'
name='email'
value='email#gmail'
/>
</Form>
);
}
}
Input.js =>
export default class Input extends Component {
constructor(props) {
super(props)
this.state = {
value: this.props.value
}
}
static setValue(val) {
return val ? val : ''
}
handleInputChange = (event) => {
this.setState({
value : event.target.value
})
this.props.onInputChange({
[event.target.name] : event.target.value
})
}
render() {
return (
<input
name={ this.props.name }
value={ Input.setValue(this.state.value) }
/>
);
}
}
Form.js =>
export default class Form extends Component {
constructor(props) {
super(props)
}
saveClick() {
/**
Save logic => I want to get the field data here
**/
}
render() {
return (<div>
{
this.props.children
}
<Button
onClick={ () => this.saveClick() }
>Save</Button>
</div>
);
}
}
For example, If post -> edit page and user -> edit page =>
I would like to do setting the input value inside the Form.js, Form.js will be responsible for setting the values inside those Inputs when it is mounted and it will be responsible for sending the data to the server.
I would like to the know, the scenario I have suggest is possible or is there a better way to do it?
Invert control (make Input a controlled component). Keeping state inside components unnecessarily is generally a bad design practice. Look at the existing <input> it takes a value and an onChange. Your component should do too.
const Input = ({
className,
...rest
}) => (
<input
className={classNames('Input', className)}
type="text"
{...rest}
/>
);
Then the parent has the state for when the form is submitted in Edit.
onFormSubmit = e => {
e.preventDefault();
console.log(this.state.name, this.state.email);
}
onChange = name => e => this.setState({ [name]: e.target.value })
// ...
<Input onChange={this.onChange('name')} value={this.state.name} />
<Input onChange={this.onChange('email')} value={this.state.email} />
If you want Form to listen in on state changes you could do this:
onChange = (e, originalOnChange) => {
console.log('onChange called:', e.target.value);
originalOnChange && originalOnChange(e);
}
// ...
const children = React.Children.map(this.props.children, child =>
React.cloneElement(child, { onChange: e => this.onChange(e, child.props.onChange) })
);
It's a bit unorthodox but technically achievable.
I have App as my main component and TodoList to render my list of todos. When Add Task button is clicked, it opens an input which autosaves the input that we type. This is saved in a state addTaskInput which is then passed to TodoList component as a prop addItem when the focus on input is removed.
Now, we may edit the same element by simply clicking on it and typing. In that case, I want to update the same component. But if a new item is added, I want to add a new item to list.
1) How to check this?
2) I want to update my todo state inside TodoList component when list updates. Where should I call setState for that?
class App extends Component {
state = {
showInput: false,
addTaskInput: '',
addItem:''
}
showAddTask = (e) => {
this.setState({showInput: true})
}
saveInput = (e) => {
this.setState({addTaskInput: e.target.value})
}
populateTaskList = (e) => {
this.setState({addItem: e.target.value})
}
render() {
const {showInput, addTaskInput, addItem} = this.state;
return (
<div className="app">
<Button
message="Add Task"
bsStyle="primary"
onClick={this.showAddTask}
/>
{ showInput && <input
type="text"
placeholder="Add Task here..."
value={addTaskInput}
onChange={this.saveInput}
onBlur={this.populateTaskList}
/> }
<TodoList
addItem={addItem}
/>
</div>
);
}
}
class TodoList extends Component {
state = {
todoList: ['a','aaa'],
todo: []
}
componentDidMount(){
const {todo, todoList} = this.state;
todoList.map((val) => {
this.state.todo.push(<div key={val}>{val}</div>)
})
this.setState({todo});
}
static getDerivedStateFromProps(nextProps, prevState){
return{
...prevState,
...nextProps
}
}
render () {
const {addItem, todoList, todo} = this.state;
return(
<div>
{todo}
</div>
)
}
}
You can keep the todoList in your app Component and pass that variable to the TodoList component:
class TodoList extends Component {
static getDerivedStateFromProps(nextProps, prevState){
return{
...prevState,
...nextProps
}
}
render () {
return(
<div>
{this.props.todoList.map((val) => <div key={val}>{val}</div>)}
</div>
)
}
}
And your app Component
class App extends Component {
state = {
showInput: false,
addTaskInput: '',
addItem:'',
todoList: [],
}
showAddTask = (e) => {
this.setState({showInput: true})
}
saveInput = (e) => {
this.setState({addTaskInput: e.target.value})
}
populateTaskList = (e) => {
const actualList = this.state.todoList;
actualList.push(e.target.value);
this.setState({addItem: e.target.value, todoList:actualList })
}
render() {
const {showInput, addTaskInput, addItem, todoList} = this.state;
return (
<div className="app">
<Button
message="Add Task"
bsStyle="primary"
onClick={this.showAddTask}
/>
{ showInput && <input
type="text"
placeholder="Add Task here..."
value={addTaskInput}
onChange={this.saveInput}
onBlur={this.populateTaskList}
/> }
<TodoList
todoList={todoList}
/>
</div>
);
}
}
1) The only way I see is you maintain a key or index for each item only then you could update existing item. There is no way for you to know if the item is new or existing based on its value
2) To be able to update Todo's state you should give "addItem(item)" function on Todo which can be called with value that needs to be added/updated. And in that function you could update Todo's state. This function can be called from TodoList when text box loses focus or user stops typing.