How to keep input focus when changing state inside onFocus event
seems like it's not happening with simple input
only when using customInput
const CustomInput = ({ onFocus, state }) => {
return (
<div>
<input onFocus={onFocus} />
</div>
);
};
const TestInput = () => {
const [state, setState] = React.useState(false);
return (
<FilledInput
inputComponent={(props) => {
return (
<CustomInput onFocus={props.onFocus} state={state} />
);
}}
onFocus={() => {
setState(true);
}}
/>
);
};
Related
I have a webpage with multiple forms. Here's a bare minimum example of the structure:
export default function Example() {
const [previousFormFetchedFromWeb, setPreviousFormFetchedFromWeb] = useState(
{}
);
const [formA, setFormA] = useState({});
const [formB, setFormB] = useState({});
const router = useRouter();
useEffect(() => {
fetchFormFromWeb(router.query.id).then((previousForm) => {
console.log("fetched info from web");
setPreviousFormFetchedFromWeb(previousForm);
setFormA(previousForm.formA);
setFormB(previousForm.formB);
});
},[router.isReady]);
return (
<>
<FormA form={formA} />
<FormB form={formB} />
</>
);
}
function FormA({ form }) {
return (
<input
type="text"
name="field1"
id="field1"
value={formA.field1}
onChange={(e) => {
setFormA(e.target.value);
}}
/>
);
}
function FormB({ form }) {
return (
<input
type="text"
name="field2"
id="field2"
value={formB.field2}
onChange={(e) => {
setFormB(e.target.value);
}}
/>
);
}
I think this should be the encapsulated logic of my form page. The problem is that when the onChange event is called for a field of any form, all forms get re-rendered. I assumed that setState should re-render only the components with the affected dependency change. Am I missing something?
Any state change in Example component will trigger re render to its child components (FormA, FormB). If you want to avoid that. wrap FormA and FormB in React.memo. That way you can prevent unwanted re-renders
export default function Example() {
const [previousFormFetchedFromWeb, setPreviousFormFetchedFromWeb] = useState(
{}
);
const [formA, setFormA] = useState({});
const [formB, setFormB] = useState({});
const router = useRouter();
useEffect(() => {
fetchFormFromWeb(router.query.id).then((previousForm) => {
console.log("fetched info from web");
setPreviousFormFetchedFromWeb(previousForm);
setFormA(previousForm.formA);
setFormB(previousForm.formB);
});
},[router.isReady]);
return (
<>
<MemFormA form={formA} />
<MemFormB form={formB} />
</>
);
}
const MemFormA = React.memo(function FormA({ form }) {
return (
<input
type="text"
name="field1"
id="field1"
value={formA.field1}
onChange={(e) => {
setFormA(e.target.value);
}}
/>
);
})
const MemFormB = React.memo(function FormB({ form }) {
return (
<input
type="text"
name="field2"
id="field2"
value={formB.field2}
onChange={(e) => {
setFormB(e.target.value);
}}
/>
);
})
Anytime state of <Example /> is updated, <Example /> re-renders, which in turn also re-renders <FormA /> and <FormB />. This is expected.
You should look into using React.memo() for FormA and FormB if you want them to only re-render when the props passed to them is changed.
I'm trying to build an input component with a clear button using react#17
import { useRef } from 'react';
const InputWithClear = props => {
const inputRef = useRef();
return (
<div>
<input
ref={inputRef}
{...props}
/>
<button
onClick={() => {
inputRef.current.value = '';
inputRef.current.dispatchEvent(
new Event('change', { bubbles: true })
);
}}
>
clear
</button>
</div>
);
};
using this component like:
<InputWithClear value={value} onChange={(e) => {
console.log(e); // I want to get a synthetic event object here
}} />
but the clear button works once only when I did input anything first, and stop working again.
if I input something first and then click the clear button, it does not work.
why not using?
<button
onClick={() => {
props.onChange({
target: { value: '' }
})
}}
>
clear
</button>
because the synthetic event object will be lost
So, how do I manually trigger a synthetic change event of a react input component?
Try this approach,
Maintain state at the parent component level (Here parent component is App), onClear, bubble up the handler in the parent level, and update the state.
import React, { useState } from "react";
import "./styles.css";
const InputWithClear = (props) => {
return (
<div>
<input {...props} />
<button onClick={props.onClear}>clear</button>
</div>
);
};
export default function App() {
const [value, setValue] = useState("");
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<InputWithClear
value={value}
onChange={(e) => {
console.log(e); // I want to get a synthetic event object here
setValue(e.target.value);
}}
onClear={() => {
setValue("");
}}
/>
</div>
);
}
Working code - https://codesandbox.io/s/youthful-euler-gx4v5?file=/src/App.js
you should use state to control input value rather than create useRef, that's the way to go. you can use a stopPropagation prop to control it:
const InputWithClear = ({value, setValue, stopPropagation = false}) => {
const onClick = (e) => {
if(stopPropagation) e.stopPropagation()
setValue('')
}
return (
<div>
<input
value={value}
onChange={e => setValue(e.target.value)}
/>
<button
onClick={onClick}
>
clear
</button>
</div>
);
};
export default function App() {
const [value, setValue] = useState('')
return (
<div className="App">
<InputWithClear value={value} setValue={setValue} stopPropagation />
</div>
);
}
I've a isView and setIsView in the ParentComponent and passing them down to the ChildComponent as props and trying to do show/hide conditional rendering but setIsView seems not to be working and isView value in the props remains same.
const ParentComponent = props => {
const [isView, setIsView] = useState(true);
const onChange = selectedOption => {
selectedOption === 'Report'
? setIsView(true)
: setIsView(false);
};
return (
<div>
<ChildComponent
isView={isView}
onChange={onChange}
/>
</div>
);
};
const ChildComponent = props => {
const {isView, onChange} = props;
return (
<div>
<RadioButton
onChange={() => onChange('Not-Report')}
/>
<If condition={isView}>
<ChildComponent2>
</If>
</div>
);
};
Edit: changed onChange={onChange('Not-Report')} to onChange={() => onChange('Not-Report')} as suggested by some. still not working.
Try feeding the onChange method as a callback function instead.
const ChildComponent = props => {
const {isView, onChange} = props;
return (
<div>
<RadioButton
onChange={() => onChange('Not-Report')} // <- Here
/>
<If condition={isView}>
<ChildComponent2>
</If>
</div>
);
};
Update child component onChange function as follows:
<RadioButton
onChange={() => onChange('Not-Report')}
/>
If you pass onChange only, it will be regarded with the function that has event as a parameter rather than the prop's onChange function.
To make it work like your way,
const ChildComponent = ({isView, onChange}) => {
const onRadioChange = () => {
onChange('Not-Report')}
}
return (
<div>
<RadioButton
onChange={onRadioChange}
/>
<If condition={isView}>
<ChildComponent2>
</If>
</div>
);
};
i am wrote this code
ParentComponent
const ParentComponent = (props) => {
const [isView, setIsView] = useState(true);
const onChange = (selectedOption) => {
console.log("selectedOption = ", selectedOption);
selectedOption === "Report" ? setIsView(true) : setIsView(false);
};
return (
<div>
<ChildComponent isView={isView} onChange={onChange} />
</div>
);
};
ChildComponent
const ChildComponent = (props) => {
const { isView, onChange } = props;
return (
<div>
<input
type="radio"
checked={isView}
onClick={() => {
onChange("Not-Report");
}}
/>
isView = {isView ? "true" : "false"}
</div>
);
};
i change onChange to onClick and use checked
Work Demo
I need to use ref to get a grandchild component's state, and I customized it by using useImperativeHandle, the whole code is simplified
here.
function App() {
const ref = useRef(null);
return (
<div className="App">
<button
onClick={() => console.log(ref.current.name, ref.current.age)}
>click me</button>
<FilterFather ref={ref} />
</div>
);
}
const FilterFather = (_, ref) => {
const filter1Ref = useRef(null);
const filter2Ref = useRef(null);
useImperativeHandle(ref, () => ({
name: filter1Ref.current.name,
age: filter2Ref.current.age,
}))
return (
<>
<Filter1 ref={filter1Ref}/>
<Filter2 ref={filter2Ref} />
</>
)
}
export default forwardRef(FilterFather);
const Filter1 = (props, ref) => {
const [name, setName] = useState('lewis')
useImperativeHandle(ref, () => ({
name
}), [name])
return (
<>
<div>
name:
<input
value={name}
onChange={e => setName(e.target.value)}
/>
</div>
</>
)
}
const Filter2 = (props, ref) => {
const [age, setAge] = useState(18)
useImperativeHandle(ref, () => ({
age
}), [age])
return (
<>
<div>
age:
<input
value={age}
onChange={e => setAge(e.target.value)}
/>
</div>
</>
)
}
export default {
Filter1: forwardRef(Filter1),
Filter2: forwardRef(Filter2),
}
one layer of forwardRef and useImperativeHandle works fine, two layers went wrong
Your imperative handle in FilterFather is not required. It doesn't add/remove anything to the handle. You can just forward it directly:
const FilterFather = (_, ref) => {
return <Filter ref={ref} />;
};
Also there is a problem with it because it will not update correctly.
useImperativeHandle(ref, () => ({
name: filterRef.current.name,
age: filterRef.current.age,
}), [filterRef]) // this will not update correctly
You passed filterRef as a dependency but filterRef is static and will not change even when filterRef.current.name or filterRef.current.age changes.
Note that useImperativeHandle is not supposed to be used to read state from child components. It is actually discouraged to use any kind of imperative methods in react except there is no other way. This is most of the time the case if you work with 3rd party libraries that are imperative in nature.
EDIT:
Given your updated code the react way to do that is to lift state up. It doesn't require any refs:
export default function App() {
const [values, setValues] = useState({
name: "lewis",
age: 18
});
const handleChange = useCallback(
(key, value) => setValues(current => ({ ...current, [key]: value })),
[]
);
return (
<div className="App">
<button onClick={() => console.log(values.name, values.age)}>
click me
</button>
<FilterFather values={values} onChange={handleChange} />
</div>
);
}
const FilterFather = props => {
return (
<>
<Filter label="Name" name="name" {...props} />
<Filter label="Age" name="age" {...props} />
</>
);
};
const Filter = ({ label, name, values, onChange }) => {
const handleChange = useCallback(e => onChange(name, e.target.value), [
name,
onChange
]);
return (
<>
<div>
<label>{label}:</label>
<input value={values[name]} onChange={handleChange} />
</div>
</>
);
};
Editing my question to make it a bit clearer
I don’t want the button to re-rendering when I type in the field and when I click on the button I want to update a state object
Here I have 2 components
const mainState = {
title: '',
};
const ButtonComponent = ({ confirmTitleName }) => {
return (
<>
<TestReRender label={'Button Container'}/>
<button style={{backgroundColor: 'red', outline: 'none'}} onClick={() => confirmTitleName('confirmTitleName >>>')}>CLICK ME</button>
</>
)
};
const InputComponent = ({ state, setState }) => {
return (
<>
<TestReRender label={'Input Container'}/>
<input
type="text"
value={state}
onChange={(e) => setState(e.target.value)}
/>
</>
)
};
Then I have created a component made up of the previous two
const InputAndButtonComponent = memo(({ confirmTitleName }) => {
const [state, setState] = useState('');
const Btn = () => <ButtonComponent confirmTitleName={() => confirmTitleName(state)}/>;
return (
<>
<InputComponent state={state} setState={setState} />
<Btn />
</>
)
});
The last component InputAndButtonComponent is then imported in the Main component
const Main = () => {
const [confirmTitle, setConfirmTitle] = useState(mainState);
const confirmTitleName = useCallback((value) => {
setConfirmTitle((prevState) => (
{
...prevState,
title: value
}
))
}, []);
return (
<main className={styles.CreateWorkoutContainer}>
<>
<TestReRender label={'Main Container'}/>
<div>
<InputAndButtonComponent confirmTitleName={confirmTitleName} />
</div>
</>
</main>
)
};
Now the problem is that when I write the component InputAndButtonComponent as follow it re-renders when I type in the input field
const InputAndButtonComponent = memo(({ confirmTitleName }) => {
const [state, setState] = useState('');
return (
<>
<InputComponent state={state} setState={setState} />
// This re-renders when typing
<ButtonComponent confirmTitleName={() => confirmTitleName(state)}/>;
</>
)
});
But the original version does not re-render when I type in the field
const InputAndButtonComponent = memo(({ confirmTitleName }) => {
const [state, setState] = useState('');
// This makes the <Btn /> below not re-rendering. I don't understand why
const Btn = () => <ButtonComponent confirmTitleName={() => confirmTitleName(state)}/>;
return (
<>
<InputComponent state={state} setState={setState} />
// This does not re-render
<Btn />
</>
)
});