This is a tricky question. Lets say I have a Component <GrandFather /> with a <Form /> Component inside. The <Form /> has Multiple <Input /> Components. The <Input /> has an internal useState. At the <GrandFather />, there are some functions like loadForm(loading some fields) and unloadForm(removes these fields). On the unloadForm I wanna reset the internal State of the Inputs. The only solution I found was to have a key on the <Form /> and increment it on unload, so thats forces the rest. Is there a better good way to do this without change the logic? P.S I 'm using Typescript.
function GrandFather (props: Props) {
const loadForm = () => // load some fields to the formData
const unloadForm = () => // unload these fields to the formData
return <Form formData={formData}/>
}
function Form (formData: FormData) {
return (
<>
<Input /> // with some props
<Input /> // with some props
<Input /> // with some props
</>
)
}
function Input (props: Props) {
const [state, setState] = useState(false);
// the state here is being used for styling and animations, at
// somepoint it will became true
return <TextInput {...props}/>
}
any way here to reset this state to all the inputs on the function unloadForm?
I see two different methods of achieving this.
Method #1
As an example, I'll use a simple login form.
So you could define the following state variables on GrandParent:
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
Then, pass all username, setUsername, password, and setPassword as props to Form and then on to the Input components.
Inside of the Input component, you could turn the inputs into controlled inputs by adding the following:
<input type="text" value={username} onChange={setUsername} />
If you ever need to clear the inputs, you could call right directly from GrandParent or Form (or anywhere where you have access to the setters) the following:
setUsername('');
setPassword('');
Method #2 (hacky)
You could define, on GrandParent, a state variable that, as the above method, you would pass both the variable and setter as props to Form and then to each Input:
const [clear, setClear] = useState(false);
Then, inside the Input component, assuming you have a value state variable on it (with the equivalent setter setValue), you could set up a listener for a change on the state variable clear:
useEffect(() => {
if (clear === true) {
setValue('');
}
}, [clear]);
Then, whenever you wanted to clear all the input values, you could call, from anywhere:
setClear(true);
setTimeout(() => { // the setTimeout might not be required
setClear(false);
}, 1);
Related
Everytime i want to update some value in my state via a text input i just simply create an onChange function that accepts a event: React.ChangeEvent<HTMLInputElement | HtmlTextAreaElement> as an argument. Something like this:
const [state, setState] = useState<IState>({ email: "", password: "" });
const onChange = (event: React.ChangeEvent<HTMLInputElement | HtmlTextAreaElement>): void => {
const { name, value } = event.target;
setState({
...state,
[name]: string
});
}
And well it perfectly works but... i have to create this function every single time that i want to change my state via an input in other page (an onChange function in the login page, an onChange function in sign up page, forgotpassword page... etc). My question is: Is there a way to create a generic onChange that accepts any state and update it?
this is my first post on stack overflow, sorry if i dont follow the guidelines at all.
If the function is short and only used once, then one option you could consider would be to define it inline.
<input
name="name"
value={state.name}
onChange={e => { setState({ ...state, name: e.target.value }); }}
/>
You can also consider separating out states, as React recommends - there's no need to put everything into a single state variable (unless there are a lot of inputs in a form, in which case a single state object with a generic updater can make things more DRY).
const [name, setName] = useState(''); // No need for type annotations anymore
const [password, setPassword] = useState('');
// ...
<input
name="name"
value={name}
onChange={e => { setName(e.target.value); }}
/>
If you want a generic state updater that you don't have to redefine every time to have another state variable, you can create a higher order function that you pass in a state setter, that returns the change handler - then import this function wherever it's needed.
const makeChangeHandler = (stateSetter: React.Dispatch<React.SetStateAction<string>>) => {
return (event: React.ChangeEvent<HTMLInputElement | HtmlTextAreaElement>) => {
const { name, value } = event.target;
stateSetter({
...state,
[name]: value
});
};
};
<input
name="name"
value={name}
onChange={makeChangeHandler(setState)}
/>
Refactoring that function to deal with separate states instead of having a single state object is trivial, if desired, just do
stateSetter(value);
I have quite a few different interfaces with many properties like
interface IEntity1 {
a: string
b: number
c: string
....
}
When I want to edit an object (i.e., create a modified copy), I can do
function Editor(props: {entity: IEntity1, setEntity(e: IEntity1) => void}) {
const {entity, setEntity} = props;
const [a, setA] = useState(entity.a);
const [b, setB] = useState(entity.a);
....
function handleOk() {
setEntity({a, b, c, ...});
}
return <div>
<input value={a} onChange={e => setA(e.target.value}/>
<input value={''+b} onChange={e => setB(+e.target.value}/>
<input value={c} onChange={e => setC(e.target.value}/>
...
<button onClick={handleOk}>OK</button>
</div>;
}
This works fine, but it's pretty repetitive. So I created a "universal" component, which can handle number, string and other inputs. Using it, I can handle all fields in a loop like
{fieldNames.map(f => <Edit value={entity[f]}
onChange={newValue => setEntity({...entity}, {[f]: newValue})}/>)}
I can generate all the needed metadata (above for simplicity just fieldNames: string[]) easily. I can do even better with
{fieldsMetadata.map(f => <Edit2 entity={entity} field={f}/>)}
where Edit2 is a "smarter" component. What bothers me is that all my edits get re-rendered on any change. This may be a premature optimization, but given that I want to use this component many time on many pages, it may be a valid concern.
So I'm asking whether it's possible to write such a component without it re-rendering every time or whether there's something performance-relevant what I'm missing?
The problem in with your component re-rendering is because on passing the inline arrow function which is created again on every render
Once you avoid it you can implement each Edit component with React.memo or PureComponent depending on whether you implement it as a functional component or class
const onChange= useCallback((name, value) => {
setEntity(prev => ({...prev, [name]: value}));
}, []);
...
{fieldNames.map(f => <Edit name={f} value={entity[f]}
onChange={onChange}/>)}
and Edit component would be like
const Edit = (props) => {
const {value, name, setEntity} = props;
const handleChange = (e) => {
const newValue = e.target.value;
onChange(name, newValue);
}
return <input value={value} name={name} onChange={handleChange} />
}
export default React.memo(Edit)
P.S Make sure you are merging the state correctly with setEntity
There is one thing that I don't understand how to do correctly in pure React (without Redux and Co for a lot of reasons that I don't want to explain here), and we can simplify the problem by studying a dummy checkbox that we scale up into a whole configuration menu.
I see 3 ways of achieving this and all seems to have downs when you want to scale up. Let's see.
Stateless checkbox
const Checkbox = ({
onPress = () => {},
checked = false
}) => {
return <input type="checkbox" checked={checked} />
}
Now, if I have a full configuration menu with many checkbox, this means that a parent should have a state containing all the state of all checkboxes, and provide everything as props to the menu. See:
const Parent = () => {
const [isBox1Checked, checkBox1] = useState(false)
const [isBox2Checked, checkBox2] = useState(false)
const [isBox3Checked, checkBox3] = useState(false)
const [isBox4Checked, checkBox4] = useState(false)
const [isBox5Checked, checkBox5] = useState(false)
return <Menu isBox1Checked={isBox1Checked} checkBox1={checkBox1} ... />
}
I could use an array, but keep in mind that what is simplified here as checkboxes would be all kind of parameters (numbers, booleans, strings, etc)
The menu would be like:
const Menu = ({isBox1Checked, checkBox1, ... }) => {
return <div>
<Checkbox onPress={checkBox1} checked={isBox1Checked} />
<Checkbox onPress={checkBox2} checked={isBox2Checked} />
<Checkbox onPress={checkBox3} checked={isBox3Checked} />
<Checkbox onPress={checkBox4} checked={isBox4Checked} />
<Checkbox onPress={checkBox5} checked={isBox5Checked} />
</div>
}
Pros:
Easy to read the current configuration where it is needed
Cons:
A lot of stupid code to write just to pass down all the props
Maintenance and refacto will probably be a nightmare...
Stateful checkbox
const Checkbox = ({
onChange = () => {},
initialValue = false
}) => {
const [checked, setChecked] = useState(initialValue)
const toggle = () => {
setChecked(!checked)
onChange(!checked)
}
return <input type="checkbox" checked={checked} onChange={toggle}/>
}
Here, we store the state of the checkbox within the checkbox, which becomes the owner of the truth. Relatives can learn about state changes with the onChange method.
Unfortunately, the Parent and the Menu component will look exactly like the previous ones...
Pros:
More Component oriented checkbox, which is better for separation of concerns.
Cons:
A lot of stupid code to write just to pass down all the props
Maintenance and refacto will probably be a nightmare...
Duplication of the data as there is a state for the checkbox within the Checkbox, and a state for the functionality within the Parent...
It could get confusing to provide a initialValue as a props that would be ignored when updated, as the reality resides within the state
Reading the state with refs
const Checkbox = forwardRef(({
onChange = () => {},
initialValue = false
}, ref) => {
const [checked, setChecked] = useState(initialValue)
//We wait for the DOM to be rendered for ref.current to worth something.
useLayoutEffect(() => ref.current.isChecked = () => checked, [ref.current])
const toggle = () => {
setChecked(!checked)
onChange(!checked)
}
return <input ref={ref} type="checkbox" checked={checked} onChange={toggle}/>
})
This time, the checkbox becomes the real owner of the truth, and to read the data, we read it directly within the checkbox. The parent would become something like that:
const Parent = () => {
const menu = createRef()
const onConfigChanged = ({ name, value }) => //Do sth if sth changed
const isBox1Checked = menu.current ? menu.current.isChecked('box1') : false
//Do something with valueOfParam1
return <Menu ref={menu} onChange={onConfigChanged} />
}
const Menu = forwardRef({ onConfigChanged }, ref) => {
// We create the refs for all the configs that this menu will be in charge for
const configs = useRef({
box1: createRef(),
box2: createRef(),
box3: createRef(),
box4: createRef(),
box5: createRef(),
})
// We provide a method to read the state of each checkbox by accessing their ref.
if(ref.current) ref.current.isChecked = name => configs[name].current.isChecked
const onChanged = name => value => onConfigChanged({name, value})
return <div ref={ref}>
<Checkbox ref={configs.current.box1} onChange={onConfigChanged('box1')} />
<Checkbox ref={configs.current.box2} onChange={onConfigChanged('box2')} />
<Checkbox ref={configs.current.box3} onChange={onConfigChanged('box3')} />
<Checkbox ref={configs.current.box4} onChange={onConfigChanged('box4')} />
<Checkbox ref={configs.current.box5} onChange={onConfigChanged('box5')} />
</div>
})
Pros:
100% Component oriented, with separation of concerns
Parent code is simplified, that helps highlight their own responsibility instead of flooding it with state distribution
Cons:
Supposedly anti-pattern of using refs to access children methods
It could get confusing to provide a initialValue as a props that would be ignored when updated, as the reality resides within the state
The current Parent implementation has a flaw as it doesn't rerender when a config changed.
So what is the correct way to implement this?
The parent component contains an array of objects.
It maps over the array and returns a child component for every object, populating it with the info of that object.
Inside each child component there is an input field that I'm hoping will allow the user to update the object, but I can't figure out how to go about doing that.
Between the hooks, props, and object immutability, I'm lost conceptually.
Here's a simplified version of the parent component:
const Parent = () => {
const [categories, setCategories] = useState([]);
useEffect(()=>{
// makes an axios call and triggers setCategories() with the response
}
return(
categories.map((element, index) => {
return(
<Child
key = {index}
id = {element.id}
firstName = {element.firstName}
lastName = {element.lastName}
setCategories = {setCategories}
})
)
}
And here's a simplified version of the child component:
const Child = (props) => {
return(
<h1>{props.firstName}</h1>
<input
defaultValue = {props.lastName}
onChange={()=>{
// This is what I need help with.
// I'm a new developer and I don't even know where to start.
// I need this to update the object's lastName property in the parent's array.
}}
)
}
Maybe without knowing it, you have lifted the state: basically, instead of having the state in the Child component, you keep it in the Parent.
This is an used pattern, and there's nothing wrong: you just miss a handle function that allows the children to update the state of the Parent: in order to do that, you need to implement a handleChange on Parent component, and then pass it as props to every Child.
Take a look at this code example:
const Parent = () => {
const [categories, setCategories] = useState([]);
useEffect(() => {
// Making your AXIOS request.
}, []);
const handleChange = (index, property, value) => {
const newCategories = [...categories];
newCategories[index][property] = value;
setCategories(newCategories);
}
return categories.map((c, i) => {
return (
<Child
key={i}
categoryIndex={i}
firstName={c.firstName}
lastName={c.lastName}
handleChange={handleChange} />
);
});
}
const Child = (props) => {
...
const onInputChange = (e) => {
props.handleChange(props.categoryIndex, e.target.name, e.target.value);
}
return (
...
<input name={'firstName'} value={props.firstName} onChange={onInputChange} />
<input name={'lastName'} value={props.lastName} onChange={onInputChange} />
);
}
Few things you may not know:
By using the attribute name for the input, you can use just one handler function for all the input elements. Inside the function, in this case onInputChange, you can retrieve that information using e.target.name;
Notice that I've added an empty array dependecies in your useEffect: without it, the useEffect would have run at EVERY render. I don't think that is what you would like to have.
Instead, I guest you wanted to perform the request only when the component was mount, and that is achievable with n empty array dependecies;
in react hook how can I empty my states and show the empty inputs in same time?
My problem is, when I reset state my Input remain full.
const [state, setState] = useState([initial])
handleInputChange(event) {
setState({
event.target.value
});
}
const resetState = () => {
setState([initial])
}
export default function newState () {
return(
<input onChange={handleInputChange()} />
<button onClick={resetState}/>
)
};
What (I think) you mean to to is this:
export default function Foo({ initial }) {
const [value, setValue] = React.useState(initial)
return(
<>
<input onChange={(event) => setValue(event.target.value)} value={value} />
<button onClick={() => setValue(initial)}>Reset</button>
</>
)
};
You have to call useState inside your components.
First problem: You have initialized state to an array, reset it to an array, but update it to an object.
Always keep your data types consistent, in this case neither seem appropriate. I would suggest just using a string value.
const [state, setState] = useState(initial)
handleInputChange(event) {
setState(event.target.value);
}
const resetState = () => {
setState(initial)
}
Remember - The useState updater is not the same as the class based setState. setState merges the previous and new state values. useState replaces. So if your state is not an object the update call setState({ newState }) is not correct anymore.
Second problem: <input onChange={handleInputChange()} /> is not right. What this code does is assign onChange the value returned from calling handleInputChange. What you actually want is to assign onChange the function itself.
// no ()
<input onChange={handleInputChange} />
Third Problem: Your state hook doesn't appear to be inside the functional component and it doesn't look like its set up for use as a custom hook (I could be wrong). Move them into the component function.