I'm recreating this module in my app. I'm using AntDesign.
But I want to have a duplicate function of it and getting the values that has been filled also.
Here's my code but it's not working
Link
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Form, Input, Icon, Button } from "antd";
let id = 0;
class DynamicFieldSet extends React.Component {
remove = k => {
const { form } = this.props;
// can use data-binding to get
const keys = form.getFieldValue("keys");
// We need at least one passenger
if (keys.length === 1) {
return;
}
// can use data-binding to set
form.setFieldsValue({
keys: keys.filter(key => key !== k)
});
};
add = () => {
const { form } = this.props;
// can use data-binding to get
const keys = form.getFieldValue("keys");
const nextKeys = keys.concat(id++);
// can use data-binding to set
// important! notify form to detect changes
form.setFieldsValue({
keys: nextKeys
});
};
duplicate = k => {
const { form } = this.props;
const keys = form.getFieldValue("keys");
form.setFieldsValue({
keys: keys.find(key => key === k)
});
};
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
const { keys, names } = values;
console.log("Received values of form: ", values);
console.log("Merged values:", keys.map(key => names[key]));
}
});
};
render() {
const { getFieldDecorator, getFieldValue } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 }
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 20 }
}
};
const formItemLayoutWithOutLabel = {
wrapperCol: {
xs: { span: 24, offset: 0 },
sm: { span: 20, offset: 4 }
}
};
getFieldDecorator("keys", { initialValue: [] });
const keys = getFieldValue("keys");
const formItems = keys.map((k, index) => (
<Form.Item
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
label={index === 0 ? "Passengers" : ""}
required={false}
key={k}
>
{getFieldDecorator(`names[${k}]`, {
validateTrigger: ["onChange", "onBlur"],
rules: [
{
required: true,
whitespace: true,
message: "Please input passenger's name or delete this field."
}
]
})(
<Input
placeholder="passenger name"
style={{ width: "60%", marginRight: 8 }}
/>
)}
{keys.length > 1 ? (
<div>
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
onClick={() => this.remove(k)}
/>
<Icon
className="dynamic-delete-button"
type="copy"
onClick={() => this.duplicate(k)}
/>
</div>
) : null}
</Form.Item>
));
return (
<Form onSubmit={this.handleSubmit}>
{formItems}
<Form.Item {...formItemLayoutWithOutLabel}>
<Button type="dashed" onClick={this.add} style={{ width: "60%" }}>
<Icon type="plus" /> Add field
</Button>
</Form.Item>
<Form.Item {...formItemLayoutWithOutLabel}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
}
}
const WrappedDynamicFieldSet = Form.create({ name: "dynamic_form_item" })(
DynamicFieldSet
);
ReactDOM.render(
<WrappedDynamicFieldSet />,
document.getElementById("container")
);
In your duplicate method you get the key or index of the field that was clicked. The keys array in form values tracks the number of fields you have and their indices. The names array in form values tracks the value for each of those fields. In your duplicate method you need to add one field by adding to keys array then set value for that field by adding to names array. See the method below:
Note: In your add method use keys.length instead of id to increment. This will always keep all keys unique
add = () => {
const { form } = this.props
// can use data-binding to get
const keys = form.getFieldValue("keys")
const nextKeys = keys.concat(keys.length)
// can use data-binding to set
// important! notify form to detect changes
form.setFieldsValue({
keys: nextKeys
})
}
duplicate = k => {
const { form } = this.props
// We are using keys to track number of fields
// and using names to track the value of each field
const { keys, names } = form.getFieldsValue()
// We can use the key to access the value of the field for
// for which the button was clicked
const nameToDuplicate = names[k]
/**
* Add a key to 'keys' array in antd form to render extra field
* Once the field is rendered we can add the name to the
* 'names' array of antd form.
*
* Note: We add keys first and then add name in callback method because
* we cannot set a value for a form field before rendering it.
*/
const newKeys = [...keys, keys.length]
form.setFieldsValue({ keys: newKeys }, () => {
const newNames = [...names, nameToDuplicate]
form.setFieldsValue({ names: newNames })
})
}
Sandbox Link
Related
I want to create a contact form where the form fields are changed after they are filled.
For example when you fill the email field, you press enter or click the Next button it disappears and the next field appears. There will be different form field types (text, email, select, etc).
What i want to achieve is based on this form, but it is coded in vanilla js and i want to write it in React from scratch.
For now my basic code is this:
ContactForm.js file:
import { useState, useEffect } from "react";
import Fields from "./Fields";
const ContactForm = ({ fields }) => {
const [current, setCurrent] = useState(0);
const [show, setShow] = useState(true);
return (
<div className='container'>
<div className='fs-form-wrap' id='fs-form-wrap'>
<Fields fields={fields} isCurrent={current} />
<button onClick={() => setCurrent(current + 1)}>Continue</button>
</div>
</div>
);
};
Fields.js file:
import { useState } from "react";
import Field from "./Field";
const Fields = ({ fields, isCurrent }) => {
return (
<form id='myform' className='fs-form fs-form-full'>
<ol className='fs-fields'>
{fields.map((field, index) => {
return (
<Field
placeHolder={field.placeholder}
title={field.title}
type={field.type}
key={index}
isCurrent={isCurrent}
/>
);
})}
</ol>
</form>
);
};
export default Fields;
Field.js file:
import { useState, useEffect } from "react";
import styled from "styled-components";
const Field = ({ placeHolder, type, title, isCurrent }) => {
return (
<TheField isCurrent={isCurrent}>
<label className='fs-field-label fs-anim-upper' htmlFor='q2'>
{title}
</label>
<input
className='fs-anim-lower'
id='q2'
name='q2'
type={type}
placeholder={placeHolder}
required
/>
</TheField>
);
};
export default Field;
const TheField = styled.li`
visibility: ${(props) => (props.isCurrent === 0 ? "visible" : "hidden")};
`;
Based on this code, initially i get my 2 fields which are coming from my dummy-data.json file but when i click on the button, both of them disappear.
I know that my code is still a mess, but i first want to make them appear one by one, then i think i know the logic for the other parts.
Any help would be appreciated.
EDIT with the solution from #zergski below:
import { useState, useEffect } from "react";
import Field from "./Field";
import { BidirectionalIterator } from "./Iterator";
const ContactForm = ({ fields }) => {
const [current, setCurrent] = useState(0);
const [show, setShow] = useState(true);
const options = { startIndex: 0, loop: false, clamp: true };
const list = new BidirectionalIterator(fields, options);
return (
<div className='container'>
<div className='fs-form-wrap' id='fs-form-wrap'>
<form id='myform' className='fs-form fs-form-full'>
<ol className='fs-fields'>
<li>{list}</li>
{/* {fields.map((field, index) => {
return (
<Field
placeHolder={field.placeholder}
title={field.title}
type={field.type}
key={index}
/>
);
})} */}
</ol>
{/* <Submit clickHandler={submitClickHandler} text={submitText} /> */}
</form>
<button onClick={() => list.next()}>Continue</button>
</div>
</div>
);
};
you need to create your own iterator... either through the use of generators or a custom class.. here's one I've written
export class BidirectionalIterator {
// TODO: support for other data types
// #iterate_over: string = 'index'
static index: number = 0
static start_index: number = 0
static looped: boolean = false
static clamped: boolean = true
static data: PropertyKey[]
static ct: number = 0
static data_len: number = 0
/** Only supports iterables for now.
* #param data - the object to be iterated
* #param options.startIndex - A negative value of less than 0 sets the index at the end of the iterable
* #param options.loop - loop to the opposite end of iterable (overrides the clamp option setting)
* #param options.clamp - iterator won't finish upon reaching iterable bounds
*
* #caution - DO NOT use a for of/in loop on the iterator if either the loop option is set to true!
*/
constructor(data: any[] = [], { startIndex = 0, loop = false, clamp = true }: BidirectionalIteratorOptions = {}) {
BidirectionalIterator.setData(data)
BidirectionalIterator.start_index = startIndex
BidirectionalIterator.clamped = loop ? false : clamp
BidirectionalIterator.looped = loop
}
static [Symbol.iterator]() {
return this
}
static setData(data: any) {
this.data = data
// TODO: finish support for different collection types
let [ct, data_len] =
data instanceof Array ? [1, data.length]
: data instanceof Map ? [2, data.size]
: data instanceof Set ? [3, data.size]
: [4, -1]
this.ct = ct
this.data_len = data_len
this.resetIndex()
}
static resetIndex() {
this.setIndex(this.start_index < -1 ? this.len : this.start_index)
}
static setIndex(idx: number) {
this.index = idx - 1
}
static get len(): number {
return this.data.length
}
// ! well I'm definitely repeating myself....
static get entry() {
this.index = num_between(this.index, 0, this.len - 1)
return {
index: this.index,
value: this.data[this.index]
}
}
static get next_entry() {
this.index = num_between(this.index, 0, this.len - 1)
return {
index: this.index + 1,
value: this.data[this.index] || (this.looped ? this.data[0] : null)
}
}
static get prev_entry() {
this.index = num_between(this.index, 0, this.len - 1)
return {
index: this.index - 1,
value: this.data[this.index + 1] || (this.looped ? this.data[this.len - 1] : null)
}
}
static next() {
let value, done
(done = this.index >= this.len)
? this.index = this.len
: done = ++this.index >= this.len
// value = this.data[done ? this.len-1 : this.index]
value = this.data[num_between(this.index, 0, this.len)]
if (done)
this.looped
? value = this.data[this.index = 0]
: this.clamped
? value = this.data[this.len - 1] : null
return {
index: this.index,
value,
done
}
}
static prev() {
let value, done
(done = this.index <= -1)
? this.index = -1
: done = --this.index <= -1
// value = this.data[done ? 0 : this.index]
value = this.data[num_between(this.index, 0, this.len)]
if (done)
this.looped
? value = this.data[this.len - 1]
: this.clamped
? value = this.data[0] : null
return {
index: this.index,
value,
done
}
}
}
so to use it just instantiate the class..
const list = new BidirectionalIterator(data_array, options)
// and use with .next() & .prev() methods on mouse input
// e.g this will return the next entry in given array
list.next()
it's written in typescript though, so you need to remove all the type declarations
const { useReducer } = React
const InputWithLabelAbove = ({
labelText,
id,
onChange,
pattern,
value,
}) => {
return (
<label htmlFor={id}>
{labelText}
<div>
<input
type="text"
id={id}
pattern={pattern}
value={value}
onChange={onChange}
/>
</div>
</label>
)
}
const MemoInputWithLabelAbove = React.memo(InputWithLabelAbove)
const Component = () => {
// calc object
const calculatorObj = {
tax: '',
water: '',
energy: '',
internet: '',
transport: '',
food: '',
education: '',
childcare: '',
otherInsurance: '',
otherCosts: ''
}
// state
const inputValues = {
total: '',
showCalculator: false,
calculatedTotal: 0,
calc: {
...calculatorObj
}
}
// reducer for form states
const [values, setValues] = useReducer(
(state, newState) => ({...state, ...newState}),
inputValues
)
// on change function to handle field states.
const handleChange = React.useCallback((e, type) => {
const { value, id } = e.target
console.log('onchange')
const re = /^[0-9\b]+$/
const converted = !re.test(value) || value.length === 0 ? '' : parseInt(value, 10)
if (type === 'calculator') {
const obj = {
...values.calc,
[id]: converted
}
setValues({ calc: { ...obj }})
}
}, [values.calc])
const calcLabelArr = ['Council tax', 'Water', 'Energy (gas and/or electricity)', 'Internet', 'Transport', 'Food', 'Children\'s education', 'Childcare', 'Other insurance', 'Other essential costs']
return (
<div
style={{ width: '60%', marginBottom: '20px', position: 'relative' }}
>
{ Object.entries(values.calc).map((i, index) => {
return <div key={calcLabelArr[index]}>
<MemoInputWithLabelAbove
id={i[0]}
type="text"
labelText={calcLabelArr[index]}
onChange={(e) => handleChange(e, 'calculator')}
value={i[1]}
/>
</div>
}
)}
</div>
)
}
ReactDOM.render(
<Component />,
document.getElementById('reactBind')
)
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="reactBind"></div>
Below is the rendering of the inputs using an array (with 10 elements) so 10 input elements are rendered.
// calculator object typically populated but for this example its empty for ease.
const calcLabelArr = []
// function to return calculator fields
const buildView = () => {
return (
<Col
xs="12"
md={{ span: 6, offset: 3 }}
style={{ marginBottom: '20px', position: 'relative' }}
>
{ Object.entries(values.calc).map((i, index) => {
return <div key={calcLabelArr[index]}>
<InputWithLabelAbove
id={i[0]}
type="text"
labelPosition="top"
labelText={calcLabelArr[index]}
onChange={(e) => handleChange(e, 'calculator')}
value={i[1]}
/>
</div>
}
)}
</Col>
)
}
Below is the onChange function used to set that state of each input.
const handleChange = React.useCallback((e, type) => {
const { value, id } = e.target
const re = /^[0-9\b]+$/
const converted = !re.test(value) || isEmpty(value) ? '' : parseInt(value, 10)
if (type === 'calculator') {
const obj = {
...values.calc,
[id]: converted
}
setValues({ calc: { ...obj }})
} else {
setValues({
total: converted,
})
}
}, [values.calc])
Below is the component that is memoized.
import React from 'react'
import { join } from 'lodash'
import { Label, StyledInput, Red } from './style'
export type IProps = {
labelPosition: string,
labelText: string,
id: string,
hasErrored?: boolean,
onChange: () => void,
dataUxId?: string,
pattern?: string,
sessioncamHide?: boolean,
sessioncamClassList?: string | string[],
value?: string,
validation?: boolean,
}
const InputWithLabelAbove: React.FC<IProps> = ({
labelPosition,
labelText,
id,
hasErrored,
onChange,
dataUxId,
pattern,
sessioncamHide,
sessioncamClassList,
value,
validation,
}) =>
(
<Label hasErrored={hasErrored} labelPosition={labelPosition} htmlFor={id}>
{labelText && (<span>{labelText}{validation && (<Red>*</Red>)}</span>)}
<div>
<StyledInput
type="text"
id={id}
hasErrored={hasErrored}
dataUxId={`InputText_${dataUxId}`}
pattern={pattern}
labelPosition={labelPosition}
value={value}
onInput={onChange}
onChange={onChange}
/>
</div>
</Label>
)
export const MemoInputWithLabelAbove = React.memo(InputWithLabelAbove)
As you can see, it isn't the key I don't think that is causing the re-render, my input component is memoized and the onChange is using a callback, however on using the react profiler, every onChange re-renders all my input components. Could someone elaborate to why this is?
One thing that jumps out is this property on the input component:
onChange={(e) => handleChange(e, 'calculator')}
Even though handleChange is memoized, you're creating a new arrow function every time to call the memoized function. So even if the input is memoized, it's seeing a new onChange every time.
You'd need to pass a stable function to avoid re-rendering, for instance:
const onChange = React.useCallback(
e => handleChange(e, "calculator"),
[handleChange]
);
and then
onChange={onChange}
(It wasn't clear to me where you're defining handleChange; if it's within the component that's rendering these inputs, you can probably combine that definition with the above in a single useCallback or possible useMemo if it's multiple callbacks. Although small pieces are good too.)
There are two select fields, Language and Currency. I am getting values dynamically showing in both fields, but now I have to change drop down value and op press on button I am calling Onclick fucntion and updating to server .
If not changing cvalue in preferredLanguage and updating its showing undefined
preferredCurrency - if am using below code for this is giving boolean value.
Please review my code and correct it .
I have to display data dynamically in both selectfield and after changing the value in selectfield and update it should updated current selected value .
this.state = { languageAndCurrecny:{
preferredLanguage: navigation.state.params.customerInfo[0].billingPreferenceDetails.presentationLanguageCode,
},
currencyChangedValue:{
preferredCurrency: navigation.state.params.customerInfo[0].billingPreferenceDetails.preferedCurrencyCode,
}
this.handleChangeCurrency=this.handleChangeCurrency.bind(this);
}
OnButtonClick = async (preferredLanguage, preferredCurrency) => {
const { OnButtonClick } = this.props;
await OnButtonClick(preferredLanguage, preferredCurrency);
this.setState({
preferredCurrency:'',
preferredLanguage:'',
})
}
languageChanged = (key, val) => {
this.handleChange({ field: "preferredLanguage" }, val);
};
handleChange = (props, e) => {
let tempObj = this.state.languageAndCurrecny;
tempObj[props.field] = e;
this.setState({ preferredLanguage: tempObj });
};
currencyChanged = (key, val) => {
this.handleChangeCurrency({ field: "preferredCurrency" }, val);
};
handleChangeCurrency = (props, e) => {
let tempObj = this.state.currencyChangedValue;
tempObj[props.field] = e;
this.setState({ preferredCurrency: tempObj });
};
render (
let {languageAndCurrecny,currencyChangedValue} = this.state;
const { navigation, clmmasterData } = this.props;
const { masterData, language } = clmmasterData;
let currencyData=[];
masterData.preferredCurrency.map(({ code: value, name: label }) => {
currencyData.push({ value, label });
});
let languageData=[];
masterData.language.map(({ code: value, name: label }) => {
languageData.push({ value, label });
});
return (
<View style={{ padding: 20 }}>
<Form style={{ width: '100%' }}>
<SelectField
label="Presentation Language"
node="presentationLanguage"
options={languageData}
value={languageAndCurrency.preferredLanguage}
onChange={this.languageChanged}
that={this}
setIcon={true}
img="LANGUAGE"
/>
<SelectField
label="Preferred Currency"
options={currencyData}
value={preferredCurrency}
node="preferredCurrency"
onChange={this.handleChangeCurrency}
that={this}
setIcon={true}
img="CURRENCY"
/>
<View style={{ marginTop: 50 }}>
<PrimaryBtn label={'submit'} disabled={false} onPress={()=> this.OnButtonClick(this.state.preferredLanguage,this.state.preferredCurrency,)}/>
</View>
Thanks .. Please help
new answer:
onChange returns a value which contain new selected value.
Use these 2 function as below
languageChanged = (key, val) => {
let languageAndCurrecny.preferredLanguage = val
this.setState({ languageAndCurrecny: languageAndCurrecny})
};
handleChangeCurrency(value){
this.setState({
preferredCurrency: value
})}
Old answer just add this in render method, you just missed preferredCurrency:
let { languageAndCurrecny, preferredCurrency } = this.state;
I have a dynamic form in react-js and some of my elements are checkbox/radio that one of them have a text input binded to it.
for example the question is:
What is your favorite color?
and the answers are:
- red
- blue
- green
- OTHER
and OTHER answer have a text input in front of it for user to typing his custom answer in it.
How can I bind that checkbox/radio to the relevant input text and get its value?
form
If you use a newer version of React, try the state hook.
Something along the lines of
import React, { useState } from 'react';
function Example() {
const [color, setColor] = useState('');
return (
<div>
<select value={color}
onChange={(e) => setColor(value)}>
{ ['red', 'blue', 'green', 'OTHER'].map((c) => <option key={c} value={c}>{c}</option>)}
</select>
{color === 'OTHER' && <input type="text"></input>}
</div>
);
}
https://reactjs.org/docs/hooks-state.html
Using Material UI, I have used a similar solution like this to add an "Other" checkbox that is fillable:
import React from "react";
import ReactDOM from "react-dom";
import FormGroup from "#material-ui/core/FormGroup";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
import TextField from "#material-ui/core/TextField";
import "./styles.css";
class App extends React.Component {
constructor() {
super();
this.state = {
options: ["red", "blue", "green", "other"],
filterOptions: ["red", "blue", "green"],
checkedValues: [],
otherValue: "other"
};
}
handleOther = () => event => {
let value = event.target.value;
this.setState({
otherValue: value
});
};
handleSaveOther = () => event => {
let newCheckedValues = [...this.state.checkedValues]; // make a separate copy of the array
let intersection = newCheckedValues.filter(x =>
this.state.filterOptions.includes(x)
);
let allValues = [...intersection, this.state.otherValue];
if (this.state.other) {
this.setState({
checkedValues: allValues
});
}
};
handleCheck = option => event => {
let value = event.target.value;
let checked = event.target.checked;
let newCheckedValues = [...this.state.checkedValues]; // make a separate copy of the array
let index = newCheckedValues.indexOf(value);
if (index !== -1) {
newCheckedValues.splice(index, 1);
this.setState({
checkedValues: newCheckedValues,
[option]: checked
});
} else {
this.setState({
checkedValues: [...this.state.checkedValues, value],
[option]: checked
});
}
};
render() {
const { options, checkedValues, otherValue } = this.state;
console.log(checkedValues);
return (
<div className="App">
<div style={{ width: "50%", margin: "0 auto" }}>
<FormGroup>
{options.map((option, i) => {
return (
<FormControlLabel
control={
<Checkbox
onChange={this.handleCheck(option)}
value={option === "other" ? otherValue : option}
color={"primary"}
/>
}
label={
option === "other" ? (
<TextField
id={"other"}
name={"other"}
value={this.state.otherValue}
fullWidth
onChange={this.handleOther()}
onBlur={this.handleSaveOther()}
/>
) : (
option
)
}
/>
);
})}
</FormGroup>
</div>
</div>
);
}
}
See working example here
I am trying to search through a flatlist based on a search bar text. The problem I am running into is that when the user mistypes...say they wanted to type "burger" but typed "burget" by mistake then it returns nothing as it should. When the user deletes the "t" then it should re-render the flatlist again with the last text matching the "burge" part.
note: using react-native-elements search bar which allows me to call the text with just e or event.
What I have so far in the Main.js file:
searchText = (e) => {
let text = e.toLowerCase();
let trucks = this.state.data;
// search by food truck name
let filteredName = trucks.filter((truck) => {
return truck.name.toLowerCase().match(text);
});
// if no match and text is empty
if(!text || text === '') {
console.log('change state');
this.setState({
data: initial
});
}
// if no name matches to text output
else if(!Array.isArray(filteredName) && !filteredName.length) {
console.log("not name");
this.setState({
data: [],
});
}
// if name matches then display
else if(Array.isArray(filteredName)) {
console.log('Name');
this.setState({
data: filteredName,
});
}
};
<View style={styles.container}>
<SearchBar
round
lightTheme
containerStyle={styles.search}
ref="search"
textInputRef="searchText"
onChangeText={this.searchText.bind(this)}
placeholder='Search by Truck Name...'
/>
<TruckList getTruck={(truck) => this.setTruck(truck)} truckScreen={this.truckScreen} data={this.state.data}/>
</View>
then the TruckList.JS:
export default class TruckList extends Component {
// rendering truck screen
renderTruckScreen = (item) => {
this.props.truckScreen();
this.props.getTruck(item);
}
render() {
return(
<List style={styles.list}>
<FlatList
data={this.props.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
avatar={{uri: item.pic1}}
avatarStyle={styles.avatar}
title={item.name}
titleStyle={styles.title}
subtitle={
<View style={styles.subtitleView}>
<Text style={styles.subtitleFood}>{item.food}</Text>
<View style={styles.subtitleInfo}>
<Icon
name="favorite"
size={20}
color={"#f44336"}
style={styles.subtitleFavorite}
/>
<Text style={styles.subtitleFavoriteText}>{item.favorited} favorited</Text>
</View>
</View>
}
onPress={() => this.renderTruckScreen(item)}
/>
)}
keyExtractor={(item) => item.uid}
ListFooterComponent={this.footer}
/>
</List>
)
}
}
I have tried a few other ways to no avail. Also the only solutions I have seen working for React Native are with ListView which will be depreciated in time. So I am trying to do this with the new FlatList Component.
Thanks for your help!
I came across this same issue today when trying to implement a filter / search function on the new FlatList component. This is how I managed to solve it:
By creating another item in the state of the parent component called noData, you can set that to true when there are no results that match your search and then render your FlatList conditionally.
My implementation is slightly different to yours, but if I had to adjust your code it would look something like this:
Searchtext function:
searchText = (e) => {
let text = e.toLowerCase()
let trucks = this.state.data
let filteredName = trucks.filter((item) => {
return item.name.toLowerCase().match(text)
})
if (!text || text === '') {
this.setState({
data: initial
})
} else if (!Array.isArray(filteredName) && !filteredName.length) {
// set no data flag to true so as to render flatlist conditionally
this.setState({
noData: true
})
} else if (Array.isArray(filteredName)) {
this.setState({
noData: false,
data: filteredName
})
}
}
Then pass the noData bool to your TruckList component:
<TruckList getTruck={(truck) => this.setTruck(truck)}
truckScreen={this.truckScreen} data={this.state.data} noData={this.state.noData}/>
Then render your FlatList in the TruckList component only if there are results:
<List style={styles.list}>
{this.props.noData ? <Text>NoData</Text> : <FlatList {...} />}
</List>
That should then take care of handling user typing errors - as it will re-render the flatlist as soon as there are no results, and will remember the previous search state when you remove the typing error..
Let me know if that helps!
For a useful in-memory search you should keep initial data seperately.
I have more simple solution for this.
This solution for in-memory search on FlatList's data and uses it String.prototype.includes() method to search substring.
You can find full source code of this component in this gist;
https://gist.github.com/metehansenol/46d065b132dd8916159910d5e9586058
My initial state;
this.state = {
searchText: "",
data: [],
filteredData: []
};
My SearchBar component (it comes from react-native-elements package);
<SearchBar
round={true}
lightTheme={true}
placeholder="Search..."
autoCapitalize='none'
autoCorrect={false}
onChangeText={this.search}
value={this.state.searchText}
/>
My search method;
search = (searchText) => {
this.setState({searchText: searchText});
let filteredData = this.state.data.filter(function (item) {
return item.description.includes(searchText);
});
this.setState({filteredData: filteredData});
};
And last my FlatList's DataSource expression;
<FlatList
data={this.state.filteredData && this.state.filteredData.length > 0 ? this.state.filteredData : this.state.data}
keyExtractor={(item) => `item-${item.id}`}
renderItem={({item}) => <ListItem
id={item.id}
code={item.code}
description={item.description}
/>}
/>
Happy coding...
Update:
This blog can help you better understand the searching in a FlatList.
FYI:
If you have huge online data then you can also use algolia.
I adjusted the above code for me in order to make it work properly. The reason is that when user removes the last wrong character, code search this new string from a previous search list (state) which does not contain all objects, although it had to search from a full list available. So, I have two list now. One contains full list of objects and second contains only rendered list of objects which is changing upon search.
handleSearchInput(e){
let text = e.toLowerCase()
let fullList = this.state.fullListData;
let filteredList = fullList.filter((item) => { // search from a full list, and not from a previous search results list
if(item.guest.fullname.toLowerCase().match(text))
return item;
})
if (!text || text === '') {
this.setState({
renderedListData: fullList,
noData:false,
})
} else if (!filteredList.length) {
// set no data flag to true so as to render flatlist conditionally
this.setState({
noData: true
})
}
else if (Array.isArray(filteredList)) {
this.setState({
noData: false,
renderedListData: filteredList
})
}
}
Make Search Bar Filter for List View Data in React Native
For Real-Time Searching in List View using Search Bar Filter
We will load the list from the network call and then show it to the user.
The user can search the data by entering the text in TextInput.
After inserting the text SearchFilterFunction will be called We will
compare the list data with the inserted data and will make a new Data
source.
We will update the data source attached to the ListView.
It will re-render the list and the user will be able to see the
filtered data.
//This is an example code to Add Search Bar Filter on Listview//
import React, { Component } from 'react';
//import react in our code.
import {
Text,
StyleSheet,
View,
FlatList,
TextInput,
ActivityIndicator,
Alert,
} from 'react-native';
//import all the components we are going to use.
export default class App extends Component {
constructor(props) {
super(props);
//setting default state
this.state = { isLoading: true, text: '' };
this.arrayholder = [];
}
componentDidMount() {
return fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(responseJson => {
this.setState(
{
isLoading: false,
dataSource: responseJson
},
function() {
this.arrayholder = responseJson;
}
);
})
.catch(error => {
console.error(error);
});
}
SearchFilterFunction(text) {
//passing the inserted text in textinput
const newData = this.arrayholder.filter(function(item) {
//applying filter for the inserted text in search bar
const itemData = item.title ? item.title.toUpperCase() : ''.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
this.setState({
//setting the filtered newData on datasource
//After setting the data it will automatically re-render the view
dataSource: newData,
text: text,
});
}
ListViewItemSeparator = () => {
//Item sparator view
return (
<View
style={{
height: 0.3,
width: '90%',
backgroundColor: '#080808',
}}
/>
);
};
render() {
if (this.state.isLoading) {
//Loading View while data is loading
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<ActivityIndicator />
</View>
);
}
return (
//ListView to show with textinput used as search bar
<View style={styles.viewStyle}>
<TextInput
style={styles.textInputStyle}
onChangeText={text => this.SearchFilterFunction(text)}
value={this.state.text}
underlineColorAndroid="transparent"
placeholder="Search Here"
/>
<FlatList
data={this.state.dataSource}
ItemSeparatorComponent={this.ListViewItemSeparator}
renderItem={({ item }) => (
<Text style={styles.textStyle}>{item.title}</Text>
)}
enableEmptySections={true}
style={{ marginTop: 10 }}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
viewStyle: {
justifyContent: 'center',
flex: 1,
marginTop: 40,
padding: 16,
},
textStyle: {
padding: 10,
},
textInputStyle: {
height: 40,
borderWidth: 1,
paddingLeft: 10,
borderColor: '#009688',
backgroundColor: '#FFFFFF',
},
});
Click Hear for more idea
Here is my solution:
You need to have a backup of your data
this.state = {
data: [],
backup: []
}
on search method
search = txt => {
let text = txt.toLowerCase()
let tracks = this.state.backup
let filterTracks = tracks.filter(item => {
if(item.name.toLowerCase().match(text)) {
return item
}
})
this.setState({ data: filterTracks })
}
Explanation: when calling setState on your data it will changed to current state and cannot be changed again.
So backup data will handle to filter your data.
ref - https://medium.freecodecamp.org/how-to-build-a-react-native-flatlist-with-realtime-searching-ability-81ad100f6699
constructor(props) {
super(props);
this.state = {
data: [],
value: ""
};
this.arrayholder = [];
}
Next fetching data :-
_fetchdata = async () => {
const response = await fetch("https://randomuser.me/api?results=10");
const json = await response.json();
this.setState({ data: json.results });
this.arrayholder = json.results;
};
Next define searchFilterFunction :-
searchFilterFunction = text => {
this.setState({
value: text
});
const newData = this.arrayholder.filter(item => {
const itemData = item.email.toLowerCase();
const textData = text.toLowerCase();
return itemData.indexOf(textData) > -1;
});
this.setState({ data: newData });
};
rendering searchView:-
<TextInput
style={{ height: 40, borderColor: "gray", borderWidth: 1 }}
onChangeText={text => this.searchFilterFunction(text)}
/>
Don't forget to import TextInput from "react-native";
You can Search your data by following these steps :
<TextInput onChangeText={(text) => searchData(text)} value={input} />
***Please Note *searchData is my function whom I passing a text prop***
const searchData = (text) => {
const newData = restaurantsData.filter((item) => {
return item.title.search(text) > -1;
});
setRestaurantsData(newData);
setInput(text);
};
Note RestaurantsData is my data array
FYI : data is the subtext to be searched, this is a basic search implemented as the data to be searched is looked into every list item of an array which is a copy of the actual array/array of objects and finally its state is set whether match found or not between 0 to (actualArray.length-1) and the temporary arrayData is rendered if there is at least one match else actualArray is rendered
implementSearch(data) {
temp = [];
var count = 0;
var searchData = data.toUpperCase();
var arr = this.state.personDetail;
for (var i = 0; i < arr.length; i++) {
var actualData = arr[i].name.toUpperCase();
if (actualData.includes(searchData)) {
temp.push(arr[i]);
count++;
}
}
this.setState({
tempArray: temp,
matches: count,
searchValue: data
});
}
Hope this helps
My search method; from #metehan-senol
search = (searchText) => {
this.setState({searchText: searchText});
let filteredData = this.state.data.filter(function (item) {
return item.description.includes(searchText);
});
this.setState({filteredData: filteredData});
};
the search method of could be simplify and Eslint proof like so
search = (searchText) => {
const searched = searchText.toLowerCase();
this.setState(prevState => ({
searchText: searched,
filteredData: prevState.data.filter(item =>
item.description.toLowerCase().includes(searched)
),
}));
};
Do filter by applying
let filterData= data.filter((item) => {
return item.name.toLowerCase().match(text)
})
if (!text || text === '') {
this.setState({
datasource: initial
})
} else if (!Array.isArray(filterData) && !filterData.length) {
// set no data flag to true so as to render flatlist conditionally
this.setState({
noData: true
})
} else if (Array.isArray(filterData)) {
this.setState({
noData: false,`enter code here`
dataSource: filterData
})`enter code here`
}
This is not the best solution in terms of performance, but if you do not have a large amount of data, then feel free to use this function:
searchFilter () {
return this.props.data.filter((item) => {
const regex = new RegExp(this.state.searchInput, "gi")
return item.label.match(regex);
})
}
And then in your FlatList component:
<FlatList
data={this.searchFilter()}
renderItem={this.renderItem}
keyExtractor={(item) => item.value}
/>
const SearchUser = (e) =>{
console.log(e)
const setProject = Project.filter(item => item.name.toLowerCase().includes(e.toLowerCase()) )
console.log(setProject)
setfetch(setProject)
}