I have two forms: <Login /> and <Register />. Both have username and password and the register form also has a name field. Also there is a login button and a register button. The common code they share though is just the username and password so I want to refactor my code to have a component <UsernamePassord /> and use it in both <Login /> and <Register />.
What I have done so far (pseudocode):
class UsernamePassword extends React {
constructor() {
this.state = {username: "", password: ""}
}
render() {
return <div>
<input username onChange => setState(username: e.value)
<input password onChange => setState(password: e.value)
</div>
}
class Login extends UsernamePassword {
render() {
return <>
{ super.render() }
<button onClick => this.state.username && this.state.password && signIn() />
}
class Register extends UsernamePassword {
constructor() {
this.state = {
...this.state,
name: ""
}
}
render() {
return <>
<input name onChange => setState(name: e.value)
{ super.render() }
<button onClick => this.state.username && this.state.password && this.state.name && signUp() />
}
I don't like this code at all. It seems really difficult to scale. How can this be done cleaner using composition ?
There are a number of ways to handle this. You could create a component that accepts props (in this case, username and password) and handles the manipulation of values. It subsequently fires an onChange event of some sort that notifies the parent of the change and the parent can manage the state. Alternatively, you could manage the state in the component and just create an event handler prop for alerting parent components of the state. I made a working example of the second scenario based off of your code (changing class-based components for functional ones for simplicity):
import React, { useState } from "react";
import "./styles.css";
const UsernamePassword = ({ onChange }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleChange = () => onChange({ username, password });
return (
<div>
<input value={username} onChange={(e) => setUsername(e.target.value)} />
<input value={password} onChange={(e) => setPassword(e.target.value)} />
<button onClick={handleChange}>Click me!</button>
</div>
);
};
const Login = () => {
const onChange = (value) => console.log("Login", value);
return (
<>
<h2>Login</h2>
<UsernamePassword onChange={onChange} />
</>
);
};
const Register = () => {
const onChange = (value) => console.log("Register", value);
return (
<>
<h2>Register</h2>
<UsernamePassword onChange={onChange} />
</>
);
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Register />
<Login />
</div>
);
}
Related
Here is my code: I have created the input component and header component. if I comment on the useImperativeHandle hook, input focus is working fine. please check this code.
import { forwardRef, useImperativeHandle, useRef } from "react";
const Input = forwardRef((props, ref) => {
// here is useImperativeHandle
useImperativeHandle(ref, () => ({
// ```method```
someExposedProperty: () => {
alert("inside the exposed property function!");
}
}));
// Return statement
return (
<>
<input type="text" ref={ref} />
</>
);
});
// header component
export default function Header() {
const inputRef = useRef();
return (
<>
<div className="container">
<Input ref={inputRef} />
</div>
<button
// Click function.
onClick={() => {
inputRef.current.focus();
}}
>
Add focus
</button>
</>
);
}
You need two refs here. One to bind the functions (which are going to expose to the outside) and another to keep a reference to the input element (this one is only used inside the Input and not exposed to outside components).
Try like below:
import { forwardRef, useImperativeHandle, useRef } from "react";
const Input = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(
ref,
() => ({
focus: () => {
inputRef.current.focus();
}
}),
[]
);
return (
<>
<input type="text" ref={inputRef} />
</>
);
});
export default function Header() {
const inputRef = useRef();
return (
<>
<div className="container">
<Input ref={inputRef} />
</div>
<button
onClick={() => {
inputRef.current.focus();
}}
>
Add focus
</button>
</>
);
}
Working Demo
I have this simplified structure:
<Page>
<Modal>
<Form />
</Modal>
</Page>
All of these are functional components.
And in <Modal /> I have a close function that looks like this:
const close = () => {
// apply a CSS class - so the modal disappears animatedly ..
// then use setTimeout() to completely remove the modal after the animation ends ..
}
Do you have an idea how the <Page /> component can call the <Modal /> close method? And the page has to do it because this is where I'm doing the call to API with the data from the form, and so if all is OK with API request - close the modal.
(The <Form /> handles only the form validation but then passes the data to <Page /> where all the business logic happens.)
PS: The project uses Typescript ... so I have to deal with types as well :(
I look into your problem. I think my example should clarify your problem. Let me know if you have any questions.
import { ReactNode, useCallback, useEffect, useState } from 'react'
import { render } from 'react-dom'
function App() {
return (
<div>
<Page />
</div>
)
}
function Page() {
const [isModalOpen, setModalOpen] = useState(false)
const handleFormSubmit = useCallback((formValues: FormValues) => {
console.log({ formValues })
setModalOpen(false)
}, [])
return (
<div>
<button onClick={() => setModalOpen(!isModalOpen)}>Toggle modal</button>
<Modal isOpen={isModalOpen}>
<Form onSubmit={handleFormSubmit} />
</Modal>
</div>
)
}
interface ModalProps {
isOpen: boolean
children: ReactNode
}
function Modal(props: ModalProps) {
const [isOpen, setIsOpen] = useState(false)
const close = () => {
setIsOpen(false)
}
const open = () => {
setIsOpen(true)
}
useEffect(() => {
if (!props.isOpen && isOpen) close()
if (props.isOpen && !isOpen) open()
}, [props.isOpen])
if (!isOpen) return null
return <div className="modal">{props.children}</div>
}
interface FormProps {
onSubmit: (formValues: FormValues) => void
}
interface FormValues {
username: string
password: string
}
function Form(props: FormProps) {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
return (
<form
onSubmit={e => {
e.preventDefault()
props.onSubmit({
username,
password
})
}}
>
<input
type="text"
placeholder="username"
onChange={e => {
setUsername(e.target.value)
}}
/>
<input
type="text"
placeholder="password"
onChange={e => {
setPassword(e.target.value)
}}
/>
<button type="submit">Submit</button>
</form>
)
}
render(<App />, document.getElementById('root'))
I assumed you are fresh in FE or React world. Propably you do not need that much-nested structure.
There is a special hook in React called useImperativeHandle. You can use it to call child's functions from parent.
You can find out more in the oficcial React documentation.
example of usage
Child Component
Needs to be wrapped into forwardRef like that:
export const ChildComponent = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
async functionName() {
await someLogic();
},
}));
Parent Component
In parent component you need to pass ref to the child.
Then you can use child's function this way:
const childRef = useRef(null)
childRef.current.functionName()
I am trying to send my dob data from my Main class to a child component (RegisterAccount.jsx) and validate it at child component using yup and withFormik Field. The problem is that:
I could not make yup or formik/yup to validate dob field.
DatePicker doesn't show selected value inside the textbox after selected.
Please check my below code:
Here is my Main.jsx class
// Some imports were removed to keep everything looks cleaner
import RegisterAccount from "RegisterAccount.jsx";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
dob: ""
}
}
render() {
return (
<Container fluid>
<Switch>
<Route exact path="/" render={() => <RegisterAccount data={this.state.dob} />} />
</Switch>
</Container>
)
}
}
export default Main;
Here is my RegisterAccount.jsx
// Some imports were removed to keep everything looks cleaner
import { Form as FormikForm, Field, withFormik } from "formik";
import * as Yup from "yup";
import DatePicker from "react-datepicker";
const App = ({ props }) => (
<FormikForm className="register-form " action="">
<h3 className="register-title">Register</h3>
<Form.Group className="register-form-group">
<DatePicker
tag={Field}
selected={props.data.dob}
onChange={(e, val) => {
console.log(this);
this.value=e;
props.data.dob = e;
console.log(props.data.dob);
}}
value="01-01-2019"
className="w-100"
placeholderText="BIRTH DATE"
name="dob" />
{touched.username && errors.username && <p>{errors.username}</p>}
</Form.Group>
</FormikForm>
);
const FormikApp = withFormik({
mapPropsToValues({ data }) {
return {
dob: data.dob || ""
};
},
validationSchema: Yup.object().shape({
dob: Yup.date()
.max(new Date())
})(App);
export default FormikApp;
Use setFieldValue method from formik's injected props.
Define it on onChange handler for your inputsetFieldValue('dob','Your Value').
You can access it from
const MyForm = props => {
const {
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
setFieldValue
} = props;
return (
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={e => {
setFieldValue("name", "Your value"); // Access it from props
}}
onBlur={handleBlur}
value={values.name}
name="name"
/>
</form>
);
};
const MyEnhancedForm = withFormik({
// your code
})(MyForm)
Reference - https://jaredpalmer.com/formik/docs/api/formik#setfieldvalue-field-string-value-any-shouldvalidate-boolean-void
I need to have the user edit the url the "path" appears in the input of the other component and when searching the input change the url in real time. How can I use "withRouter" to perform this function?
// App.js component
class App extends Component {
render() {
const { match, location, history } = this.props;
return (
<div >
<Search
searchString={location.pathname}
/>
</div>
)
}
}
export default withRouter(App)
//Search.js component
const Search = ({ searchString }) => (
<div>
<input
value={searchString}
type="search"
placeholder="Search"
aria-label="Search"
/>
</div>
)
This should work for you although I'm not sure redirecting user to another page while typing into search input is a good UX.
// App.js component
class App extends Component {
state = {
searchString: this.props.location.pathname
}
handleSearch = (event) => {
this.setState(
{ searchString: event.target.value },
() => this.props.history.push('/search?query=' + this.state.searchString)
);
}
render() {
return (
<div >
<Search
onChange={this.handleSearch}
searchString={this.state.searchString}
/>
</div>
)
}
}
export default withRouter(App)
const Search = ({ searchString, onChange }) => (
<div>
<input
onChange={onChange}
value={searchString}
type="search"
placeholder="Search"
aria-label="Search"
/>
</div>
)
How to clear the materialUI textfield value in react?
Check the below code -
<TextField
hintText=""
ref={(node) => this._toField = node}
onChange={this.changeToText}
floatingLabelText="To*"
floatingLabelFixed={true}
fullWidth={true}
/>
I'm using the raisedButton while pressing it validate the above field. If the field has error then displaying the error message. If not, then we need to clear the input. But how can we clear the input text?
if you are using a stateless functional component then you can use react hooks.
Also make sure you are using inputRef
import React, { useState, useRef } from "react";
let MyFunctional = props => {
let textInput = useRef(null);
return (
<div>
<Button
onClick={() => {
setTimeout(() => {
textInput.current.value = "";
}, 100);
}}
>
Focus TextField
</Button>
<TextField
fullWidth
required
inputRef={textInput}
name="firstName"
type="text"
placeholder="Enter Your First Name"
label="First Name"
/>
</div>
);
};
There is a value property that you have to pass to the TextField component.
check example below:
class SomeComponent extends Component {
state = {value: ''}
resetValue = () => {
this.setState({value: ''});
}
render() {
return (
<div>
<TextField
...
value={this.state.value}
/>
<button onClick={this.resetValue}>Reset</button>
</div>
)
}
}
try this
import { Button, Container, InputBase } from '#material-ui/core'
import React, { useState } from 'react'
const ClearText = ()=> {
const [text , setText] = useState("")
const clearTextField = () => setText("")
return (
<Container>
<InputBase
value={text ? text : ""}
onChange={(e)=>setText(e.target.value)}
/>
<Button onClick={clearTextField} > Clear </Button>
</Container>
)
};
export default ClearText;
You need to, somehow, store the input's value. State seems to be an initial approach in this case. Whenever the text changes, you have to update the state. Same applies when you click the button and click the input's value afterwards:
class App extends React.Component {
constructor() {
super()
this.state = {
value: ''
}
this.handleChange = this.handleChange.bind(this)
this.handleClick = this.handleClick.bind(this)
}
handleChange(event) {
this.setState({ value: event.target.value })
}
handleClick() {
// validation...
this.setState({ value: '' })
}
render() {
return (
<div>
<input type="text" value={this.state.value} onChange={this.handleChange}/>
<button onClick={this.handleClick}>Click-me</button>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>