Keyboard hides when using useState in TextInput - reactjs

I have a text input inside the tab view in which I have used setState for updating the value of the input.
export function Login(){
const [email, setEmail] = useState();
return(
<View>
<TextInput
onChangeText={setEmail}
value={email}
/>
</View>
)
}
Now every time I called setEmail on onChangeText input gets hides. Is there anything using which I can prevent the keyboard from getting close?

You need to use the synthetic event listener and set the state to the desired value:
import React from "react";
export default function App() {
const [email, setEmail] = React.useState('');
return (
<>
<input onChange={(e) => setEmail(e.target.value)} value={email} />
</>
);
}

This happens when you define a nested component as in the code below:
const ListMenu = () => {
const [searchText, setSearchText] = useState("");
const Header = () => {
return <TextInput
placeholder="Search"
onChangeText={text => { setSearchText(text) }}
/>
}
return (<View>
<Header />
</View>
)}
instead make nested component like this:
const ListMenu = () => {
const [searchText, setSearchText] = useState("");
const header = () => {
return <TextInput
placeholder="Search"
onChangeText={text => { setSearchText(text) }} />
}
return (<View> {header()} </View>)
}

Related

Why is my test failing to detect that button will enable when form fields are not empty?

I have implemented a login page such that when emailId and password fields are empty it keeps the button disabled and enable the button once both fields are filled with some text.
Here is my code:
Login.tsx:
const Login = () => {
const navigate = useNavigate();
const [error, setError] = useState("");
const [emailId, setEmailId] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
return (
<>
<NavBar variant="secondary" button={<SignupBtn />} />
<Container>
<MuiTextField
fullWidth
label="Email address"
id="outlined-size-small"
size="small"
onChange={(event) => {
setEmailId(event.target.value);
setError("");
}}
/>
<MuiTextField
fullWidth
label="Password"
id="outlined-size-small"
size="small"
onChange={(event) => {
setPassword(event.target.value);
setError("");
}}
/>
{error && (
<Alert variant="outlined" severity="error">
{error}
</Alert>
)}
<Button variant="contained" disabled={!emailId || !password} onClick={(e) => handleLogin({ e, navigate, data: { emailId, password }, setError, setLoading })}>
{loading ? <Progress size={20} /> : "Login"}
</Button>
</Container>
</>
);
};
Here is my test code:
test("button enables when form fields are not empty", async () => {
render(<Login />, { wrapper: BrowserRouter });
const user = userEvent.setup();
const emailTextField = screen.getByLabelText(/email address/i);
const passwordTextField = screen.getByLabelText(/password/i);
const LoginBtn = screen.getByRole("button", { name: "Login" });
await waitFor(async () => {
await user.type(emailTextField, "hello");
await user.type(passwordTextField, "12345");
expect(LoginBtn).toBeEnabled();
});
});
On running the above test logic I get an error as:
Received element is not enabled:
expect(LoginBtn).toBeEnabled();
^
I am trying to learn to test. Please guide me on what I am doing wrong.
You should set a different id to each TextField component. It's important. It will set the relationship between the label and input elements like the below:
<label for="username-input">Username</label>
<input id="username-input" />
So that you can query the input node correctly using screen.getByLabelText('Username').
From the documentation of TextField, we know:
id: The id of the input element. Use this prop to make label and helperText accessible for screen readers.
You don't need to use waitFor.
E.g.
Login.tsx:
import React, { useState } from "react";
import { TextField, Button } from "#mui/material";
import "./styles.css";
export default function Login() {
const [emailId, setEmailId] = useState("");
const [password, setPassword] = useState("");
return (
<div className="App">
<TextField
label="Email address"
id="email"
onChange={(event) => setEmailId(event.target.value)}
/>
<TextField
label="Password"
id="pwd"
onChange={(event) => setPassword(event.target.value)}
/>
<Button variant="contained" disabled={!emailId || !password}>
Login
</Button>
</div>
);
}
Login.test.tsx:
import { render, screen } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import Login from "./Login";
import "#testing-library/jest-dom";
test("button enabling and disabling based on textfield", async () => {
render(<Login />);
const user = userEvent.setup({ document });
const emailTextField = screen.getByLabelText(/email address/i);
const passwordTextField = screen.getByLabelText(/password/i);
const LoginBtn = screen.getByRole("button", { name: "Login" });
await user.type(emailTextField, "hello");
await user.type(passwordTextField, "12345");
expect(LoginBtn).toBeEnabled();
});
package versions:
#mui/material: ^5.11.0
#testing-library/user-event: ^14.4.3
codesandbox

How to write a useComponent custom hook in React?

I want to create a custom hook useComponent which returns a JSX.Element that will be rendered elsewhere.
I have tried this:
import { useState} from 'react';
const useComponent = () => {
const [value, setValue] = useState('');
const c = () => {
return <>
<p>Component</p>
<input value={value} onChane={(e) => setValue(e.target.value)} />
</>
}
return {
c,
value,
}
}
export default function App() {
const {c: C} = useComponent();
return (
<div className="App">
<C />
</div>
);
}
but it does not work. Once I try typing on input, nothing happens.
How can I achieve this ?
I know it might be a bad practice to do such a thing, but the reason I want this is to be able to open a global dialog and pass the c component as children to the <Dialog /> component so I can both render c inside the dialog's body and also have access to the [value, setValue] state. So my use case would be something like:
[EDIT]
I also add the whole logic with dialog:
import { createContext, useContext, useState } from "react";
const Test = ({ value, setValue }) => {
return (
<>
<p>Component</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
};
const useComponent = () => {
const [value, setValue] = useState("");
return {
element: <Test value={value} setValue={setValue} />,
value
};
};
const DialogCTX = createContext({});
export function DialogProvider(props) {
const [component, setComponent] = useState(null);
const ctx = {
component,
setComponent
};
return (
<DialogCTX.Provider value={ ctx }>
{props.children}
</DialogCTX.Provider>
);
}
export const useDialog = () => {
const {
component,
setComponent,
} = useContext(DialogCTX);
return {
component,
setComponent,
}
};
const Dialog = () => {
const { component } = useDialog();
return <div>
<p>Dialog</p>
{component}
</div>
}
const Setter = () => {
const {element, value} = useComponent();
const {setComponent} = useDialog();
return <div>
<p>Setter component</p>
<p>{value}</p>
<button onClick={() => setComponent(element)}>Set</button>
</div>
}
export default function App() {
return <div className="App">
<DialogProvider>
<Setter />
<Dialog />
</DialogProvider>
</div>;
}
As you said you want to return a JSX.Element but you actually returning a new component (a new function) every time your hook runs. So you could achieve your goal if you actually declare your component outside your hook and return the rendered one. Here is a working example:
import { useState } from "react";
const Test = ({ value, setValue }) => {
return (
<>
<p>Component</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
};
const useComponent = () => {
const [value, setValue] = useState("");
return {
element: <Test value={value} setValue={setValue} />,
value
};
};
export default function App() {
const { element } = useComponent();
return <div className="App">{element}</div>;
}

useRef - use ref to handler function inside functional component?

How do you pass / use refs inside a handler function inside a functional component?
import React, { useRef } from 'react';
function RegisterUser() {
const emailInput = useRef(null);
const passwordInput = useRef(null);
const handleClickRegister = () => {
RegisterPersonMutation(email, password, callbackValue => {
emailInput.current.value = ''; // <---------------------this guy
passwordInput.current.value = ''; // <------------------and his friend
});
};
return (
<div className="register-wrap">
<form>
<input type="text" ref={emailInput} />
<input type="password" ref={passwordInput} />
</form>
<button onClick={() => handleClickRegister()}>Register</button>
</div>
);
}
export default RegisterUser;
I would still like to know how to create and use refs inside nested functions using useRef... but in this case, Frank was right in that I should just use useState.
I'm already using it in my real component so it's like this:
function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
handleClick(foo, bar, callback => {
setEmail('');
setPassword('');
});
<input value={email} />
<input value={password} />
}

Focusing the next input after reaching the max length React Native

I want to be able to move the focus from one input onto the next after the user has entered 2 numbers in the input box. Instead of the user manually clicking the next field I'd like the focus to switch there automatically.
Below is how I've currently implemented my two input fields.
I've tried the things mentioned here using refs but doesn't seem to work at all https://www.codegrepper.com/code-examples/csharp/react+native+textinput+focus
<Controller
control={form.control}
defaultValue={initialMonthValue}
name={monthName}
render={({ onChange, onBlur, value }) =>
<Input
containerStyle={{ width:80 }}
editable={!isSubmitting}
autoCapitalize="none"
numberOfLines={1}
multiline={false}
keyboardType='number-pad'
placeholder="MM"
maxLength={2}
placeholderTextColor={Colors.Grey}
onChangeText={onChange}
onBlur={onBlur}
secureTextEntry={false}
invalid={!!monthError}
value={value} />}
/>
<Slash text="/" />
<Controller
control={form.control}
defaultValue={initialYearValue}
name={yearName}
render={({ onChange, onBlur, value }) =>
<Input
containerStyle={{ width:80 }}
editable={!isSubmitting}
autoCapitalize="none"
numberOfLines={1}
multiline={false}
keyboardType='number-pad'
placeholder="YY"
placeholderTextColor={Colors.Grey}
onChangeText={onChange}
onBlur={onBlur}
secureTextEntry={false}
invalid={!!yearError}
value={value} />}
/>
If I understand what are you looking to implement then this should work for you.
import { useRef, useEffect, useState } from "react";
export default function FormInputs() {
const inputRef = useRef(null);
const [length, setLength] = useState(0);
const handleChange = (e) => {
// Handle type checking here
setLength(length + 1);
};
useEffect(() => {
// if the Ref doesn't exist for some reason then break.
if (!inputRef) return;
const input = inputRef.current;
if (length === 2) input.focus();
}, [length]);
return (
<>
<input onChange={handleChange} />
<input ref={inputRef} />
</>
);
}
https://codesandbox.io/s/wonderful-platform-no55n?file=/src/App.js:77-590
if you would love me to help you implement this on your code then don't hesitate to share the components needed for the code to be functional.
You can use TextInput's focus function. Here is a working example:
import React from 'react';
import {SafeAreaView, StyleSheet, TextInput} from 'react-native';
const App = () => {
const [textInputValue1, setTextInputValue1] = React.useState('');
const textInputRef2 = React.useRef<TextInput | null>();
const [textInputValue2, setTextInputValue2] = React.useState('');
return (
<SafeAreaView>
<TextInput
style={styles.textInput}
value={textInputValue1}
onChangeText={(value) => {
if (value.length >= 2) {
textInputRef2.current?.focus(); // Focus on the other TextInput
}
setTextInputValue1(value);
}}
/>
<TextInput
style={styles.textInput}
ref={(ref) => (textInputRef2.current = ref)}
value={textInputValue2}
onChangeText={(value) => setTextInputValue2(value)}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
textInput: {
backgroundColor: 'gray',
margin: 20,
minHeight: 40,
},
});
export default App;
You can focus other Input's ref when you reach maxLength on first input' maxLength prop's value like this:
import React, {useRef} from 'react';
import {View, TextInput} from 'react-native';
const App = () => {
const secondTextInputRef = useRef(null);
const [firstTextInputValue, setFirstTextInputValue] =
React.useState('');
const [secondTextInputValue, setSecondTextInputValue] =
React.useState('');
const maxInputSize = 3;
const App = () => {
return (
<View>
<TextInput
value={firstTextInputValue}
maxLength={maxInputSize}
onChangeText={(value) => {
setFirstTextInputValue(value);
if (value.length >= maxInputSize) {
secondTextInputRef.current?.focus();
}
}}/>
<TextInput
ref={(ref) => (secondTextInputRef.current = ref)}
value={secondTextInputValue}
onChangeText={(value) => setSecondTextInputValue(value)} />
</View>
);
};
export default App;

Is drilling down setSate() through multiple components an anti-pattern in React?

I am new with React development and I wonder if this is considered an anti-pattern to pass down setState hook as props.
My code looks like this:
const App = () => {
const [value, setValue] = useState('')
return (
<Component value={value} helpers={{ setValue }} />
)
}
const Component = (props) => {
return <Component2 {...props} />
}
const Component2 = (props) => {
return <Input {...props} />
}
const Input = (props) => {
const handleChange = (e) => props.helpers.setValue(e.target.value)
return <input onChange={handleChange} {...props} />
}
I am working with React-Hook-Form and I need to declare the state at the top level but I still want my handleChange function to be at the component level. However, I don't see this pattern on example codes.
Is it an anti-pattern?

Resources