Forgive me I've been through a lot of the existing questions around on this topic without success.
I have a filter dialog with several input fields (child components) and a submit button. I want the submit button to be disabled if a validation message exists on one of the child components.
Child (simplified):
const TextSearch: FC<TextSearchProps> = memo((props) => {
const [validationMessage, setValidationMessage] = useState('')
const onTextChange = (e: React.FormEvent<HTMLInputElement>) => {
const input = e.currentTarget.value
if(input && !checkNumericOnly(input)) {
setValidationMessage('Only numbers allowed')
} else {
setValidationMessage('')
}
return (
<div className={classes.textSearch}>
<InputText
className={validationMessage ? 'p-invalid' : ''}
onChange={onTextChange}
/>
<div className={classes.invalid}>{validationMessage}</div>
</div>
)
Parent:
const AdvancedSearch: FC<SearchProps> = memo(_props => {
const dispatch: AppDispatch = useDispatch()
const data = useSelector(selectSearchData())
const [validInput, setValidInput] = useState(true) // this is what I'm trying to set
How do I setValidInput based on whether the child validationMessage is true?
Have tried a function in the parent dialog:
function handleValidInput(valid: boolean) {
setValidInput(valid)
}
But have not been able to figure out how to call it from the child.
You should pass your setState function to your children and then update the state there:
In parent (AdvancedSearch):
const [validInput, setValidInput] = useState(true);
return (
<TextSearch setValidInput={setValidInput} />
)
In child (TextSearch):
const onTextChange = (e: React.FormEvent<HTMLInputElement>) => {
const input = e.currentTarget.value
if(input && !checkNumericOnly(input)) {
setValidationMessage('Only numbers allowed');
props.setValidInput(false);
} else {
setValidationMessage('');
props.setValidInput(true);
}
}
return (
<div className={classes.textSearch}>
<InputText
className={validationMessage ? 'p-invalid' : ''}
onChange={onTextChange}
/>
<div className={classes.invalid}>{validationMessage}</div>
</div>
)
Related
I have a component in reactjs where i define 2 states input and resultlist(an array). My functionality is like, I will enter some input click on search, call an api, set search results in result list. Now my problem is my search result is very huge. Once resultlist is populated first time, then whenever I am typing input it is rerendering whole component on each keypress including resultlist which is lagging a lot. How to handle this issue.
I tried using usememo on resultlist but it is not working. I don't want my Child to rerender on every keyspress in input.
const comp1 = () => {
const [input, setinput] = useState('');
const [resultlist, setresultlist] = useState([]);
const fetchResult = () =>{
fetch('some url') // returns a very long list of items(10k +)
.then(res=>setresultlist(res));
}
return <>
<input value={input} onChange={(e)=>setinput(e.target.value)}></input>
<button oncClick={fetchResult}></button>
<Child resultlist={resultlist} />
</>
}
const Child = ({resultlist}) => {
return <>
{resultlist.map(element=><div>element</div>)}
</>
}
I found a solution.
const Parent = () => {
const [resultlist, setresultlist] = useState([]);
return <>
<comp1 setresultlist={setresultlist} />
<Child resultlist={memoizedResultList} />
</>
}
const comp1 = ({setresultlist}) => {
const [input, setinput] = useState('');
const fetchResult = () =>{
fetch('some url') // returns a very long list of items(10k +)
.then(res=>setresultlist(res));
}
return <>
<input value={input} onChange={(e)=>setinput(e.target.value)}></input>
<button oncClick={fetchResult}></button>
</>
}
const Child = ({resultlist}) => {
return <>
{resultlist.map(element=><div>element</div>)}
</>
}
I have React App which detects hovered elements to perform the action of getting images. I would like to use ref to assure catching hover only from a specific areas of the page.
Working simplified version is like this:
const Form = props => {
const { fetchHints, hints, clearHints, fetchImages } = props;
const refSelect = React.useRef();
function getImages(e) {
if (e.key === "Enter") {
const withHover = getHovered(refSelect.current);
if (withHover) {
const className = withHover.className;
if (className.includes("option")) {
fetchImages(withHover.textContent);
history.push("./images");
}
}
}
}
return (
<div onKeyDown={debouncedGetImages}>
<div ref={refSelect}>
<SelectSection hints={hints} getValues={getValues} changeHandler={fetchImages} />
</div>
</div>
);
};
However, it is obvious that <div ref={refSelect}> only role is to be a point where the ref is anchored and it is useless besides. So, I tried to anchor ref just on SelectionSection:
<SelectSection hints={hints} getValues={getValues} changeHandler={fetchImages} ref={refSelect} />
while SelectionSection is as follows (any type is for initial simplicity):
const SelectSection = React.forwardRef((props: Props & RouteComponentProps, ref: any) => {
const { hints, getValues, changeHandler } = props;
const history = useHistory();
const path = props.match.path;
return hints?.length && path === "/" ? (
<Select
ref={ref}
className="select-top"
id="BigSelect"
value={getValues}
isClearable={true}
menuIsOpen={true}
onChange={selectValue => {
changeHandler(selectValue.value);
history.push("./images");
}}
options={hints}
/>
) : null;
});
export default withRouter(SelectSection);
The problem is that in this case refSelect is always undefined. What is wrong with this code?
(in REACT)
i have app function :
function App() {
const [welcomeMenu, setWelcomeMenu] = useState(true);
const [gameMenu, setGameMenu] = useState(false);
const [username, setUsername] = useState('');
const welcomeMenuShow = () => {
setWelcomeMenu(false);
}
const getUserName = (value) => {
setUsername(value);
console.log(username);
};
return (
<div className="App">
{
welcomeMenu ? <WelcomeMenu gameStarter={welcomeMenuShow} getUserName={getUserName}/> : null
}
</div>
);
}
in welcomemenu component i pass getUserName function to get username which user input
next in Welcome menu i have :
const WelcomeMenu = ({ gameStarter, getUserName }) => {
return (
<div className="welcome-menu">
<WelcomeText />
<WelcomeBoard gameStarter={gameStarter} getUserName={getUserName}/>
</div>
)
};
i pass get User Name in second time
in WelcomeBoard i have:
const WelcomeBoard = ({ gameStarter, getUserName }) => {
const [text, setText] = useState('');
const [warning, setWarning] = useState(false);
const checkBtn = (event) => {
if(text) {
gameStarter();
} else {
setWarning(true);
setTimeout(() => {
setWarning(false);
}, 3000);
}
};
const handleChange = (event) => {
setText(event.target.value);
};
return (
<div className="welcome-board">
<div className="username">Please enter the name</div>
<input type="text" value={text} onChange={handleChange} className="username-input" />
<button className="username-btn" onClick={() => {
getUserName(text);
checkBtn();
}}>start</button>
{warning ? <Warning /> : null}
</div>
)
};
in input onchange i make state and pass the input value on text state
next on button i have on click which active 2 function:
getUserName(text) // text is a state text with input value
checkBtn()
and after a click button in app i activate getUserName(text), this function pass the text in username state and here is a problem
when i try to see this text console.log(username) - it's give me null
but it if i try to see value console.log(value) - i see my input text
i don't understand how to fix that
react setState is async, which means those state variables are updated in the NEXT RENDER CYCLE(think of it as a thread or buffer).
try running this code if you want to understand what is happening BEHIND THE SCENES.
let renderCount = 0;
function TestApp() {
renderCount++;
const [state, setState] = useState(0);
const someRef = useRef(0);
someRef.current = state;
const someCallback = () => {
const someValue = new Date().getTime();
setState(someValue);
console.log(someRef.current, renderCount);
setTimeout(() => {
console.log(someRef.current, renderCount);
},100)
}
return <button onClick={someCallback}>clickme<button>;
}
I am just trying to figure out how to do my to-do list and currently am just experimenting with adding elements containing text given a text input element.
The issue is presented in this clip: https://imgur.com/a/DDTyv1I
import { useState } from "react"
function App() {
const [inputVal, setInputVal] = useState('')
const [tasks, setTasks] = useState([])
console.log(inputVal);
return <>
<Input valor = {setInputVal} inputVal={inputVal} tasks={tasks} setTasks={setTasks}/>
{
tasks.map(e=>(
<Display text={e.text}/>
))
}
</>
}
const Input = ({valor, inputVal, tasks, setTasks}) =>{
const keyPressed = (val) =>{
if(val.key === 'Enter'){
valor(val.target.value)
setTasks([
...tasks, {text: inputVal, key: Math.random()*2000}
])
}
}
return <>
<input type="text" onKeyUp={keyPressed}/>
</>
}
const Display = ({text}) => {
return <>
<h1>{text}</h1>
</>
}
export default App;
I believe this is happening because you are not using onChange on your input so your state is going stale and you are always one value behind.
I have tidied up the code and added some missing pieces (like the value attribute in the input element).Then I split the function that takes care of the submission to 2 functions - one function that is handling changing the input value and one that submits the value as a new entry to your tasks list
import { useState } from "react"
const Input = ({ input, handleChange, tasks, setTasks }) => {
const onSubmit = (e) => {
if (e.key === 'Enter') {
setTasks([
...tasks,
{ text: input, key: Math.random()*2000 }
])
setInput('');
}
}
const handleChange = (e) => {
setInput(e.target.value)
}
return <input type="text" onKeyUp={onSubmit} onChange={handleChange} value={input}/>
}
const Display = ({ text }) => <h1>{text}</h1>
const App = () => {
const [input, setInput] = useState('')
const [tasks, setTasks] = useState([])
return <>
<Input input={input} setInput={setInput} tasks={tasks} setTasks={setTasks}/>
{tasks.map((task) => (
<Display text={task.text}/>
))
}
</>
}
When keyPressed is called, your code calls setInputVal (valor) and setTasks. The setTask is being called before setInputVal actually has time to update the state, so it sets the ”old” value. This is because state setting is asynchronous and the code does not wait for the inputVal to be set before setting the task.
I have a custom input component that has an edit mode, I use multiple inputs and my goal is to extract all the state from the input and put it on the parent.
I managed to do it for the input value since I create a useState for every input, but how can I achieve the same thing for the toggle behavior. I want to have a button next to each input that toggles the state of the input.(Show/Hide the input).
Right now when I press edit all the edits Booleans get triggered so all the inputs become visible.
How can I toggle one input at a time from the parent, Basically I want my custom components to be purely for presentation and the state should live on the parent
Example:
const Parent = () => {
const [value1, setValue1] = useState('')
const [value2, setValue2] = useState('')
const [value3, setValue3] = useState('')
const [editMode, setEditMode] = useState(false)
return (
<div>
<ChildCustomInput value={value1} updateValue={setValue1} editMode={editMode} updateEditMode={setEditMode} />
<ChildCustomInput value={value2} updateValue={setValue2} editMode={editMode} updateEditMode={setEditMode}/>
<ChildCustomInput value={value3} updateValue={setValue3} editMode={editMode} updateEditMode={setEditMode}/>
</div>
)
}
const ChildCustomInput = (props)=> {
// ref to update the input's value in the parent
const textInputEl = useRef(null)
// method to update the parent's values
const updateComponentValue = () => {
props.updateValue(textInputEl.current.value)
props.updateEditMode(!props.editMode)
}
// Toggle Edit mode
const changeEditMode = () => {
props.updateEditMode(!props.editMode)
}
const renderEditView = () => {
return (
<div>
<input ref={textInputEl}></input>
<button onClick={updateComponentValue}>
OK
</button>
</div>
)
}
const renderDefaultView = () => {
return (
<div>
<div>
{props.value}
</div>
<button onClick={changeEditMode}>
EDIT
</button>
</div>
)
}
return (
<div>
{
props.editMode
? renderEditView()
: renderDefaultView()
}
</div>
)
}
export default Parent;
It seems that you need a editMode boolean in the parent state for each of your input (ie editMode1, editMode2, editMode3), and each ChildCutomInput should use its own editModeX and setEditModeX instead of sharing the same boolean.
const Parent = () => {
const [value1, setValue1] = useState('');
const [value2, setValue2] = useState('');
const [value3, setValue3] = useState('');
const [editMode1, setEditMode1] = useState(false);
const [editMode2, setEditMode2] = useState(false);
const [editMode3, setEditMode3] = useState(false);
return (
<div>
<ChildCustomInput value={value1} updateValue={setValue1} editMode={editMode1} updateEditMode={setEditMode1} />
<ChildCustomInput value={value2} updateValue={setValue2} editMode={editMode2} updateEditMode={setEditMode2}/>
<ChildCustomInput value={value3} updateValue={setValue3} editMode={editMode3} updateEditMode={setEditMode3}/>
</div>
)
}
For this case you should have a separated state that manages the toggle property for each input. Like:
const [value1EditMode, setValue1EditMode] = useState(false);
const [value2EditMode, setValue2EditMode] = useState(false);
And so on for each input.
But I suggest you to use the useReducer hook, so you just manage the whole child components state with just one reducer. You could do something like this:
const initialState = {
input1: {
value: "",
editMode: false
},
input2: {
value: "",
editMode: false
},
input3: {
value: "",
editMode: false
}
};
function reducer(state, action) {
switch (action.type) {
case "CHANGE_VALUE":
let newState = {
...state
};
newState[action.key].value = action.value;
return newState;
case "SET_EDIT_MODE":
newState = {
...state
};
newState[action.key].editMode = action.editMode;
return newState;
default:
throw new Error();
}
}
const Parent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
function updateValue(key, value) {
dispatch({ type: "CHANGE_VALUE", value, key });
}
function updateEditMode(key, editMode) {
dispatch({ type: "SET_EDIT_MODE", editMode, key });
}
return (
<div>
<ChildCustomInput
value={state.input1.value}
updateValue={value => {
updateValue("input1", value);
}}
editMode={state.input1.editMode}
updateEditMode={editMode => {
updateEditMode("input1", editMode);
}}
/>
<ChildCustomInput
value={state.input2.value}
updateValue={value => {
updateValue("input2", value);
}}
editMode={state.input2.editMode}
updateEditMode={editMode => {
updateEditMode("input2", editMode);
}}
/>
<ChildCustomInput
value={state.input3.value}
updateValue={value => {
updateValue("input3", value);
}}
editMode={state.input3.editMode}
updateEditMode={editMode => {
updateEditMode("input3", editMode);
}}
/>
</div>
);
};
Here is a working example: https://codesandbox.io/s/eloquent-hellman-8on6s?file=/src/Parent.js:52-1818