Override input value in Child component in React - reactjs

I have a child input component and parent where I set value.
Parent component
const \[dummyText, setDummyText\] = useState('')
return (
<Input
{...props}
id="first-name"
label="First name"
type="text"
placeholder="Please enter your first name"
value={dummyText}
onChange={(value) => setDummyText(value.target.value)}
/>
)
Child input component
import React, { InputHTMLAttributes, forwardRef } from 'react'
export type Props = {} & InputHTMLAttributes<HTMLInputElement>
const FormInput: React.FC<Props> = forwardRef<HTMLInputElement, Partial<Props>>(
({ ...props }, ref) => <input ref={ref} {...props} />
)
FormInput.displayName = 'FormInput'
export default FormInput
I want to add button in child component where on click I need to clear the value.
The thing is that I want to have clear functionality in child component and I do not want to write extra code for every <Input in parent, and whatever I do in child component, I just can't override props.value.
Does anybody have any experience how to solve this?

If you want to clear the input value you can pass the clearing function as a prop to the child component like this:
const Input = ({ onClearInput, ...otherProps }) => {
return (
<div style={{ display: 'flex', gap: '.5rem' }}>
<input {...otherProps} />
<button onClick={onClearInput}>Clear</button>
</div>
);
};
export default function App() {
const [firstName, setFirstName] = useState('');
const [secondName, setSecondName] = useState('');
const onClearInputHandler = (handler) => handler('');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '.5rem' }}>
<Input
id="first-name"
label="First name"
type="text"
placeholder="Please enter your first name"
value={firstName}
onChange={(event) => setFirstName(event.target.value)}
onClearInput={() => onClearInputHandler(setFirstName)}
/>
<Input
id="second-name"
label="Second name"
type="text"
placeholder="Please enter your second name"
value={secondName}
onChange={(event) => setSecondName(event.target.value)}
onClearInput={() => onClearInputHandler(setSecondName)}
/>
</div>
);
}
Because you'll have a different state for each input you can pass the set function from useState as a parameter and run it inside the onClearInputHandler to clear the value.
Edit:
Then I think the easiest solutions is to handle the clear function inside the input itself like this:
const Input = (props) => {
const [value, setValue] = useState(props.value || '');
return (
<div style={{ display: 'flex', gap: '.5rem' }}>
<input
{...props}
value={value}
onChange={(event) => setValue(event.target.value)}
/>
<button onClick={() => setValue('')}>Clear</button>
</div>
);
};
export default function App() {
const onSubmitHandler = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const formProps = Object.fromEntries(formData);
console.log(formProps);
};
return (
<form action="submit" onSubmit={onSubmitHandler}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '.5rem' }}>
<Input
id="first-name"
label="First name"
type="text"
placeholder="Please enter your first name"
name="first-name"
/>
<Input
id="second-name"
label="Second name"
type="text"
placeholder="Please enter your second name"
name="second-name"
/>
</div>
<button type="submit" style={{ margin: '.5rem 0' }}>
Submit
</button>
</form>
);
All inputs are inside the form and you can read the values when the form is submitted. Otherwise you'll have to create a new state or ref for every single Input you have in your parent component. This way the input value and the handler to clear it are always inside the Input component.
Here's a demo on stackblitz: https://stackblitz.com/edit/react-gnyvav?file=src%2FApp.js

There are a few solutions for this, I came up with these two
onValueChange
Another solution might be to refactor the onChange to a onValueChange which instead of the event immediately returns the value. Updated the type and removed the Partial since it was giving some errors.
type Props = {
onValueChange: (value: string) => void;
} & InputHTMLAttributes<HTMLInputElement>;
const FormInput: React.FC<Props> = forwardRef<HTMLInputElement, Props>(
({ onValueChange, ...props }, ref) => {
return (
<>
<input
ref={ref}
onChange={(e) => onValueChange(e.target.value)}
{...props}
/>
<button onClick={() => onValueChange("")}>clear</button>
</>
);
}
);
A issue with this is that if a onChange is provided it will override the existing onChange. We can solve this by simply adding it and passing the e (event)
const FormInput: React.FC<Props> = forwardRef<HTMLInputElement, Props>(
({ onValueChange, onChange, ...props }, ref) => {
return (
<>
<input
ref={ref}
onChange={(e) => {
onValueChange(e.target.value);
onChange(e);
}}
{...props}
/>
<button onClick={() => onValueChange("")}>clear</button>
</>
);
}
);
Which you can then use by
<FormInput
id="first-name"
type="text"
placeholder="Please enter your first name"
value={dummyText}
onValueChange={setDummyText}
// onChange={(e) => console.log(e)}
/>
onClear
Now this does not fully move the functionality to the Input component.
We destructure the onClear function and add it to the onClick of the clear button.
type Props = { onClear: () => void } & InputHTMLAttributes<HTMLInputElement>;
const FormInput: React.FC<Props> = forwardRef<HTMLInputElement, Props>(
({ onClear, ...props }, ref) => {
return (
<>
<input ref={ref} {...props} />
<button onClick={onClear}>clear</button>
</>
);
}
);
Now to use it you can go like this
<Input
id="first-name"
onClear={() => setDummyText("")}
type="text"
placeholder="Please enter your first name"
value={dummyText}
onChange={(event) => setDummyText(event.target.value)}
/>

Related

React - pass props into input component

What I need is to be able to customize textAreaCount and other props in each separate instance of <Texarea/>. The textAreaCount is different for each <Texarea/> so how do I modify the component to be able to pass in custom textAreaCount for each <Texarea/>?
https://codesandbox.io/s/rkv88-forked-vjo0rn?file=/src/App.js:0-948
import React, { useState } from "react";
const Textarea = (value, id, maxLength, textAreaLimit) => {
const [textAreaCount, ChangeTextAreaCount] = React.useState(0);
const [state, setstate] = useState({
headline: "",
title: ""
});
const { headline, title } = state;
const changevalue = (e) => {
setstate({
...state,
[e.target.name]: value
});
ChangeTextAreaCount(e.target.value.length);
};
return (
<>
<p>{textAreaCount}/{textAreaLimit}</p>
<textarea
type="text"
rows={5}
id={id}
value={value}
maxLength={maxLength}
onChange={(e) => {
changevalue(e);
}}
/>
</>
);
};
export default function FullWidthTabs() {
return (
<div>
<Textarea value={headline} id="test" maxLength={5} textAreaLimit={5}/>
<Textarea value={title} id="test2" maxLength={10} textAreaLimit={10}/>
</div>
);
}
Forward the props you need.
const Textarea = (props) => {
const [textAreaCount, setTextAreaCount] = React.useState(0);
const recalculate = (e) => {
setTextAreaCount(e.target.value.length);
};
return (
<>
<p>{textAreaCount}/5</p>
<textarea type="text" rows={5} maxLength={5} onChange={recalculate} {...props} />
</>
);
};
Now it will forward any props into the textarea element. This will set the id and will overwrite the rows prop.
<Textarea id="textarea-1" rows={4} />
<Textarea id="textarea-2" rows={5} maxLength={10} />
As we can see you try to pass props as below:
<Textarea value={title} id="test2" maxLength={10} textAreaLimit={10}/>
But In Your Textarea Component you received props argument as multiple args as below:
const Textarea = (value, id, maxLength, textAreaLimit) => {
return (
<>
</>
);
};
Instead that you need to destruct your props argument or you can set whole passed value props as single object props as below:
Method 1:
const Textarea = ({value, id, maxLength, textAreaLimit}) => {
return (
<>
<textarea type="text" id={id} value={value} rows={5} maxLength={maxLength} onChange={recalculate} textAreaLimit={textAreaLimit} />
</>
);
};
Method 2:
const Textarea = ({...props}) => {
return (
<>
<textarea type="text" id={id} value={value} rows={5} maxLength={maxLength} onChange={recalculate} textAreaLimit={textAreaLimit} />
</>
);
};
Method 3:
const Textarea = (props) => {
return (
<>
<textarea type="text" id={props.id} value={props.value} rows={5} maxLength={props.maxLength} onChange={recalculate} textAreaLimit={props.textAreaLimit} />
// Or Instead you can do as below
// <textarea type="text" rows={5} maxLength={5} onChange={recalculate} {...props} />
</>
);
};

Why my checkbox doesn't work in my Dialog?

I create a component for my Dialog and my Checkbox my issue is when my checkbox is not in the Dialog the update works but when it's inside it doesn't work. I don't understand why.
const Popup = ({ title, handleClose, openned, children }) => {
return (
<Dialog className='react-popup-template' fullWidth={true} maxWidth='sm' open={openned} onClose={handleClose} aria-labelledby="parent-modal-title" aria-describedby="parent-modal-description">
<DialogContent id="modal-description" >
<div>
{title && <div><h4 style={{ textAlign: 'center', fontWeight: 'bold', fontSize : '23px' }}>{title}</h4><br/></div>}
{children}
</div>
</DialogContent>
</Dialog>
);
}
const CheckBox = (value, onChange) => {
return (
<label>
<input type='checkbox' value={value} onChange={onChange} />
</label>)
}
const App = () =>{
const [openPopup, setOpenPopup] = React.useState(false)
const [checked, setChecked] = React.useState(false)
const [title, setTitle] = React.useState('')
const [description, setDescription] = React.useState('')
const showModal = (title) =>{
setTitle(title)
setDescription(<CheckBox value={checked} onChange={() => {setChecked(!checked)}} />)
}
return (
<button onClick={() => {showModal('Title')}}>showModal</button>
<PopupTemplate title={title} handleClose={() => { setOpenPopup(false) }} openned={openPopup}>
{description}
</PopupTemplate>)
}
In your Checkbox you should either destructure your props
const CheckBox = ({ value, onChange }) => {
return (
<label>
<input type="checkbox" value={value} onChange={onChange} />
</label>
);
};
Or use your props via the props value
const CheckBox = (props) => {
return (
<label>
<input type="checkbox" value={props.value} onChange={props.onChange} />
</label>
);
};
EDIT:
The state only updates the first time you click the checkbox. Using the callback in the setChecked method will solve this.
...
setDescription(
<CheckBox
value={checked}
onChange={() => {
setChecked((prevChecked) => !prevChecked);
}}
/>
);
...
PS: I don't now if its just a copy/paste error, but you're missing setOpenPopup(true) in your showModal function.
Try this, as mui uses forwarRef for its components this should work,
setDescription(<CheckBox checked={checked} onChange={e => setChecked(!checked)} />)

How to pass state props to another component

I want to pass a state variable to another component but i don't get it what i'm doing wrong.
I have a component for radio inputs and I need to pass that taskLabel to Form component.
path is components/inputs/index.js
const TaskLabel = (props) => {
const [taskLabel, setTaskLabel] = useState('');
return (
<div label={props.taskLabel} >
<input
type='radio'
name='label'
value='urgent'
onChange={(e) => setTaskLabel(e.target.value)}
/>
<input
type='radio'
name='label'
value='not-urgent'
onChange={(e) => setTaskLabel(e.target.value)}
/>
</div>
);
};
i want to receive the taskLabel value, to use it in submitHandler function.
components/form/index.js
const Form = ({taskLabel}) => {
return (
<form onSubmit={submitHandler}>
<input
type='text'
placeholder='Text here'
className='form-input'
value={task}
onChange={(e) => {
setTask(e.target.value);
}}
/>
<TaskLabel taskLabel={taskLabel} />
</form>
)
}
This is what i tried, to pass taskLabel props from label={taskLabel}.
You need to move your state, to Form component, like this:
const [labelProp, setLabelProp] = useState("");
Your TaskLabel component should be
<TaskLabel label={{ labelProp, setLabelProp }} />
That means, you send label, as a prop to TaskLabel component.
In TaskLabel component you need to recive the prosp, by passing to component {label}.
Next, for every input use onChange={(e) => label.setLabelProp(e.target.value)}.
Edited sandbox => https://codesandbox.io/s/laughing-proskuriakova-dhi568?file=/src/components/task-label/index.js
Generally speaking, the concept is "data-down, actions-up". So if you want to pass data down to a lower-level component, you just pass a prop. If you want to update a value from a lower-level component to a higher-level component, you could pass a setter function down as a prop and call that.
Note I just call it taskLevelProp for a little more clarity. You should probably use a better name.
TaskLabel (lower level component)
/* It looks like TaskLabelProp gets passed in from something else.
Otherwise, you would use useState to get your setter function */
const TaskLabel = ({taskLabelProp, setTaskLabelProp}) => {
return (
<div label={props.taskLabel} >
<input
type='radio'
name='label'
value='urgent'
onChange={(e) => setTaskLabelProp(e.target.value)}
/>
<input
type='radio'
name='label'
value='not-urgent'
onChange={(e) => setTaskLabelProp(e.target.value)}
/>
</div>
);
};
Form (higher level component)
const Form = () => {
const [taskLabelProp, setTaskLabelProp] = useState('');
return (
<form onSubmit={submitHandler}>
<input
type='text'
placeholder='Text here'
className='form-input'
value={task}
onChange={(e) => {
setTask(e.target.value);
}}
/>
<TaskLabel taskLabel={taskLabelProp, setTaskLabelProp} />
</form>
)
}
Let me know if this answers your question.
EDIT: Made Form use useState. Based off your code, I was assuming you were using useState at the app level.
function App() {
const [task, setTask] = useState("");
const [taskLabelProp, setTaskLabelProp] = useState("");
const handleChange = (e) => {
setTaskLabelProp(e.target.value);
};
const submitHandler = (e) => {
e.preventDefault();
};
return (
<div className="App">
<form onSubmit={submitHandler}>
<input
type="text"
placeholder="Text here"
className="form-input"
value={task}
onChange={(e) => {
setTask(e.target.value);
}}
/>
<TaskLabel
onChange={handleChange}
taskLabelProp={taskLabelProp}
taskLabel="select"
/>
<button type="submit">fs</button>
</form>
</div>
);
}
export default App;
const TaskLabel = ({ taskLabel, onChange, taskLabelProp }) => {
return (
<div label={taskLabel}>
<input
type="radio"
name="label"
value="urgent"
onChange={(e) => onChange(e)}
checked={taskLabelProp === "urgent"}
/>
urgent
<input
type="radio"
name="label"
value="not-urgent"
onChange={(e) => onChange(e)}
checked={taskLabelProp === "not-urgent"}
/>
not-urgent
</div>
);
};
export default TaskLabel;

Rendering a new Component with React Hooks onClick

I am trying to generate a new form when you click a button. I want to do this with hooks. I am struggling to wrap my head around what I need to do, below is an example function and the button. Any help is appreciated.
const addStudent = () => {
console.log("clicked")
return(
<Container>
<Form form="First Name" type="text" placeholder="First Name"/>
<Form form="Last Name" type="text" placeholder="Last Name"/>
<Form form="Date of Birth" type="date"/>
<Form form="School ID" type="text"/>
<Form form="Campus" type="text"/>
<Form form="School Grade" type="text"/>
</Container>
)
console.log(setStudent)
}
<button onClick={addStudent}>Add Another Student</button>
You can do something like this
https://codesandbox.io/s/silly-nash-0slhh?file=/src/App.js:97-1145
App.js
import Input from "./Input";
export default function App() {
const [students, setStudents] = useState([]);
const addStudent = () => {
setStudents(students => [...students, <Input />]);
console.log("dddd", students);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={addStudent}>Add Another Student</button>
{students.map((item, i) => (
<div key={i}>{item}</div>
))}
</div>
);
}
Input.js
import React, { useState } from "react";
export default function Input() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastname] = useState("");
const [Dob, setDob] = useState("");
return (
<div style={{ width: "200px", height: "100px", padding: "5px" }}>
<input
type="text"
value={firstName}
onChange={e => setFirstName(e.target.value)}
placeholder="First Name"
/>
<input
type="text"
value={lastName}
onChange={e => setLastname(e.target.value)}
placeholder="Last Name"
/>
<input
type="text"
value={Dob}
onChange={e => setDob(e.target.value)}
placeholder="DOB"
/>
</div>
);
}
I created a variable called students and using Context for capturing the state values. I then mapped through students and then for the addStudent function just used the spread operator and set setStudents below.
const [students, setStudents] = useContext(FormDataContext);
*this is in the return()*
{students.map((student, index) => (
<FormCard
key={index}
index={index}
handleChange={handleChange}
student={students[index]}
metadata={metadata}
/>
))}
*then called this function in my button onClick*
const addStudent = () => {
setStudents([...students, {}]);
};

How to set state for text box in functional component

I am working on React JS. I have one text-box component and I want to show some default value in it. After that, the user should be allowed to change the value. Now I am unable to change the value. The text box is behaving like read-only. Below is my code
const EditStyleFormComponent = ({
submitting,
invalid,
}) => (
<form className={className} onSubmit={handleSubmit}>
<h2>LSPL (Low Stock Presentation Level)</h2>
<Line />
<InputGroup>
<TextFieldWithValidation name="lsplMan" label="LSPL Manual" input={{ onChnage:'', value: 'Current' }} />
</InputGroup>
</form>
);
Below is my TextFieldWithValidation code.
export const TextFieldWithValidationComponent = ({
meta,
input,
noStyles,
...otherProps
}) => (
<TextField
state={noStyles ? textFieldStates.DEFAULT : getState(meta)}
errorMessage={meta.touched ? meta.error : null}
{...input}
{...otherProps}
/>
);
Below is my TextField code.
const TextField = ({
className,
label,
description,
state,
errorMessage,
isEditable,
spaceAtBottom, // Not used, but we don't want it in otherProps
...otherProps
}) => {
const inputId = _.uniqueId();
return (
<div className={className}>
{label &&
<label htmlFor={inputId}>{label}</label>
}
<div className="input-group" id={isEditable ? 'editable' : 'readonly'}>
<input
id={inputId}
readOnly={!isEditable}
{...otherProps}
/>
{getStatusIcon(state)}
{errorMessage &&
<Error>{errorMessage}</Error>
}
{description &&
<Description>{description}</Description>
}
</div>
</div>
);
};
Can someone help me to fix this issue? Any help would be appreciated. Thanks
You can use State Hook for manage state in functional component.
Example :
const Message = () => {
const [message, setMessage] = useState( '' );
return (
<div>
<input
type="text"
value={message}
placeholder="Enter a message"
onChange={e => setMessage(e.target.value)}
/>
<p>
<strong>{message}</strong>
</p>
</div>
);
};
Yu defined onChange as empty string in EditStyleFormComponent component. So on any change input component just do nothing.
onChange should be some function that will update value.
If you want to use functional components there are two possible solutions:
Lift state up to parent component of EditStyleFormComponent (in case parent is class based component)
Use React Hooks like so (just example!)
const EditStyleFormComponent = ({
submitting,
invalid,
}) => {
const [inputValue, setInputValue] = useState ('Current'); // default value goes here
return <form className={className} onSubmit={handleSubmit}>
<h2>LSPL (Low Stock Presentation Level)</h2>
<Line />
<InputGroup>
<TextFieldWithValidation name="lsplMan" label="LSPL Manual" input={{ onChnage: (e) => { setInputValue(e.target.value); }, value: inputValue }} />
</InputGroup>
</form>
};

Resources