Enzyme button simulate doesn't work as expected - javascript

I have the following react-native test code.
import { shallow } from 'enzyme';
import React from 'react';
import {
BorderlessButton,
InputBox,
ProgressBar,
} from 'components';
import Name from '../name.component';
describe('Name component', () => {
let wrapper: any;
const mockOnPress = jest.fn();
const mockSaveStep = jest.fn();
const mockProps: any = {
errors: null,
values: [{ givenName: 'givenName', familyName: 'familyName' }],
};
beforeEach(() => {
wrapper = shallow(<Name signUpForm={mockProps} saveStep={mockSaveStep} />);
});
it('should render Name component', () => {
expect(wrapper).toMatchSnapshot();
});
it('should render 2 <InputBox />', () => {
expect(wrapper.find(InputBox)).toHaveLength(2);
});
it('should render a <ProgressBar />', () => {
expect(wrapper.find(ProgressBar)).toHaveLength(1);
});
it('should render a <BorderlessButton /> with the text NEXT', () => {
expect(wrapper.find(BorderlessButton)).toHaveLength(1);
expect(wrapper.find(BorderlessButton).props().text).toEqual('NEXT');
});
it('should press the NEXT button', () => {
wrapper.find(BorderlessButton).simulate('click');
expect(mockOnPress).toHaveBeenCalled();
});
});
But the last test doesn't work properly. How can I simulate a this button click? This gives me an error saying
expect(jest.fn()).toHaveBeenCalled().
Expected mock function to have been called, but it was not called.
This is the component.
class NameComponent extends Component {
componentDidMount() {
const { saveStep } = this.props;
saveStep(1, 'Name');
}
disableButton = () => {
const {
signUpForm: {
errors, values,
},
} = this.props;
if (errors && values && errors.givenName && errors.familyName) {
if (errors.givenName.length > 0 || values.givenName === '') return true;
if (errors.familyName.length > 0 || values.familyName === '') return true;
}
}
handleNext = () => {
navigationService.navigate('PreferredName');
}
resetForm = () => {
const { resetForm } = this.props;
resetForm(SIGN_UP_FORM);
navigationService.navigate('LoginMain');
}
render() {
const { name, required } = ValidationTypes;
const { step } = this.props;
return (
<SafeAreaView style={{ flex: 1 }}>
<KeyboardAvoidingView style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : null}
enabled>
<ScreenContainer
navType={ScreenContainer.Types.LEVEL_THREE}
levelThreeOnPress={this.resetForm}>
<View style={styles.container}>
<View style={{ flex: 1 }}>
<SinglifeText
type={SinglifeText.Types.H1}
label='Let’s start with your legal name'
style={styles.textLabel}
/>
<View style={styles.names}>
<InputBox
name='givenName'
form={SIGN_UP_FORM}
maxLength={22}
placeholder='Given name'
containerStyle={styles.givenNameContainer}
inputContainerStyle={styles.inputContainer}
errorStyles={styles.inputError}
keyboardType={KeyBoardTypes.default}
validations={[required, name]}
/>
<InputBox
name='familyName'
form={SIGN_UP_FORM}
maxLength={22}
placeholder='Family name'
inputContainerStyle={styles.inputContainer}
errorStyles={styles.inputError}
keyboardType={KeyBoardTypes.default}
validations={[required, name]}
/>
</View>
<SinglifeText
type={SinglifeText.Types.HINT}
label='Please use the same name you use with your bank'
style={styles.hint}
/>
</View>
</View>
</ScreenContainer>
<ProgressBar presentage={(step / MANUAL_SIGNUP_STEP_COUNT) * 100} />
<View style={styles.bottomButtonContainer}>
<BorderlessButton
text='NEXT'
disabled={this.disableButton()}
onPress={this.handleNext}
/>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
}
How can I solve this??

You create the function mockOnPress(), but mockOnPress() is never injected into the component.
In the component you wrote, NameComponent has a child BorderlessButton component, in which the line, onPress={this.handleNext} is hard-coded in. handleNext() is defined elsewhere as:
handleNext = () => {
navigationService.navigate('PreferredName');
}
To test that the functionality of the button is working, I see two viable options. One is to use dependency injection. Instead of hard-coding the button to call navigationService.navigate('PreferredName'), you could have it execute code that is passed in as a prop. See the following as an example:
it('Button should handle simulated click', function (done) {
wrappedButton = mount(<MyButton onClick={() => done()}>Click me!</BaseButton>)
wrappedButton.find('button').simulate('click')
}
Note that you could take the principle provided in the above example and expand it to your example by passing in the functionality you want to occur onClick as a prop to your NameComponent.
Another option you have, is to test whether clicking the button causes the side effects you want to occur. As written, pressing the button should call, navigationService.navigate('PreferredName'). Is this the intended effect? If so, you can change your test to validate whether navigationService.navigate('PreferredName') was called somehow.

Related

React Native, how to make redux state false again

I created a view cart in which I show total price and view cart button, when I add item it makes condition true and display that cart below in every screen, but when I click view cart it's not making it false again, how can I do this? can someone check my code and tell me please. Below is my code
Viewcart.js
<View>
{this.props.show && this.props.items.length > 0 ? (
<View style={styles.total}>
<Text style={styles.totaltext}>Total:</Text>
<Text style={styles.priceTotal}>{this.props.total}</Text>
<View style={styles.onPress}>
<Text
style={styles.pressText}
onPress={() => {
RootNavigation.navigate("Cart");
this.props.show;
}}
>
View Cart
</Text>
</View>
</View>
) : null}
</View>
const mapStateToProps = (state) => {
return {
show: state.clothes.show,
};
};
const mapDispatchToProps = (dispatch) => {
return {
showCart: () => dispatch(showCart()),
};
};
reducer.js
if (action.type === SHOW_CART) {
let addedItem = state.addedItems;
if (addedItem.length === 0) {
return {
...state,
show: state.showCart,
};
} else {
return {
...state,
show: action.showCart,
};
}
}
const initialstate = {
showCart: false
}
action.js
export const showCart = (id) => {
return {
type: SHOW_CART,
showCart: true,
id,
};
};
As per the chat the requirement is to toggle this when exiting the screen so the easiest way to do that is to use the lifecycle methods.
To hide use componentDidMount
componentDidMount(){
this.props.showCartOff();
}
to show use component
componentWillUnmount(){
this.props.showCart();
}

Why isnt running the function from class component in another component? REACT-NATIVE

I have a stacknavigator and in headerTitle have a header component for each screen, heres the code:
const Home_stack = createStackNavigator({ //hooks
Home: {
screen: Home,
navigationOptions: ({navigation}) => {
return {
headerTitle: () => <Header navigation = {navigation} title = "Shum Note"/>}
}
},
Create: {
screen: Create,
navigationOptions: ({navigation}) => {
return {
headerTitle: () => <Childs_header navigation = {navigation} title = "Create Note"/>}
}
},
Edit: {
screen: Edit,
navigationOptions: ({navigation}) => {
return {
headerTitle: () => <Childs_header navigation = {navigation} title = "Edit Note"/>}
}
},
});
and this is the component Childs_header:
import Create_note from "../components/Create_note";
class Header extends Component {
comun = new Create_note();
render() {
return (
<>
<View style ={{backgroundColor: "white", flexDirection: "row", alignItems: "center"}}>
<View>
<Text style = {{color: "black", fontSize: 30, marginLeft: -20}}>{this.props.title}
</Text>
</View>
<View>
<Text>
<Feather name="check-square" size={24} color="black" onPress = {() => this.comun.save_data(this.props.navigation)}/>
</Text>
</View>
</View>
</>
);
}
}
export default Header;
as you can see I import the component Create_note and create an object of it to use one of its function, in this case save_data, but for some reason it isnt working, dont know if it has something to do with AsyncStorage becase with console.log("hi") it works, but saving data it doesnt, heres the structure of create_note component:
class Create_note extends Component {
state = {
content: "",
default_color: "#87cefa", //default color (cyan)
}
save_data = async() => {
if (this.state.content === "") {
//navigation.navigate("Home");
}else {
let clear_content = this.state.content.replace(/ /g,""); //replace al
try {
const data = await AsyncStorage.getItem("data");
if (data === null) {
const data = {"array_notes": [], "last_note": 0};
const last_note = data.last_note + 1;
const new_note = {note_number: last_note, content: clear_content, color: this.state.default_color, text_color: this.state.color}; //create a new_note object, note_number will be the key for each note
const array_notes = [];
array_notes.push(new_note);
data.array_notes = array_notes;
data.last_note = last_note;
await AsyncStorage.setItem("data", JSON.stringify(data)); //using stringify to save the array
//navigation.navigate("Home");
}else {
const data = JSON.parse(await AsyncStorage.getItem("data")); //use parse to acces to the data of the array
const last_note = data.last_note + 1;
const new_note = {note_number: last_note, content: clear_content, color: this.state.default_color, text_color: this.state.color};
const array_notes = data.array_notes;
array_notes.push(new_note);
data.array_notes = array_notes;
data.last_note = last_note;
await AsyncStorage.setItem("data", JSON.stringify(data));
//navigation.navigate("Home");
}
}catch(error) {
alert(error);
}
}
}
render() {
const props = {
screen: "create_note",
change_color: this.change_color.bind(this),
update_color: this.update_color.bind(this),
}
return (
<>
<ScrollView>
<RichEditor
ref = {this.richText}
onChange = {text => this.setState({content: text}, () => console.log(this.state.content))}
allowFileAccess = {true}>
</RichEditor>
</ScrollView>
{this.state.change_color ?
<Color
{...props}>
</Color>
: null}
<RichToolbar
editor = {this.richText}
onPressAddImage = {this.insertImage}
actions = {[
actions.insertBulletsList,
actions.insertOrderedList,
actions.insertImage,
"change_text_color",
]}
iconMap ={{
[actions.insertBulletsList]: () => <Text style = {this.styles.icon}><MaterialIcons name = "format-list-bulleted" size = {this.option_icon.size} color = {this.option_icon.color}/></Text>,
[actions.insertOrderedList]: () => <Text style = {this.styles.icon}><MaterialIcons name = "format-list-numbered" size = {this.option_icon.size} color = {this.option_icon.color}/></Text>,
[actions.insertImage]: () => <Text style = {this.styles.icon}><MaterialIcons name = "image" size = {this.option_icon.size} color = {this.option_icon.color}/></Text>,
change_text_color: () => <Text style = {this.styles.icon}><MaterialIcons name = "format-color-text" size = {this.option_icon.size} color = {this.option_icon.color}/></Text>,
}}
change_text_color = {this.change_color}
style = {{backgroundColor: "white"}}>
</RichToolbar>
<Button title = "save" onPress = {this.save_data}></Button>
</>
);
}
heres an image so you can see better the structure:
the function should run when I click in the check icon, in the blue button works because its part of the create_note component, but I want it in the check icon
From looking at your code I think the problem is that you're passing the navigation state object as a parameter to your save_data function in the onClick of your checkmark.
this.comun.save_data(this.props.navigation)
but the function definition of save_data doesn't take any parameters:
save_data = async () => {
// ...
};
So you could change the save_data function to something like this
save_data = async (navigation) => {
// ...
};
in order to have it work from inside the Header component.
If you want the save button, rendered by the Create_note component, to also call save_data onPress; you will have to pass the navigation state there as well.

React Native display validation errors inline

I am a complete noob to React Native and I am just trying to get hang of it.
I have a simple Login screen which I have developed using Container/Presentation component concept. My presentation component only has render function that renders three two TextInput and a Button.
Rather than displaying errors using Toasts I want to display errors below the TextInput itself. So, what I have done is added Text element below the TextInput. Something like below.
<TextInput placeholder="Email"></TextInput>
<Text ref="emailErrors"></Text>
By default, Text with ref emailErrors is hidden. But when the focus shifts from Email TextInput and if email is invalid for some reason I want to generate a simple error and set it as text for Text element with ref emailErrors.
Now, I understand that I will have to write my logic in container components and pass it as prop to presentation component. But what I am unable to understand is how to trigger the setting of error text and displaying the Text element.
UPDATE:
My Presentation Component:
class LoginForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<KeyboardAvoidingView
style={styles.loginFormContainer}>
<ScrollView
scrollEnabled={true}
enableOnAndroid={true}
enableAutomaticScroll={true}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="always"
resetScrollToCoords={{ x: 0, y: 0 }}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}>
<View style={styles.loginForm}>
<Image
source={require("../../../assets/images/logos/logo.png")}
style={styles.logoImage}></Image>
<View style={styles.textInputWithIcon}>
<Image
style={styles.textInputIcon}
source={require("../../../assets/images/icons/email.png")}
></Image>
<View style={styles.textField}>
<TextInput name="email"
ref="email"
placeholder="Email"
blurOnSubmit={false}
returnKeyType={"next"}
underlineColorAndroid={COLORS.red}
onSubmitEditing={() => this.refs.password.focus()}
style={[GLOBALSTYLES.textInput, styles.textInput]}
onChangeText={(text) => this.props.onEmailTextChanged(text)}
>
</TextInput>
<Text
ref="email">
</Text>
</View>
</View>
<View style={styles.textInputWithIcon}>
<Image
style={styles.textInputIcon}
source={require("../../../assets/images/icons/locked.png")}
></Image>
<View style={styles.textField}>
<TextInput name="password"
ref="password"
blurOnSubmit={false}
placeholder="Password"
secureTextEntry={true}
returnKeyType={"next"}
style={styles.textInput}
underlineColorAndroid={COLORS.red}
onSubmitEditing={() => Keyboard.dismiss()}
style={[GLOBALSTYLES.textInput, styles.textInput]}
onChangeText={(text) => this.props.onPasswordTextChanged(text)}
onBlur={() => this.props.onPasswordTextBlurred()}
></TextInput>
<Text
ref="password">
</Text>
</View>
</View>
<TouchableOpacity
activeOpacity={0.5}
onPress={() => { this.props.onLoginPressed() }}
style={styles.loginButton}
underlayColor={COLORS.white}>
<Text style={styles.loginButtonText}>Login</Text>
</TouchableOpacity>
</View>
</ScrollView>
</KeyboardAvoidingView>
)
};
};
export default LoginForm;
My Container Component:
class Login extends Component {
static navigationOptions = ({ navigation }) => ({
"title": "Login"
});
constructor(props) {
super(props);
this.state = {}
}
// onLoginPressed will trigger the authentication workflow with the remote server.
onLoginPressed() {
const { isUserLoggedIn, email, password } = this.state;
if (this.state.isEmailValid && this.state.isPasswordValid) {
axios.post(CONFIGURATION.LOGIN_URL, {
username: email,
password: password
}).then(response => {
const navigationParams = {
baseUrl: response.data.url,
token: response.data.token,
username: email
}
this.props.dispatch(loginSuccess(navigationParams));
// Adding retrieved values to AsyncStorage
AsyncStorage.multiSet(
[
[IS_USER_LOGGED_IN, "YES"],
[USER, email],
[TOKEN, response.data.token],
[BASE_URL, response.data.url]
],
() => {
this.props.navigation.navigate("WebApp", navigationParams);
});
}).catch(error => {
console.error(error);
ToastAndroid.show("Authentication Failed", ToastAndroid.SHORT);
});
}
}
// Updating the state key email
onEmailTextChanged(text) {
this.setState({ "email": text });
}
// Updating the state key password
onPasswordTextChanged(text) {
this.setState({ "password": text });
}
onEmailTextBlurred() {
var text = this.state.email;
console.warn(text);
if (text == undefined || text.trim().length == 0) {
this.setState({ "isEmailValid": false });
this.setState({ "emailErrorMessage": "Email cannot be empty" });
}
else {
var regex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var isEmailValid = regex.test(text);
if (!isEmailValid) {
this.setState({ "isEmailValid": false });
this.setState({ "emailErrorMessage": "Email is incorrect." });
}
else {
this.setState({ "isEmailValid": true });
}
}
}
onPasswordTextBlurred() {
var text = this.state.password;
if (text == undefined || text.trim().length == 0) {
this.setState({ "isPasswordValid": false });
this.setState({ "passwordErrorMessage": "Password cannot be empty" });
}
else {
this.setState({ "isPasswordValid": true });
}
}
// rendering the LoginForm (presentational component) corresponding to this container component
render() {
return (
<LoginForm
onLoginPressed={() => this.onLoginPressed()}
onEmailTextChanged={(text) => this.onEmailTextChanged(text)}
onPasswordTextChanged={(text) => this.onPasswordTextChanged(text)}
onEmailTextBlurred={() => this.onEmailTextBlurred()}
onPasswordTextBlurred={() => this.onPasswordTextBlurred()}
/>
)
}
}
const mapStateToProps = (state) => {
return state;
}
const mapDispatchToProps = (dispatch) => {
const boundActionCreators = bindActionCreators(loginSuccess, dispatch);
return { ...boundActionCreators, dispatch };
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
You can use a flag in state of the component to check if invalid input is given by checking the status of the flag.
For example:
emailValidationRegex(v){
// use regex to check if email is valid and return true if email is valid, false if email is invalid
}
<TextInput
placeholder="Email"
onChange={(v)=>{this.setState({
email: v,
isEmailInvalid: !emailValidationRegex(v)
})}} >
</TextInput>
{this.state.isEmailInvalid && <Text>Sorry! Invalid Email</Text>}
Explanation:
isEmailInvalid is keeping the status of given email address if it is valid or not. Depending on it's status the following Error is shown conditionally.
Update:
In the onEmailTextChanged method in your container component update another state to hold if the email is valid or not:
onEmailTextChanged(text) {
this.setState({ "email": text, isEmailInvalid: !emailValidationRegex(text) });
}
Then pass this.state.isEmailInvalid in the props sent to the presentational component. Show error conditionally in the presentational component then.
render method of container component:
render() {
return (
<LoginForm
onLoginPressed={() => this.onLoginPressed()}
onEmailTextChanged={(text) => this.onEmailTextChanged(text)}
isEmailInvalid={this.state.isEmailInvalid}
onPasswordTextChanged={(text) => this.onPasswordTextChanged(text)}
onEmailTextBlurred={() => this.onEmailTextBlurred()}
onPasswordTextBlurred={() => this.onPasswordTextBlurred()}
/>
)
Now you are able to use this.props.isEmailInvalid in the presentational component

Search Filter with React Native on FlatList

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)
}

How to render multi components and set individual status in react-native?

I have a component will use map to render multi checkbox, and each checkbox has a callback function "onPress" get by props, the "onPress" function will setState checked, but now when I click on one checkbox, all checkboxs will be chosed, it cause they all use the same state, the goal I wanna choose each checkbox what I just ckick on, I know I can write many state different "onPress" function for each checkbox, but it looks stupid, I will add more checkbox in the future, What's the best and flexiable way to solve the task?
import React, { Component } from 'react'
import { View } from 'react-native'
import { CheckBox } from 'react-native-elements'
const styles = {
CheckBox: {
borderBottomWidth: 0.3,
borderBottomColor: 'gray'
},
checkBox : {
backgroundColor: "#ffffff",
borderWidth: 0
},
text: {
flex: 0.95,
backgroundColor: "#ffffff"
}
}
const languages = ["中文","英文","日文","韓文"]
class Language extends Component {
constructor(props) {
super(props);
this.state = { checked: false };
}
onPress = () => {
this.setState({ checked: !this.state.checked })
}
renderlanguages = () => {
return languages.map((langauge) => {
return(
<View key = { langauge } style = { styles.CheckBox }>
<CheckBox
title = { langauge }
iconRight
containerStyle = { styles.checkBox }
textStyle = { styles.text }
checkedColor = 'red'
checked = { this.state.checked }
onPress = { this.onPress }
/>
</View>
)
})
}
render(){
return(
<View>
{ this.renderlanguages() }
</View>
)
}
}
export default Language;
The behavior is choose all checkbox even though I only choose one now.
You can just pass the langauge (note this is probably a typo for language) variable to the function and us it to identify which one is being checked
onPress = (langauge) => {
this.setState({ [langauge]: { checked: !this.state[langauge].checked } })
}
renderlanguages = () => {
return languages.map((langauge) => {
return(
<View key = { langauge } style = { styles.CheckBox }>
<CheckBox
title = { langauge }
iconRight
//component = { () => {return <TouchableOpacity></TouchableOpacity>}}
containerStyle = { styles.checkBox }
textStyle = { styles.text }
checkedColor = 'red'
checked = { this.state[langauge].checked }
onPress = { () => this.onPress(langauge) }
/>
</View>
)
})
}

Categories