React : retrieve the state of a child component - reactjs

I am trying to retrieve the value of the input in my Input component, to assign it to my user object in the parent component.
Here is my user object and my JSX:
const Form = ({ form }) => {
const [inputValue, setInputValue] = useState("");
const user = {
user_firstname: "OO",
user_lastname: "OO",
user_email: "lOOl0jyyv",
user_password: "OO"
};
return (
<form className="form" onSubmit={callApi}>
<Input
key={uuidv4()}
className="input_container"
type="text"
placeholder="Prénom"
id="firstname"
name="firstname"
min="2"
max="40"
/>
<Input
key={uuidv4()}
className="input_container"
type="text"
placeholder="Nom"
id="lastname"
name="lastname"
min="2"
max="60"
/>
<Input
key={uuidv4()}
className="input_container"
type="email"
placeholder="Votre adresse mail"
id="email"
name="email"
/>
<Input
key={uuidv4()}
className="input_container"
type="password"
placeholder="Mot de passe"
id="password"
name="password"
min="10"
max="32"
/>
<Input
key={uuidv4()}
className="input_container"
type="password"
placeholder="Confirmez le mot de passe"
id="verify-password"
name="verify-password"
min="10"
max="32"
/>
<Button name="Inscription" />
</form>
);
};
Each has its own state, I would like to be able to retrieve it from the parent component to assign them to my "user" object. This is my Input component :
const Input = ({
className,
type,
id,
name,
placeholder,
min,
max,
icon1,
icon2
}) => {
const inputHandler = e => {
setInputValue(e.target.value);
};
const [inputValue, setInputValue] = useState("");
return (
<div className={className}>
<input
className="testt"
type={type}
id={id}
name={name}
placeholder={placeholder}
minLength={min}
maxLength={max}
required
value={inputValue}
onChange={inputHandler}
/>
{icon1 && (
<div className="icons_container">
<FontAwesomeIcon icon={icon1} />
<FontAwesomeIcon icon={icon2} />
</div>
)}
</div>
);
};

Why not define the state in the parent element, then pass a reference to the state in the props of the child? In your case, place the input state of all the <Input /> components in the <Form /> component and pass each state to its respective input field.

You can pass onChange handler function as a prop from parent to child. This function will set value for user when child (Input) component invokes the function.
Changes needed for parent component:
...
const [user, setUser] = React.useState({
user_firstname: "OO",
user_lastname: "OO",
user_email: "lOOl0jyyv",
user_password: "OO"
});
return (
<form className="form" onSubmit={callApi}>
<Input
key={uuidv4()}
className="input_container"
type="text"
placeholder="Prénom"
id="firstname"
name="firstname"
min="2"
max="40"
onChange={value => setUser({ ...user, user_firstname: value })}
/>
...
Changes needed for Input component:
const Input = ({
className,
type,
id,
name,
placeholder,
min,
max,
icon1,
icon2,
onChange
}) => {
const inputHandler = e => {
setInputValue(e.target.value);
onChange(e.target.value);
};
...

Related

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;

Can't input on other Form Fields

I'm using react. I was wondering on why can I only edit the FirstName part of the form while the other input fields wont let me type anything.
Here is my code:
import React, {useContext, useState, useEffect} from 'react';
import{GlobalContext} from '../context/GlobalState';
import {Link, useHistory} from 'react-router-dom';
import {Form,FormGroup,Label,Input,Button} from 'reactstrap';
export const EditUser = (props) => {
const {users,editEmployee } = useContext(GlobalContext);
const [selectedUser, setSelectedUser] = useState({
id:'',
name:'',
nameL:'',
email:'',
contact:'',
address:'',
date:'',
});
const history= useHistory();
const currentUserId = props.match.params.id;
useEffect(() =>{
const userId =(currentUserId);
const selectedUser =users.find(user=>user.id === userId)
setSelectedUser(selectedUser)
}, [currentUserId,users])
const onChange = (e) =>{
setSelectedUser({...selectedUser, [e.target.name]: e.target.value})
}
const onNameL = (e) =>{
setSelectedUser({...selectedUser, [e.target.nameL]: e.target.value})
}
const onEmail = (e) =>{
setSelectedUser({...selectedUser, [e.target.email]: e.target.value})
}
const onContact = (e) =>{
setSelectedUser({...selectedUser, [e.target.contact]: e.target.value})
}
const onAddress = (e) =>{
setSelectedUser({...selectedUser, [e.target.address]: e.target.value})
}
const onDate = (e) =>{
setSelectedUser({...selectedUser, [e.target.date]: e.target.value})
}
const onSubmit= () =>{
editEmployee(selectedUser)
history.push('/');
}
return (
<React.Fragment>
<Form onSubmit={onSubmit}>
<FormGroup>
<Label>FirstName</Label>
<Input type="text" name="name" value={selectedUser.name} onChange={onChange} placeholder="enter first name"></Input>
<Label>Last Name:</Label>
<Input type="text" name="nameL" value= {selectedUser.nameL} onChange={onNameL} placeholder="enter your Last name"></Input>
<Label>Email:</Label>
<Input type="email" name="email" value= {selectedUser.email} onChange={onEmail} placeholder="Email Address"></Input>
<Label>Contact Number:</Label>
<Input type="number" name="contact" value= {selectedUser.contact} onChange={onContact} placeholder="Contact"></Input>
<Label>Address:</Label>
<Input type="text"name="address" value= {selectedUser.address} onChange={onAddress} placeholder="enter your Address"></Input>
<Label>Date</Label>
<Input type="date" name="date" value= {selectedUser.date} onChange={onDate} placeholder="enter date employed"></Input>
</FormGroup>
<Button type="submit" className="btn btn-info">Edit </Button>
<Link to="/" className="btn btn-danger">Cancel</Link>
</Form>
</React.Fragment>
)
}
export default EditUser;
In all the functions which are being used to control form fields, use the following code:
setSelectedUser({...selectedUser, [e.target.name]: e.target.value})
Also, you can just use a single function i.e. onChange, for the current implementation. There is no use in using multiple functions.
Welcome to the Stack Overflow Community !!!
You can do that using fewer lines of code in a very optimized manner like this. It will fix your error & make it easier to debug. Try this out.
// necessary imports
export const EditUser = (props) => {
const [selectedUser, setSelectedUser] = useState({
id: "",
name: "",
nameL: "",
email: "",
contact: "",
address: "",
date: "",
});
const onChange = (e) => {
const property = e.target.name;
const value = e.target.value;
setSelectedUser({
...selectedUser,
[property]: value,
});
}; // Just have only one event for all form elements.
return (
<React.Fragment>
<Form>
<FormGroup>
<Label>FirstName</Label>
<Input
type="text"
name="name"
value={selectedUser.name}
onChange={onChange}
placeholder="enter first name"
></Input>
<Label>Last Name:</Label>
<Input
type="text"
name="nameL"
value={selectedUser.nameL}
onChange={onChange}
placeholder="enter your Last name"
></Input>
<Label>Email:</Label>
<Input
type="email"
name="email"
value={selectedUser.email}
onChange={onChange}
placeholder="Email Address"
></Input>
<Label>Contact Number:</Label>
<Input
type="number"
name="contact"
value={selectedUser.contact}
onChange={onChange}
placeholder="Contact"
></Input>
<Label>Address:</Label>
<Input
type="text"
name="address"
value={selectedUser.address}
onChange={onChange}
placeholder="enter your Address"
></Input>
<Label>Date</Label>
<Input
type="date"
name="date"
value={selectedUser.date}
onChange={onChange}
placeholder="enter date employed"
></Input>
</FormGroup>
<Button type="submit" className="btn btn-info">
Edit{" "}
</Button>
</Form>
</React.Fragment>
);
}
Let me know if you need further support.

React/Hooks extra undefined field upon submission

I'm getting a really weird return error that on submission I randomly add an extra duplicate field somehow which is of course then undefined. The input value is also randomly copied from one of the other fields within the form.
const GameForm = () => {
const url = 'http://localhost:6060/games'
const handleInputChange = e => {
const { name, value, year, rating, developer } = e.target
setGameData({ ...gameData, [name]: value, [year]: value, [rating]: value, [developer]: value })
}
const onSubmit = (e) => {
e.preventDefault()
const { name, year, rating, developer } = gameData
if (!name || !year || !rating || !developer) return
console.log(gameData)
axios
.post(url, gameData)
.then((res) => {
setGameData(res)
}).catch((error) => console.log(error))
}
const [gameData, setGameData] = useState({ name: '', year: 0, rating: '', developer: "" })
return (
<form onSubmit={onSubmit}>
<label id="name" htmlFor="name">Name: </label>
<input type="text" name="name" placeholder="Game title" onChange={handleInputChange} />
<br />
<label htmlFor="year">Year: </label>
<input type="number" name="year" placeholder="Release year" onChange={handleInputChange} />
<br />
<label htmlFor="rating">Rating: </label>
<input type="text" name="rating" placeholder="Age rating" onChange={handleInputChange} />
<br />
<label htmlFor="developer">Developer: </label>
<input type="text" name="developer" placeholder="Developer" onChange={handleInputChange} />
<br />
<input type="submit" value="Submit"></input>
</form>
)
}
Console logged return: (I also get a 500 error obviously)
{name: "1", year: "1", rating: "1", developer: "1", undefined: "1"}
The undefined value is seemingly taken from any of the existing fields at random.
I feel like I am likely overlooking something obvious.
You are mis-using e.target. It will not have all the properties that you try to destruct from it. From the ones you list in your example code, it will only have name and value, all the other ones (rating, year, developer) will be undefined as they are not part of the event's target property.
The reason you only get one undefined value in your state object is because it keeps overriding itself when you set your state.
The property from the event target you are trying to access is name, so in your case basically e.target.name
Anyway, with that in mind the fix for your app will be quite simple:
const handleInputChange = e => {
const { name, value } = e.target
// Note: The name will hold whatever value of the name prop you put on your input.
// When you are editing the input with the name prop set to name, it will be "name"
// For the input with the name prop set to "year", it will be "year:
// For the input with the name prop set to "developer" it will be "developer" and so on.
setGameData({ ...gameData, [name]: value })
}
Here is a demo for you with the fix:
const App = () => {
const [gameData, setGameData] = React.useState({ name: '', year: 0, rating: '', developer: "" })
const handleInputChange = e => {
const { name, value } = e.target
setGameData({ ...gameData, [name]: value })
}
return (
<div>
<label id="name" htmlFor="name">Name: </label>
<input type="text" name="name" placeholder="Game title" onChange={handleInputChange} />
<br />
<label htmlFor="year">Year: </label>
<input type="number" name="year" placeholder="Release year" onChange={handleInputChange} />
<br />
<label htmlFor="rating">Rating: </label>
<input type="text" name="rating" placeholder="Age rating" onChange={handleInputChange} />
<br />
<label htmlFor="developer">Developer: </label>
<input type="text" name="developer" placeholder="Developer" onChange={handleInputChange} />
<br />
<button onClick={() => console.warn('GAME DATA OBJECT', gameData)}>Click</button>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Clear value into input using useState or useRef (React)

I got hook:
const [myName, setMyName] = useState("");
const myNameRef = useRef();
Then I have form:
<form onSubmit={(e) => addVist(e, user.id)}>
<input type="text" name="myName" ref={myNameRef} onChange={e => setMyName(e.target.value)} />
<input type="submit" value="Run" />
</form>
And method:
const addVist = (e, userId) => {
e.preventDefault();
console.log(myName)
//some code....
//clear value form
setMyName("");
//setMyName('');
//setMyName(e.target.value = "");
//myNameRef.current.value("");
}
But the setMyName("") is not working - still I see value inside input.
You missed binding myName as value attribute of the input tag.
<input type="text" name="myName" value={myName} ref={myNameRef} onChange={e => setMyName(e.target.value)} />
Here is a complete example of clearing input using state OR a reference:
export default function App() {
const [value, setValue] = useState("");
const inputRef = useRef();
return (
<>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<input ref={inputRef} />
<button
onClick={() => {
setValue("");
inputRef.current.value = "";
}}
>
Clear
</button>
</>
);
}
Refer to Controlled VS Uncontrolled components.
On your text input you should pass myName into the value attribute like so
<input type="text" name="myName" value={myName} ref={myNameRef} onChange={e => setMyName(e.target.value)} />
You forgot to add myName as value.
<input type="text" name="myName" value={myName} ref={myNameRef} onChange={e => setMyName(e.target.value)} />

Reseting value of input element using React Hooks

I made a useInput hook that looks like this.
export function useInput({
type,
name,
placeholder,
initialValue,
helpText,
required,
onKeyUp,
errorMessage,
}: Props) {
const [value, setValue] = useState(initialValue)
const input =
type === 'textarea' ? (
<div className="form-group">
<label>{name}</label>
<span>{helpText}</span>
<textarea
name="email"
className="form-control"
required={required}
id={name}
value={value}
aria-describedby={name}
placeholder={placeholder}
onChange={(e) => setValue(e.target.value)}
/>
</div>
) : (
<div className="form-group">
<label>{name}</label>
<span>{helpText}</span>
<input
type={type}
name="email"
className="form-control"
id={name}
required={required}
value={value}
aria-describedby={name}
placeholder={placeholder}
onChange={(e) => setValue(e.target.value)}
onKeyUp={onKeyUp}
/>
<>{errorMessage}</>
</div>
)
return [value, input]
}
In my pages, I can simply use the hook, like below
const [description, descriptionInput] = useInput({
type: 'textarea',
name: 'Description',
helpText: <small className="muted"> (Optional)</small>,
placeholder: 'I found this interesting because...',
})
and inside the render function use them as {descriptionInput} and the value will be available in description.
However, I'm looking for ways to update the value programmatically - to be specific, to reset the value of inputs on form submission for example. All I have is a reference to the input, and the value itself. Is there a nice way to keep using the hook and reset the value at will?
You can simply expose a reset function from your hook which you can call to reset the state to either the initialValue or empty
export function useInput({
type,
name,
placeholder,
initialValue,
helpText,
required,
onKeyUp,
errorMessage,
}: Props) {
const [value, setValue] = useState(initialValue);
const resetInput = useCallback(() => {
setValue(initialValue || "" );
}, [])
const input =
type === 'textarea' ? (
<div className="form-group">
<label>{name}</label>
<span>{helpText}</span>
<textarea
name="email"
className="form-control"
required={required}
id={name}
value={value}
aria-describedby={name}
placeholder={placeholder}
onChange={(e) => setValue(e.target.value)}
/>
</div>
) : (
<div className="form-group">
<label>{name}</label>
<span>{helpText}</span>
<input
type={type}
name="email"
className="form-control"
id={name}
required={required}
value={value}
aria-describedby={name}
placeholder={placeholder}
onChange={(e) => setValue(e.target.value)}
onKeyUp={onKeyUp}
/>
<>{errorMessage}</>
</div>
)
return [value, input, resetInput]
}
and use it like
const [description, descriptionInput, resetDescriptionInput] = useInput({
type: 'textarea',
name: 'Description',
helpText: <small className="muted"> (Optional)</small>,
placeholder: 'I found this interesting because...',
})
P.S. However, since this hook is actually returning JSX content, you could write it as a component too and expose a function to be used by a ref with useImperativeHandle

Resources