I am trying to enable or disable a button based on whether or not there is text in my input but cant seem to achieve it. When I manually set {true OR false} in the disabled property of Button function it works fine but I am really confused on how to set that dynamically based on the content of the input.
Any guidance is super welcome!
This is my app code
import { useState } from "react";
function Input (props){
const { onChange, value } = props
return (<input value={value} onChange={onChange} type="text" placeholder="Add a ToDo" maxLength="50"/>)
}
function Button (props) {
const {onChange, state, text} = props
return (<button disabled={false} onChange={onChange}>{text}</button>)
}
function App() {
const [text, setText] = useState("");
const [state, setSate] = useState(true);
const handleChange = (event) => {
if (!setText(event.target.value)) {
setSate(false);
} else {
setSate(true);
}
};
return (
<div className="App">
<div className="container">
<Input value={text} onChange={handleChange} />
<Button onChange={() => handleChange(state)} text="Add" />
<Button onChange={() => handleChange(state)} text="Clean" />
</div>
);
}
export default App;
Button element should change to:
function Button (props) {
const {disabled, onChange, state, text} = props
return (<button disabled={disabled} onChange={onChange}>{text}</button>)
}
Rendering of it should change to:
...
<Button disabled={!text} onChange={() => handleBtn()} text="Add" />
...
Sandbox: https://codesandbox.io/s/zen-hawking-qqzkw?file=/src/App.js
The idea is to send down disabled prop which would be true if the there is no text in the field.
ALSO, handleChange should look like this:
const handleChange = (event) => {
setText(event.target.value);
};
because the rest of your code in that function does not do anything.
Buttons should have their own handler functions .e.g. const handleBtn = () => {};
So you should pass the state value you are using to store whatever the users write in the input to the button so that the button knows when the input has text on it or not. And then your second state value can be used to store your todo list, so something like this
import { useState } from "react";
function Input({ handleChange, value }) {
return (
<input
value={value}
onChange={handleChange}
type="text"
placeholder="Add a Todo"
maxLength="50"
/>
);
}
function Button({ handleClick, text, disabled }) {
return (
<button disabled={disabled} onClick={handleClick}>
{text}
</button>
);
}
function App() {
const [value, setValue] = useState("");
const [todoList, setTodoList] = useState([]);
const handleChange = (event) => {
setValue(event.target.value);
};
const handleAdd = () => {
setTodoList([...todoList, value]);
handleClear();
};
const handleClear = () => {
setValue("");
};
return (
<div className="App">
<div className="container">
<Input value={value} handleChange={handleChange} />
<Button handleClick={handleAdd} disabled={!value} text="Add" />
<Button handleClick={handleClear} disabled={!value} text="Clear" />
</div>
</div>
);
}
export default App;
Related
Can anyone give an answer?
Unable to update the state when I click getbtn -> placeRange pass jsx to setbtn ->then unable to update the State when Silde the Range.
import React, { useState } from "react";
export default function Stack() {
const [value, setvalue] = useState(0);
const [btn, setbtn] = useState(<></>);
function placeRange() {
const jsx = (
<>
<input
type="range"
onChange={(e) => {
setvalue(e.target.value);
}}
/>
<h1>{value}</h1>
</>
);
setbtn(jsx);
}
return (
<>
<button onClick={placeRange}>getrange</button>
{btn}
</>
);
}
This seems to be working for me.
Here's a working example at codesandbox
import { useState } from "react";
export default function App() {
const [value, setvalue] = useState(0);
const [btn, setbtn] = useState(<></>);
function placeBtn() {
const jsx = (
<>
<button
onClick={() => {
setvalue(1);
}}
>
Convert to 1
</button>
</>
);
setbtn(jsx);
}
return (
<>
<h1>{value}</h1>
<button onClick={placeBtn}>getbtn</button>
{btn}
</>
);
}
Not sure I fully understand what you are trying to achieve, this is not clear enough for me.
From your code it seems you are trying to set an input range in place when clicking the button, then, you want this range to update the number below it.
If this is the case I suggest the following solution:
import React, { useState } from "react";
export default function Stack() {
const [value, setvalue] = useState(0);
const [showRange, setShowRange] = useState(false);
function placeRange() {
setShowRange(!showRange);
}
return (
<>
<button onClick={placeRange}>getrange</button>
{showRange && (
<>
<input
type="range"
onChange={(e) => {
setvalue(e.target.value);
}}
/>
<h1>{value}</h1>
</>
)}
</>
);
}
I have a single input element from react-bootstrap that will allow the user to change the field value and 2 buttons will appear, one to accept the changes and the other will cancel the changes leaving the original value in.
I manage to control the focus and blur events by delegating the listeners to the wrapping component, my thinking is that since the focus is still within the wrapping component, I won't lose its focus, but pressing the inner buttons seems to blur the focus, therefore the Accept and Cancel buttons don't fire any events...
Here is my code example:
import { useState, useEffect, useRef } from "react";
import { InputGroup, Button, FormControl } from "react-bootstrap";
import "./styles.css";
const InputField = ({ title }) => {
const formRef = useRef(null);
const [value, setValue] = useState(title);
const [toggleButtons, setToggleButtons] = useState(false);
const onChange = (e) => {
setValue(e.target.value);
};
const onFocus = () => {
setToggleButtons(true);
};
const onBlur = () => {
setToggleButtons(false);
};
const acceptChange = () => {
console.log("Accept");
setToggleButtons(false);
};
const cancelChange = () => {
console.log("Cancel");
setToggleButtons(false);
};
useEffect(() => {
const form = formRef.current;
form.addEventListener("focus", onFocus);
form.addEventListener("blur", onBlur);
return () => {
form.removeEventListener("focus", onFocus);
form.removeEventListener("blur", onBlur);
};
}, []);
return (
<div className="App">
<InputGroup className="m-3" style={{ width: "400px" }}>
<FormControl
ref={formRef}
value={value}
onChange={onChange}
// onFocus={onFocus}
// onBlur={onBlur}
/>
{toggleButtons ? (
<InputGroup.Append>
<Button variant="outline-secondary" onClick={() => acceptChange()}>
Accept
</Button>
<Button variant="outline-secondary" onClick={() => cancelChange()}>
Cancel
</Button>
</InputGroup.Append>
) : null}
</InputGroup>
</div>
);
};
export default function App() {
return (
<>
<InputField title={"Input 1"} />
<InputField title={"Input 2"} />
<InputField title={"Input 3"} />
<InputField title={"Input 4"} />
</>
);
}
A couple of changes are needed to make this work:
The toggle buttons need to always be in the DOM, so hide them rather than only rendering if the focus is there.
To avoid hiding the buttons when the blur occurs from the input to one of the buttons you can check if the newly focused element is a sibling of the input by using the event's relatedTarget and the currentTarget.parentNode.
For example:
import { useState } from "react";
import { InputGroup, Button, FormControl } from "react-bootstrap";
import "./styles.css";
const InputField = ({ title }) => {
const [value, setValue] = useState(title);
const [toggleButtons, setToggleButtons] = useState(false);
const onChange = (e) => {
setValue(e.target.value);
};
const onFocus = () => {
setToggleButtons(true);
};
const onBlur = (e) => {
if (!e.currentTarget.parentNode.contains(e.relatedTarget)) {
setToggleButtons(false);
}
};
const acceptChange = () => {
console.log("Accept");
setToggleButtons(false);
};
const cancelChange = () => {
console.log("Cancel");
setToggleButtons(false);
};
return (
<div className="App">
<InputGroup className="m-3" style={{ width: "400px" }}>
<FormControl
value={value}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
/>
<InputGroup.Append className={toggleButtons ? "d-flex" : "d-none"}>
<Button
onBlur={onBlur}
variant="outline-secondary"
onClick={() => acceptChange()}
>
Accept
</Button>
<Button
onBlur={onBlur}
variant="outline-secondary"
onClick={() => cancelChange()}
>
Cancel
</Button>
</InputGroup.Append>
</InputGroup>
</div>
);
};
export default function App() {
return (
<>
<InputField title={"Input 1"} />
<InputField title={"Input 2"} />
<InputField title={"Input 3"} />
<InputField title={"Input 4"} />
</>
);
}
https://codesandbox.io/s/input-group-focus-slwoh
I have an input field that can be reset/cleared by a button click:
https://codesandbox.io/s/basictextfields-material-demo-forked-m1z5l?file=/demo.js:127-482
const searchInput = useRef(null);
const clearInput = () => {
searchInput.current.value = '';
searchInput.current.blur();
}
return (
<div>
<Input
inputRef={searchInput}
id="outlined-basic"
label="Outlined"
variant="outlined" />
<button onClick={clearInput}>Clear</button>
</div>
);
The issue is that the label does not reset to it's original position after clearing the input. I'm guessing because the input field still thinks it has the focus. I tried adding a blur() but that doesn't seem to do anything.
You can use inputProps for catch onBlur event.
<Input
id="outlined-basic"
label="Outlined"
variant="outlined"
inputProps={{
onBlur: () => {
console.log('foo bar')
}
}}/>
<button onClick={clearInput}>Clear</button>
This is in no way an elegant solution. But it's what I got to make it work as you wanted
import React, {useRef, useState, } from 'react';
import Input from '#mui/material/TextField';
export default function BasicTextFields() {
const searchInput = useRef(null);
const [isFocused, setFocus] = useState(false);
const clearInput = () => {
searchInput.current.value = '';
searchInput.current.blur();
setInputFocus(false)
}
const setInputFocus = (state)=>{
const notEmpty = !!searchInput.current?.value;
if(notEmpty) return setFocus(true)
setFocus(state)
}
return (
<div>
<Input
inputRef={searchInput}
id="outlined-basic"
label="Outlined"
InputLabelProps={{shrink: isFocused}}
onFocus={()=>setInputFocus(true)}
onBlur={()=>setInputFocus(false)}
variant="outlined" />
<button onClick={clearInput}>Clear</button>
</div>
);
}
If what you really want is clearing the TextField programmatically, then just use controlled mode and reset the value state when needed. See uncontrolled-vs-controlled:
export default function BasicTextFields() {
const [value, setValue] = useState("");
const clearInput = () => setValue("");
return (
<>
<Input
value={value}
onChange={(e) => setValue(e.target.value)}
label="Outlined"
variant="outlined"
/>
<button onClick={clearInput}>Clear</button>
</>
);
}
Live Demo
If you are using TextField, then you just have to use onBlur property:
<TextField
onBlur={your_method_handleOnBlur}
...other props
/>
Guys I was using the Material-UI text-field and made some custom theme options by by making it as a custom component .
Like Below :-
const CustomTextField = ({props}) =>{
return (
<TexField
label={props.label}
name={props.label}
value={props.row[props.label]}
onChange={(e)=>props.handleChangeFunction(e)}
/>
)
}
In My Component I inject like below :-
const App = () =>{
const [value,setValue] = useState('');
//Some methods here
const handleChange = () =>{
//Logic here.......
}
return(
<>
<CustomTextField {...{label:'name',row:emp,handleChangeFun:handleChange}}/> //Custom Comps...
</>
)
}
So What happens on each input my component renders and the cursor jumps out off the text field so for the next value to input I have to click the text field again.
But when I use the normal text field , directly in the component it works fine , I understand it is because of the onChange Event and using text field in a custom component .
On each value change the parent compoenent re-renders .
So what will be the solution can anyone give me a solution ?
I'm not sure about your way of passing props. This should work:
const CustomTextField = (props) => {
return (
<TextField
label={props.label}
name={props.label}
value={props.value}
onChange={props.onChange}
/>
);
};
export default function App() {
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<>
<CustomTextField label="name" value={value} onChange={handleChange} />
</>
);
}
Optionally you can create your component like this, without grouping props:
const CustomTextField = ({label, value, onChange}) => {
return <TextField label={label} name={label} value={value} onChange={onChange}/>
};
I am new to react and I am trying to build a website where the user can write a query in a searchbar and get some results back.
I created two components: Searchform (parent) and Searchbar (child).
I know how to update the state of the child:
function Searchbar() {
const [query, setQuery] = useState("");
return(
<TextField
defaultValue=""
placeholder="Search"
label="Search bar"
fullWidth
onChange={(event)=>setQuery(event.target.value)}
/>
)
}
However, I am not sure how to send this new state to the parent.
function Searchform() {
function handleSubmit() {
console.log(query)
}
const [query, setQuery] = useState("");
return(
<form onSubmit={handleSubmit}>
<div className="searchbar">
<Searchbar /> # I think I have to do something here but not sure what.
</div>
<Button variant="contained" color="primary" type="submit">
Submit your search
</Button>
</form>
)
}
You need to maintain the query in the parent and pass value and onChange to child:
function Searchbar({ value, onChange }) {
return (
<TextField
placeholder="Search"
label="Search bar"
fullWidth
value={value}
onChange={onChange}
/>
);
}
function Searchform() {
function handleSubmit() {
console.log(query);
}
const [query, setQuery] = useState('');
//create onChange function that will never
// change during component life cycle
const onChange = useCallback(
e => setQuery(e.target.value),
[]
);
return (
<form onSubmit={handleSubmit}>
<div className="searchbar">
{/* pass value and onChange */}
<Searchbar value={query} onChange={onChange} />
but not sure what.
</div>
<Button
variant="contained"
color="primary"
type="submit"
>
Submit your search
</Button>
</form>
);
}
Hope this helps,
function Searchform() {
function handleSubmit() {
console.log(query)
}
const [query, setQuery] = useState("");
return(
<form onSubmit={handleSubmit}>
<div className="searchbar">
<Searchbar setQuery={setQuery} />
</div>
<Button variant="contained" color="primary" type="submit">
Submit your search
</Button>
</form>
)
}
function Searchbar({setQuery}) {
// const [query, setQuery] = useState("");
return(
<TextField
defaultValue=""
placeholder="Search"
label="Search bar"
fullWidth
onChange={(event)=>setQuery(event.target.value)}
/>
)
}
State should be hoisted to the top-most container that deals with the state, and then passed down through props (or providers for contexts).
To update the state, you must pass down a callback function (through props) from the parent to child components that allow the child to update the parent's state. The change will then propagate to all children during a re-render.
This is a universal React concept, whether using hooks or classes, see the React documentation
A basic example:
const Parent = () => {
const [query, setQuery] = useState(null);
return <Child onQuery={setQuery} />;
}
const Child = ({ onQuery }) => (
<TextField onChange={event => onQuery(event.target.value)} />
);