How can update props in child component functional component? - reactjs

I have a parent component and a child component.
The parent component sends data to the child.
The child modifies the value and the parent should see the change reflected.
But nevertheless there is something that I am not doing well because I do not understand the mechanisms of react well.
Parent component
function ParentComponent() {
var userName = "Didi";
return (
<div className="Parent">
<label>Parent - {userName}</label>
<ChildComponent userName={userName} />
</div>
);
}
Child component
function ChildComponent({ userName }) {
const handleChange = (e) => {
userName = e.target.value;
};
return (
<div className="ChildComponent">
<input type="text" defaultValue={userName} onChange={handleChange} />
<br />
<label>ChildComponent - {userName}</label>
</div>
);
}

React can't monitor free variables for changes. You need to store data in a state (or use some external system like Redux).
If you want to change the state from a different component, then you need to pass the function which changes it to the component.
function ParentComponent() {
const [userName, setUserName] = React.useState("Didi");
return (
<div className="Parent">
<label>Parent - {userName}</label>
<ChildComponent userName={userName} setUserName={setUserName} />
</div>
);
}
function ChildComponent({ userName, setUserName }) {
const handleChange = (e) => {
setUserName(e.target.value);
};
return (
<div className="ChildComponent">
<input type="text" defaultValue={userName} onChange={handleChange} />
<br />
<label>ChildComponent - {userName}</label>
</div>
);
}

Try this:
Parent component
function ParentComponent() {
const [username, setUsername] = useState("");
return (
<div className="Parent">
<label>Parent - {username}</label>
<ChildComponent
username={username}
changeUsername={value => setUsername(value)}
/>
</div>
);
}
Child component
function ChildComponent({ username, changeUsername }) {
const handleChange = e => {
changeUsername(e.target.value);
};
return (
<div className="ChildComponent">
<input type="text" value={username} onChange={handleChange} />
<br />
<label>ChildComponent - {username}</label>
</div>
);
}

You have to do this:
Parent component
import {useState} from 'react';
function ParentComponent() {
const [userName,setUserName]=useState("Didi");
return (
<div className="Parent">
<label>Parent - {userName}</label>
<ChildComponent userName={userName} setUserName={setUserName}/>
</div>
);
}
Child component
function ChildComponent({ userName,setUserName }) {
return (
<div className="ChildComponent">
<input type="text" defaultValue={userName} onChange={(e)=>setUserName(e.target.value)} />
<br />
<label>ChildComponent - {userName}</label>
</div>
);
}

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} />
</>
);
};

share hooks across components

I am trying to understand how to share hooks state across components. But it doesn't seem to be sharing. Am I doing something wrong here?
Home.js
export default function Home() {
const [search, setSearch]= useState('');
return (
<div>
<Input search={search} handleChange={setSearch} />
<Products search={search} handleChange={setSearch} />
</div>
)
}
Input.js
export default function Input({search, setSearch}) {
const handleChange = (e) => {
setSearch(e.target.value)
}
return (
<div className='App'>
<input
placeholder='search...'
value={search}
onChange={handleChange}
/>
{search}
</div>
)
}
Live Example:
const { useState } = React;
/*export default*/ function Home() {
const [search, setSearch]= useState('');
return (
<div>
<Input search={search} handleChange={setSearch} />
<Products search={search} handleChange={setSearch} />
</div>
)
}
/*export default*/ function Input({search, setSearch}) {
const handleChange = (e) => {
setSearch(e.target.value)
}
return (
<div className='App'>
<input
placeholder='search...'
value={search}
onChange={handleChange}
/>
{search}
</div>
)
}
const Products = ({search}) => {
return <div>Product: {search}</div>;
};
ReactDOM.createRoot(document.getElementById("root"))
.render(<Home />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.0.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.development.js"></script>
You pass handleSearch as prop in your Home component but Input is expecting setSearch, so just change this line in your Home
return (
<div>
<Input search={search} setSearch={setSearch} /> // change here
<Products search={search} handleChange={setSearch} />
</div>
)

how to get the data from a component within a form in react

I have a with the following structure in react...
<form>
<div>
<label>Name</label>
<input />
</div>
<div>
<label>Age</label>
<input />
</div>
<div>
<label>Gender</label>
<input />
</div>
<AddressComponent />
<button type='submit'>
Submit
</button>
</form>
Does anyone know if there is a way for me to pass the AddressComponent data up to the parent form component onSubmit? The AddressComponent has a lot of state to manage and a load of functions and it appears a few more times across my website and so I don't want to keep repeating the same code over and over again.
Anyone know if there's a way for me to pass the AddressComponent state up to the parent form component onSubmit?
you can use ref to get access to the child functions so:
const AddressComponent = ({}, ref) => {
const [data, setData] = useState();
useImperativeHandle(ref, () => ({
getData: getData,
}));
const getData = () => {
return data;
}
return <div></div>;
};
export default React.forwardRef(AddressComponent);
and you can get data in your parent and in submit like this:
const ParentComponent = () => {
const AddressRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
const data = AddressRef.current.getData();
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>Name</label>
<input />
</div>
<div>
<label>Age</label>
<input />
</div>
<div>
<label>Gender</label>
<input />
</div>
<AddressComponent ref={AddressRef} />
<button type='submit'>
Submit
</button>
</form>
)
}

Value from input field does not display

I am trying to set the value inside an input in a Form component using KendoReact but the value is not displayed and upon debugging I see that the value is not defined. What am I doing wrong and what is the right approach here? Here is my code:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Form, Field, FormElement } from '#progress/kendo-react-form';
import { Input } from '#progress/kendo-react-inputs';
const InputField1 = (fieldRenderProps) => {
const { label, value } = fieldRenderProps;
return (
<div>
<Input label={label} value={value} />
</div>
);
};
const App = () => {
return (
<div>
<Form
render={(formRenderProps) => (
<>
<br />
<br />
<div>
<Field
name="Field1"
label="Field1 Label"
component={InputField1}
value="value1"
/>
</div>
<br />
<br />
</>
)}
/>
</div>
);
};
ReactDOM.render(<App />, document.querySelector('my-app'));
Here is a working codesandbox You have to pass the other props to the Input:
const InputField1 = (fieldRenderProps) => {
const { label, value, ...others } = fieldRenderProps;
console.log(value);
return (
<div>
<Input label={label} value={value} {...others} />
</div>
);
};
const MyCustomInput = (fieldRenderProps) => {
const {label, value, onChange} = fieldRenderProps;
return (
<Input label={label} value={value} onChange={onChange} />
);
};
const App = () => {
const handleSubmit = (dataItem) => alert(JSON.stringify(dataItem, null, 2));
return (
<Form
onSubmit={handleSubmit}
render={(formRenderProps) => (
<FormElement style={{maxWidth: 650}}>
<Field name={'firstName'} label={'First Name'} component={MyCustomInput} />
<div className="k-form-buttons">
<button type={'submit'} disabled=
{!formRenderProps.allowSubmit} className={'k-button'}>
Submit
</button>
</div>
</FormElement>
)}
/>
);
};
ReactDOM.render(
<App />,
document.querySelector('my-app')
);
you have to add onChange in input tag

Onchange in React input causes the input to rerender on every letter

So I've created inputs in two functions and on Onchange it gets the value that is being input into them. But anytime I type into the inputs I can only type a letter at a time before it is unfocused and I believe this is because the component keeps on rendering.
I've also realized that whenever I remove the Onchange and value props then everything goes back to normal and I'm able to type everything easily. How would I fix this?
App.js
import style from "./auth.module.css";
import { useEffect, useRef, useState } from "react";
import { useAuthState } from 'react-firebase-hooks/auth';
import { auth, signInWithEmailAndPassword, signInWithGoogle, registerWithEmailAndPassword } from "../../firebase";
import { CSSTransition } from "react-transition-group";
function Auth() {
const [activeMenu, setActiveMenu] = useState("main");
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [user, loading, error] = useAuthState(auth);
const Emailform = () => {
return (
<div className={style.formBox}>
<label className={style.label}>Email:</label>
<form className={style.input}>
<input
type="email"
name="email"
required="true"
value={email}
onChange={(e) => setEmail(e.target.value)} />
</form>
</div>
);
}
const Passform = () => {
return (
<div className={style.formBox}>
<label className={style.label}>Password:</label>
<form className={style.input}>
<input
type="password"
name="password"
required="true"
value={password}
onChange={(e) => setPassword(e.target.value)} />
</form>
</div>
);
}
let domNode = useClickOutside(() => {
setActiveMenu(false);
});
return (
<div className={style.container}>
<Login />
<Signup />
</div>
);
function AuthType(props) {
return (
<a
href={props.link}
className={style.menuItem}
onClick={() => props.goToMenu && setActiveMenu(props.goToMenu)}
>
{props.children}
</a>
);
}
/* Login */
function Login() {
return (
<CSSTransition in={activeMenu === "main"} unmountOnExit timeout={500}>
<div ref={domNode}>
<div className={style.login}>
<h1 className={style.title}>Clip It</h1>
{/* Email and Password */}
<Emailform/>
<Passform/>
<div className={style.button}>
<input
type="submit"
value="Login"
onClick={() => signInWithEmailAndPassword(email, password)} />
<input
type="submit"
value="Login with Google"
onClick={signInWithGoogle} />
</div>
<div className={style.text}>
<p className={style.plink}>Forgot Password</p>
<div>
Need an account?
<AuthType goToMenu="Signup">click here</AuthType>
</div>
</div>
</div>
</div>
</CSSTransition>
);
}
function Signup() {
return (
<CSSTransition in={activeMenu === "Signup"} unmountOnExit timeout={500}>
<div ref={domNode}>
<div className={style.signUp}>
<div className={style.title}> Clip It</div>
<Form label="First Name" type="text" />
<Form label="Last Name" type="Text" />
<Form label="Email" type="email"/>
<Form label="Date of Birth" type="date" />
<Form label="Password" type="password"/>
<Form label="Confirm Password" type="password" />
<div className={style.button}>
<input type="submit" value="Sign Up" />
</div>
<div className={style.text}>
have an
<AuthType goToMenu="main"> account</AuthType>
</div>
</div>
</div>
</CSSTransition>
);
}
}
let useClickOutside = (handler) => {
let domNode = useRef();
useEffect(() => {
let clickListener = (event) => {
if (domNode.current && !domNode.current.contains(event.target)) {
handler();
}
};
document.addEventListener("mousedown", clickListener);
return () => {
document.removeEventListener("mousedown", clickListener);
};
});
return domNode;
};
function Form(props) {
return (
<div className={style.formBox}>
<label className={style.label}>{props.label}:</label>
<form className={style.input}>
<input
type={props.input}
name={props.input}
required="true" />
</form>
</div>
);
}
export default Auth;
Don't put your component definition inside the other component. If you do it, you create a new component at every render.
You need to work around to move Emailform, Passform to outside of Auth.
Checkout this sandbox to experiment
function Auth() {
// 🚫 the bad place to define components
const Emailform = () => {
...
}
// 🚫 the bad place to define components
const Passform = () => {
...
}
}

Resources