change text of array item in React: undefined is not a function - reactjs

I want to setup the onChangeText function for a TextInput which is a child element of an array, but I get 'undefined is not a function (evaluating '_this2.props.updatePhoneNumber(text)')'
I'm not sure which part I've done wrong so I just paste my code here.
The child item part:
class PhoneInput extends React.Component {
render() {
return ( <FormGroup style={{flexDirection: 'row'}}>
<TextInput placeHolder="phone" value={this.props.phone.number}
onChangeText={(text) => {this.props.updatePhoneNumber(text)}}/>
</FormGroup>);
}
}
The father component:
export class RecommendScreen extends React.Component {
state = {
phones: [{number: "", id: 1}, {number: "", id: 2}]
}
constructor(props) {
super(props);
this.updatePhoneNumber = this.updatePhoneNumber.bind(this);
}
updatePhoneNumber(id, number) {
const phones = this.state.phones.slice();
for (let phone of phones) {
if (phone.id == id) {
phone.number = number;
}
}
this.setState({phones: phones});
}
render() {
return (
<Screen styleName="paper">
{this.state.phones.map((phone, i) => (
<PhoneInput phone={phone} key={phone.id}
onChangeNumber={(text) => {this.updatePhoneNumber(phone.id, text)}}
/>
))}
</Screen>
);
}
}
Any ideas?

In the first line, you just need to pass the function to the child component.
export class RecommendScreen extends React.Component {
state = {
phones: [
{
number: "",
id: 1
}, {
number: "",
id: 2
}
]
}
constructor(props) {
super(props);
this.updatePhoneNumber = this.updatePhoneNumber.bind(this);
}
updatePhoneNumber(id, number) {
const phones = this.state.phones.slice();
for (let phone of phones) {
if (phone.id == id) {
phone.number = number;
}
}
this.setState({phones: phones});
}
render() {
return (
<Screen styleName="paper">
{this.state.phones.map((phone, i) => (<PhoneInput
phone={phone}
key={phone.id}
updatePhoneNumber={this.updatePhoneNumber}/>))}
</Screen>
);
}
}
In this component, just call this function and pass the value to it
class PhoneInput extends React.Component {
render() {
return (
<FormGroup style={{
flexDirection: 'row'
}}>
<TextInput
placeHolder="phone"
value={this.props.phone.number}
onChange={(e) => {this.props.updatePhoneNumber(e.target.value)
}}/>
</FormGroup>
);
}
}

It's a name mismatch issue, You are passing the function by different name using by different name, From parent you are passing onChangeNumber and in child you are using updatePhoneNumber that's why it is throwing the error.
Use this:
<PhoneInput
phone={phone}
key={phone.id}
updatePhoneNumber = {(text) => this.updatePhoneNumber(phone.id, text)}}
/>
Or Inside child component use onChangeNumber like this:
<TextInput
placeHolder = "phone"
value = {this.props.phone.number}
onChangeText={(text) => this.props.onChangeNumber(text)}/>

Related

how to make a 'number' component in react native

I want to make a 'number' component which accepts numbers on input.
I tried to make it, but it is not working.
Here the code-
import React, { Component } from 'react';
import { TextInput } from 'react-native';
constructor(props)
{
super(props);
this.state = {
text: ''
};}
handleInputChange = (text) => {
if (/^\d+$/.test(text)) {
this.setState({
text: text
});
}
}
const NumberInput = (props) => {
return (
<TextInput
keyboardType='numeric'
onChangeText={this.handleInputChange}
value={this.state.text}
/>
)
}
export { NumberInput };
You don't have access to this in functional component, you need to define it as class based component,
class NumberInput extends Component{
constructor(props){
super(props);
this.state = {
text: ''
};
}
handleInputChange = (text) => {
if (/^\d+$/.test(text)) {
this.setState({
text: text
});
}
}
render(){
return (
<TextInput
keyboardType='numeric'
onChangeText={this.handleInputChange}
value={this.state.text}
/>
)
}
}
Update
You can also try this,
<TextInput
keyboardType='numeric'
onChange={this.handleInputChange} //onChange instead of onChangeText
value={this.state.text}
/>
And your function should be,
handleInputChange = (e) => {
if (/^\d+$/.test(e.target.value)) {
this.setState({
text: e.target.value
});
}
}
Reference to this change.
Also, you can use Number() function to check if the input is a number. It not, it will return NaN
you should use the class component when to use the constructor and use super or use and used hock function with useState in react
class NumberInput extends Component{
constructor(props){
super(props);
this.state = {
text: ''
};
}
handleInputChange = (text) => {
if (/^\d+$/.test(text)) {
this.setState({
text: text
});
}
}
render(){
return (
<TextInput
keyboardType='numeric'
onChangeText={this.handleInputChange}
value={this.state.text}
/>
)
}
}
or using the following shape when used the function component
import useState from'react'
function NumberInput (){
const [text, setText] = useState('');
handleInputChange = (text) => {
if (/^\d+$/.test(text))(setText(text)) ;
}
}
return (
<TextInput
keyboardType='numeric'
onChangeText={this.handleInputChange}
value={text}
/>
)
}
}

React Native Search filter not working properly

I am trying to implement react native search filter. I have my data array on the parent screen and I applied the search filter on the parent file and passed the filtered data array as props to the child screen. But no filter is happening. I am confused or no clue what wrong i am doing. I have the following codes:
ParentScreen.js
import SearchInput, { createFilter } from 'react-native-search-filter';
...
const KEYS_TO_FILTERS = ['title']
export default class ProductScreen extends Component {
constructor(props) {
super(props)
this.state = {
items: API,
searchTerm: '',
}
}
searchUpdated(term) {
this.setState({ searchTerm: term })
}
render() {
const filteredItems = this.state.items.filter(createFilter(this.state.searchTerm, KEYS_TO_FILTERS))
let cardGridWithBodyProps = {
navigation: this.props.navigation,
api: filteredItems,
gridNumber: 2,
}
return (
<Container>
<ScrollView showsVerticalScrollIndicator={false}>
<View>
<Header>
<SearchInput
onChangeText={(term) => { this.searchUpdated(term) }}
placeholder="Search"
/>
</Header>
</View>
<View>
<CardGridWithBody {...cardGridWithBodyProps} />
</View>
</ScrollView>
</Container>
)
}
}
ChildScreen.js
export default class CardGridWithBody extends Component {
constructor(props) {
super(props)
this.state = {
items: this.props.api
}
}
renderGrid(gridArray) {
return gridArray.map((row, rowIndex) => (
<Row key={rowIndex}>
{row.map((col, colIndex) => (this.renderColumn(col, rowIndex,colIndex)))}
</Row>
));
}
renderColumn(colItem, rowIndex, colIndex) {
return (
<Col key={colIndex}>
<Text>{colItem.title}</Text>
</Col>
)
}
renderContent() {
let gridArray = this.state.items
return this.renderGrid(gridArray)
}
render() {
return (
this.renderContent()
)
}
}
Instead of saving the data in state, access it directly from props. If you save it in state, you'll need to update it manually using lifecycle methods such as shouldComponentUpdate or getDerivedStateFromProps
renderContent = () => {
let gridArray = this.props.api;
return this.renderGrid(gridArray);
}
In parent screen convert
searchUpdated(term) {
this.setState({ searchTerm: term })
}
to
searchUpdated = term => {
this.setState({ searchTerm: term })
}
and in your child component you can do
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.api !== prevState.api) {
return { api: nextProps.api };
}
return null;
}
componentDidUpdate(prevProps, prevState) {
if (
this.state.items !==
prevState.items
) {
this.setState({ items: api });
}
}

How to handle input change for semantic-ui-react Dropdown object in a form without creating multiple handlers?

the semantic-ui-react Dropdown object does not accept a name or id attribute, and therefore the change cannot be handled in the same way as other form elements. the docs show this:
import React, { Component } from 'react'
import { Dropdown, Grid, Segment } from 'semantic-ui-react'
const options = [
{ key: 1, text: 'One', value: 1 },
{ key: 2, text: 'Two', value: 2 },
{ key: 3, text: 'Three', value: 3 },
]
export default class DropdownExampleControlled extends Component {
state = {}
handleChange = (e, { value }) => this.setState({ value })
render() {
const { value } = this.state
return (
<Grid columns={2}>
<Grid.Column>
<Dropdown
onChange={this.handleChange}
options={options}
placeholder='Choose an option'
selection
value={value}
/>
</Grid.Column>
<Grid.Column>
<Segment secondary>
<pre>Current value: {value}</pre>
</Segment>
</Grid.Column>
</Grid>
)
}
}
when combining inputs into a single event handler, there's no tidy way to pull out an identifier to update the state for the dropdown. how is this normally handled?
thanks
One option is to use a simple wrapper(not unnecessary bloated) over different input controls, so that even if we change a control library we will have limited change scope. Below is simple example of such wrapper, and shows a simple approach to use same value change handler for multiple fields (even for different type of input controls):
import React, { Component } from 'react';
import { render } from 'react-dom';
const FIELD_NAMES = {
FirstName: 'FirstName',
LastName: 'LastName',
};
const TEXT_CONTAINER_STYLE = { padding: 5 };
function MyTextInput(props) {
const {
name,
onChange,
value,
} = props;
function handleValueChange(e) {
onChange(name, e.target.value);
}
return (
<div style={TEXT_CONTAINER_STYLE}>
<input onChange={handleValueChange} value={props.value} />
</div>
);
}
class App extends Component {
constructor() {
super();
this.state = {
};
this.state[FIELD_NAMES.FirstName] = '';
this.state[FIELD_NAMES.LastName] = '';
}
handleValueChange = (fieldName, fieldValue) => {
if (fieldName) {
let newState = {};
switch (fieldName) {
case FIELD_NAMES.FirstName:
newState[FIELD_NAMES.FirstName] = fieldValue;
break;
case FIELD_NAMES.LastName:
newState[FIELD_NAMES.LastName] = fieldValue;
break;
}
this.setState(newState);
}
}
getFieldValue = (fieldName) => {
return this.state[fieldName]
}
render() {
return (
<div>
<MyTextInput
name={FIELD_NAMES.FirstName}
value={this.getFieldValue(FIELD_NAMES.FirstName)}
onChange={this.handleValueChange}
/>
<MyTextInput
name={FIELD_NAMES.LastName}
value={this.getFieldValue(FIELD_NAMES.LastName)}
onChange={this.handleValueChange}
/>
<div>
{`First Name : ${this.getFieldValue(FIELD_NAMES.FirstName)}`}
</div>
<div>
{`Last Name : ${this.getFieldValue(FIELD_NAMES.LastName)}`}
</div>
</div >
);
}
}
render(<App />, document.getElementById('root'));
Working example

redux-form always returns same values for Multiselect react-widget

I am trying to use redux-form with react-widget Multiselect this example:
var Multiselect = ReactWidgets.Multiselect
, people = listOfPeople();
var Example = React.createClass({
getInitialState() {
return { value: people.slice(0,2) };
},
_create(name){
var tag = { name, id: people.length + 1 }
var value = this.state.value.concat(tag)
// add new tag to the data list
people.push(tag)
//add new tag to the list of values
this.setState({ value })
},
render(){
// create a tag object
return (
<Multiselect data={people}
value={this.state.value}
textField="name"
onCreate={this._create}
onChange={value => this.setState({ value })}/>
)
}
});
ReactDOM.render(<Example/>, mountNode);
Below is a code snippet for a parent component which makes usage of redux-form (EditVideo component) component (please look at the comments in onSubmit method):
class VideoEdit extends React.Component {
constructor(props) {
super(props);
}
onSubmit = (values) => {
console.log(values.categories) // always returns initialValues for categories, new values not adding
}
render() {
const { loading, videoEdit, categories } = this.props;
if (loading) {
return (
<div>{ /* loading... */}</div>
);
} else {
return (
<div>
<h2>Edit: {videoEdit.title}</h2>
<EditVideo
onSubmit={this.onSubmit}
initialValues={videoEdit}
categories={categories}
/>
</div>
);
}
}
}
And here is a code snippet of redux-form component with react-widget Multiselect component:
class CategoryWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.defValue,
extData: this.props.data
}
this._create = this._create.bind(this);
}
_create(name) {
var tag = { name, id: this.state.extData.length + 100 + 1 }
var value = this.state.value.concat(tag)
var extData = this.state.extData.concat(tag)
this.setState({
extData,
value
})
}
render() {
return (
<Multiselect
{...this.props.input}
data={this.state.extData}
onBlur={() => this.props.input.onBlur()}
value={this.state.value || []}
valueField="id"
textField="name"
onCreate={this._create}
onChange={value => this.setState({ value })}
/>
)
}
}
const EditVideoForm = (props) => {
const { handleSubmit, submitting, onSubmit, categories, initialValues, defBook } = props;
return (
<Form name="ytvideo" onSubmit={handleSubmit(onSubmit)}>
<div>
<Field
name="categories"
component={CategoryWidget}
data={categories}
defValue={initialValues.categories}
/>
</div>
<br />
<Button color="primary" type="submit" disabled={submitting}>
Submit
</Button>
</Form>
);
};
export default reduxForm({
form: 'videoEdit',
enableReinitialize: true
})(EditVideoForm);
The Multiselect widget works as expected, yet the form on submit always returns the same initial values for categories.
I believe the problem lays in the fact that CategoryWidget is a class base component? If so, what is a way to make it work?
Here is what I have done for my Multiselect at the end:
class CategoryWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.defValue,
extData: this.props.data
}
this._create = this._create.bind(this);
}
_create(name) {
var tag = { name, id: this.state.extData.length + 100 + 1 }
var value = this.state.value.concat(tag)
var extData = this.state.extData.concat(tag)
this.setState({
extData,
value
})
}
componentDidUpdate() {
let { onChange } = this.props.input
onChange(this.state.value)
}
handleOnChange(value) {
this.setState({ value })
}
render() {
const input = this.props.input
return (
<Multiselect
{...input}
data={this.state.extData}
onBlur={() => input.onBlur()}
value={this.state.value || []}
valueField="id"
textField="name"
onCreate={this._create}
onChange={value => this.handleOnChange(value)}
/>
)
}
}

How to pass a component to another component in React?

I want to write a tab component like below,it's ok when I click tab1 to show the content of test,but when I define a class component like test2, it seems wrong.And the erro is 'Uncaught TypeError: Cannot call a class as a function'.Thanks so much.
// use of component
const tabs = [
{tabName: 'tab1', content: () => <Tab.Pane component={test} /> },
{tabName: 'tab2', content: () => <Tab.Pane component={test2} /> }
];
const test = () => {
return (<HGroup>tetetetetetet</HGroup>)
};
class test2 extends Component {
render() {
return(<HGroup>123213</HGroup>)
}
}
#observer
export default class EditLabels extends Component {
render() {
return (<Tab panes={tabs} />)
}
}
// component
#observer
class Tab extends Component {
#observable activeIndex = 0;
static Pane = ({children, component}) => {
console.log(typeof component);
return (
<VGroup>
{children && <HGroup>{children}</HGroup>}
{component && component.call(this)}
</VGroup>
)
};
render() {
const { panes } = this.props;
return (
<VGroup>
<HGroup>{ panes.map((item, index) => {
return (<HGroup key={index}
onClick={() => this.activeIndex = index}
style={this.activeIndex === index && {color: 'red'}}>{item.tabName}</HGroup>)
})}
</HGroup>
{panes[this.activeIndex].content()}
</VGroup>
)
}
}

Resources