React Native putting different User Inputs in one Object - javascript

I am trying to put together a simple Question Answer Quiz with React Native. That said I want to store both the question and the answer in one object.
Currently I am running into trouble with the following codepieces:
import React, { useState, useEffect } from 'react'
import {
StyleSheet,
Button,
View,
Text,
TextInput,
TouchableOpacity
} from 'react-native'
const Test = () => {
const[card, setValues] = useState({
title: '',
question: '',
answer: ''
})
const updateField = e => {
setValues({
...card,
[e.target.name]: e.target.value
});
};
<TextInput
placeholder='Kartentitel eingeben..'
name = "title"
value={card.title}
onChangeText={updateField}>
</TextInput>
<TextInput
placeholder='Fragestellung eingeben..'
name = "question"
value={card.question}
onChangeText={updateField}>
</TextInput>
<TextInput
placeholder='Antwortmöglichkeit eingeben..'
name = "answer"
value={card.answer}
onChangeText={updateField}>
</TextInput>
}
export default Test;
However I get the following Error
ERROR TypeError: undefined is not an object (evaluating 'e.target.value')
error
I've got the idea behind this from react(not react native) posts and tutorials.
Basically what this is trying to do is to get each corresponding UserInput value and put it into the object target(this is what the updateField function should do)
Any help would really appreciated
Richard

The error happens because e is not an HTMLInputChange event, I think that the onChangeText functions expects a functions with just a string argument.
You could wrapp the updateField function with another function that expects a name something like this:
const updateField = (name) => (value) =>{
setValues({
...card,
[name]: value
});
}
And use it like this
<TextInput ... onChangeText={updateField("title")} />
This should work, although I recommend using something like Formik to make form validations and handle the value changes.

Difference between onChangeText and onChange is that the argument on onChangeText is the actual value only whereas the argument on onChange is the event itself. So try replacing onChangeText with onChange.
<TextInput
placeholder='Antwortmöglichkeit eingeben..'
name = "answer"
value={card.answer}
onChange={updateField}>
</TextInput>

Related

How to test TextInput in react-native with #testing-library/reat-native?

I'm trying to test a TextInput by checking its value after entering a string, but it's not working. Any help would be appreciated.
Here's the code:
import React from 'react'
import { TextInput } from 'react-native'
import { fireEvent, render } from "#testing-library/react-native"
test('<TextInput/>, () => {
const { getByTestId } = render( <TextInput testID="input" /> );
const input = getByTestId("input");
fireEvent.changeText(input, "123");
expect(input.value).toBe("123");
})
The test fails with the message:
Expected: "123"
Received: undefined
I think your example is not working because you are not controlling the input. Try adding a value and an onChangeText function. Something like:
function Example() {
const [value, setValue] = useState('')
return <TextInput value={value} onChangeText={setValue} testID="input" />
}
test('Should apply the value when changing text', () => {
const { getByTestId } = render(<Exampler />);
const input = getByTestId('input');
fireEvent.changeText(input, '123');
expect(input.props.value).toBe('123');
});
Also, you need to check input.props.value instead of input.value
Hope it helps.
I'm going to suggest a few things to assist you to get to the solution,
Are you sure TextInput has a prop called testID? You use getByTestId subsequently, which needs a data-testid value on the element. So please make sure of this first.
You can try finding the element in another way. Probably use getByPlaceholderText or similar.
Once you can get the element properly, and after the fireEvent, the value should certainly be updated, and assertion would succeed.

Custom input component field data not updating after submit in react-hook-form

I'm building a basic custom component for input masking (rolling my own!), which returns an input field for a form.
import React, { useState, useEffect, useRef } from "react";
import mask from "./mask";
const InputMask = props => {
let {
field,
register,
type,
inputMaskType,
defaultValue
} = props;
const inputField = useRef();
const [fieldValue, setFieldValue] = useState("");
const onInputLoad = () => {
setFieldValue(mask.maskShortDateOnLoad({
inputField,
defaultValue
}));
};
const onInputChange = () => {
setFieldValue(mask.maskInput({
type: inputMaskType,
inputField,
defaultValue
}));
};
useEffect(() => {
onInputLoad();
}, []);
return (
<>
<input
{...register(field)}
ref={inputField}
type={type}
onChange={onInputChange}
value={fieldValue || ""}
/>
</>
);
};
export default InputMask;
Used like so, in the parent component, in the form:
<InputMask
type="text"
field="from_date"
register={register}
inputMaskType={inputMaskType.SHORT_DATE}
defaultValue={selectedEntity.from_date}
/>
It's binding to the field when the form loads, because the registered field is reading the data in. It shows up. On save, however, the custom field's data is not updated. It remains the same as it was when first loaded.
Looking at it in dev tools, it has the correct "name" property on the tag, and it looks like all the others in the form.
I guess I don't get it! It's an input, nested in another component, and it's registered like every other input in the form. I'm passing register down and doing what I'm supposed to. Is the state change reloading w/ the old data? Am I going to be forced to use a Controller? If so...how in this case? What am I doing wrong?

Can't figure out why React code doesn't work

I've built a custom Input component which is simply a wrapper for the HTML input element. Here's the critical code, which I've simplified for posting here:
// #flow
import React, { useState } from 'react';
type Props = {
value?: string,
onChange: Function
};
const Input = ((props: Props) => {
const [ currentValue, setCurrentValue ] = useState(!!props.value ? props.value : '');
const handleChange = (event: SyntheticInputEvent<EventTarget>) => {
setCurrentValue(event.target.value);
props.onChange(event);
};
return <input type='text'
value={currentValue}
onChange={handleChange} />;
});
I wrote a bunch of React Testing Library tests for this and they all pass fine. But when I implemented this component in a web page, the initial value failed to appear. I solved the problem by dropping the currentValue code and just using props.value instead. That solves it. But I'm most curious why this approach above fails to display the initial value.
Look at this code, I did use prop-types

How do props work when used by a component declared inside another component?

I am making a multi-stage form in react. The overall component dependency structure is as follows:
MainForm
SubForm1
SubForm2
SubForm3
The MainForm component has two states called step and formData, and methods called handleNext, handleBack which modify the state step. It also has a method called handleChange, which reads the value from input fields present in SubForm* and update the state formData, so that on clicking back and next the formData stays there until a final API call has been made on the last SubForm3. Upon which the MainForm component is unmounted. The MainForm uses switch case to render a particular SubForm using the state step as the decision variable.
I am passing the following to SubForms as props:
formData
handleNext
handlePrev
handleChange
In SubForm1 I have the following piece of code:
import React from 'react';
const SubForm1 = ({
formData,
handleNext,
handlePrev,
handleChange,
}) => {
const FormInput = ({ attr }) => <input name={attr} onChange={handleChange} value={formData[attr]} />;
return (
<FormContainer>
<form>
<input name='fullName' onChange={handleChange} value={field_values.fullName} />
<FormInput attr="employee_id" />
</form>
<button onClick={prevStep}>Back</Button>
<button onClick={nextStep}>Next</button>
</FormContainer>
);
}
The handleChange method captures the user input via the onChange event and upadates the corresponding field. It is declared in MainForm.jsx as:
// MainForm.jsx
import React, { useState } from 'react';
const MainForm = () => {
const [step, setStep] = useState(0);
const [formData, setFormData] = useState({ fullName: '', employee_id: '' });
const handleChange = (event) => {
event.preventDefault();
event.persist();
setFormData(prevState => ({
...prevState,
[event.target.name]: event.target.value,
}));
};
const handleNext = () => {
setStep(old => (old + 1));
};
const handleBack = () => {
setStep(old => (old - 1));
};
It works smoothly in the input field (fullName) - The value updates as the user types and the formData state remains intact on traversing through subforms.
But for the employee_id input, following happens:
A character is entered in the employee_id input field.
The employee_id input field immediately looses focus.
The user has to click on the employee_id input field again to gain focus (of course without which he/she cannot type into the field)
Go To step 1.
The state formData is however updated since on going to next/previous SubForm, the state formData remains intact.
I feel that it has something to do with how I have declared the FormInput component. I am not passing handleChange as a prop to it from SubForm1, but instead relying on the scope.
After googling a lot, I couldn't get an answer for my question, since search engines confused my question with answers relating to Component composition on any query containing 'declaring a component inside another component` etc.
PS. I am relatively new to react and javascript, so please feel free to suggest any better ways to implement the above.
Any help is highly appreciated.
sometimes event.persist(); is causing problem.
If you want your event.target.name and event.target.value then just create two variables which will store these values.
and remove event.persist().
just try with below code :
const handleChange = (event) => {
event.preventDefault();
// event.persist(); //comment out this line
const name = event.target.name;
const value = event.target.value;
setFormData(prevState => ({
...prevState,
[name]: value,
}));
};

Modularizing code in React/Redux

The main question
I am used to using React with ES6 classes. I am also used to modularizing portions of code into separate functions. I am looking at the following example and trying to figure out how to put the value for onSubmit as a separate function.
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form
onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}
>
<input
ref={node => {
input = node
}}
/>
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
AddTodo = connect()(AddTodo)
export default AddTodo
I have tried something like this:
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
function handleSubmit(e){
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => handleSubmit(e)}>
<input ref={node => {input = node }}
/>
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
AddTodo = connect()(AddTodo)
export default AddTodo
But then of course it does not work as it does not recognize the input variable. I could pass the input variable to the function, but this does not seem like the right way to do it.
Question 2:
I am unfamiliar with what the following piece of code is doing:
let AddTodo = ({ dispatch }) => {
Where exactly is it getting dispatch from? Is the value of dispatch being passed into the anonymous function?
Question 3
The same with the following code:
<input ref={node => {input = node }}
Where is the value of node coming from and why is it being stored into the input variable?
Answer to Question 1
AddTodo is a React stateless functional component (SFC). It is also a function. Within the SFC is defined a variable input. In order for the handleSubmit callback to be able to make use of input, it is necessary that input be in the enclosing scope where handleSubmit is defined or input be passed as an argument to handleSubmit.
Thus, the following two implementations achieve the desired behavior:
const AddTodo = ({dispatch}) => {
let input
const handleSubmit = e => {
...
}
return (
...
onSubmit={handleSubmit}
...
)
and
const handleSubmit = (e, input) => {
...
}
const AddTodo = ({dispatch}) => {
let input
return (
...
onSubmit={e => handleSubmit(e, input)}
...
)
I highly recommend reading the following blog post by Kent Dodds, paying particular attention to the use of classes vs function closures.
Classes, Complexity, and Functional Programming
Answer to Question 2
The connect function from react-redux wraps the AddTodo component. The way in which connect is being called (with no second argument, or any arguments in this particular case) means AddTodo will receive a prop named dispatch.
To better understand how react-redux and the connect function it provides work, have a look at the documentation:
https://github.com/reactjs/react-redux/blob/master/docs/api.md
Answer to Question 3
Refs are built into React. A function passed to the ref prop receives the underlying DOM element as an argument. In this case, the function passed to the ref prop stores a reference to the DOM element in the variable input. This allows the DOM element to be accessed and mutated later by the callback passed to onSubmit (i.e. handleSubmit). See the React documentation for more details on refs:
https://reactjs.org/docs/refs-and-the-dom.html

Categories