How to implement multiselect using Flatlist in react native? - javascript

I am trying to create a flatlist with text,but it selects only single value but i want to select multiple values from the list. and i also need color change for selected item. Here i am fetching data from api.
Here is my code:
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { FlatList } from "react-native";
import {
stepDone,
setInputData,
} from "../../actions";
import Options from "../../components/Options";<----- It is a component Here define the flatlist
class Qualifier extends React.Component {
_afterQualifierSelected = (id, name) => {
const { stepDone, setInputData, input, updateSymptom } = this.props,
symptomId = input.symptomIds[input.symptomIds.length - 1],
currentSymptom = input.symptoms.filter(entry => entry.id === symptomId),
symptom = currentSymptom ? currentSymptom[0] : {};
symptom.qualifier = id;
setInputData("qualifier", { id, name });
stepDone("qualifierSelected");
};
render = () => {
return (
<Options
data={this.qualifiers}
exclusive={true}
onSelect={this._afterQualifierSelected}
/>
)
}
}
options.js
export default class Options extends React.PureComponent {
static propTypes = {
data: PropTypes.array.isRequired,
extraData: PropTypes.object,
exclusive: PropTypes.bool.isRequired,
onSelect: PropTypes.func.isRequired,
onDone: PropTypes.func
};
_keyExtractor = (item, index) =>
"undefined" === typeof item.id
? index.toString()
: "string" === typeof item.id
? item.id
: item.id.toString();
_renderItem = ({ item }) => {
const { onSelect } = this.props;
return <Item id={item.id} text={item.name} onPress={onSelect} />;
};
render = () => {
const { data, extraData, exclusive, onDone } = this.props;
return (
<View style={styles.container}>
<View style={styles.listContainer}>
<FlatList
data={data}
extraData={extraData}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
</View>
{!exclusive && (
<Button
style={styles.done}
onPress={onDone}
title={Language.done}
accessibilityLabel={Language.optionsDoneAccessibility}
/>
)}
</View>
);
};
}
How should i implement multi select in this flat list using react native?
Can somebody help me to solve this issue?

Check out the multi-select-flatlist example from the official docs. The ListItems are components with their own state & props. You can pass them down whenever your List-component receives a click and do some magic with conditional rendering.

Related

Struggling to pass an index of an array as props to another component

I am trying to build an app in which a user can add a card to an array of cards, then switch the positions of a specific card with the card to the left or right. I wrote a function that I believe will switch a card with that on the left, but I am struggling to debug it because it seems that the index of the chosen card is not properly being passed down to the child component.
Here is my code so far:
CardList.js is what is attempting to pass the moveLeft method to cardItem
import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';
class CardList extends React.Component {
state = {
cards: JSON.parse(localStorage.getItem(`cards`)) || []
// when the component mounts, read from localStorage and set/initialize the state
};
componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
localStorage.setItem(
`cards`,
JSON.stringify(this.state.cards)
);
}
render() {
const cards = this.getCards();
const cardNodes = (
<div style={{ display: 'flex' }}>{cards}</div>
);
return (
<div>
<CardForm addCard={this.addCard.bind(this)} />
<div className="container">
<div className="card-collection">
{cardNodes}
</div>
</div>
</div>
);
}
addCard(name) {
const card = {
name
};
this.setState({
cards: this.state.cards.concat([card])
}); // new array references help React stay fast, so concat works better than push here.
}
removeCard(index) {
this.state.cards.splice(index, 1)
this.setState({
cards: this.state.cards.filter(i => i !== index)
})
}
moveLeft(index, card) {
this.setState((prevState, prevProps) => {
return {cards: prevState.cards.map(( c, i)=> {
// also handle case when index == 0
if (i === index) {
return prevState.cards[index - 1];
} else if (i === index - 1) {
return prevState.cards[index];
}
})};
});
}
//moveRight(index, card) {
// ?
// }
getCards() {
return this.state.cards.map((card) => {
return (
<CardItem
card={card}
index={card.index}
name={card.name}
removeCard={this.removeCard.bind(this)}
moveLeft={this.moveLeft.bind(this)}
// moveRight={this.moveRight}
/>
);
});
}
}
export default CardList;
cardItem is struggling to find the index of the necessary card even though I passed that in as props. I am getting an error saying "×
TypeError: Cannot read property 'index' of undefined" originating from my CardList component.
import React from 'react';
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
class CardItem extends React.Component {
render() {
return (
<div>
<Card style={{ width: '15rem'}}>
<Card.Header as="h5">{this.props.name}</Card.Header>
<Card.Body>
<Button variant="primary" onClick={this.handleClick.bind(this)}>Remove</Button>
</Card.Body>
<Card.Footer style={{ display: 'flex' }}>
<i class="arrow left icon" onClick={this.leftClick.bind(this)} style={{ color: 'blue'}}></i>
{/*<i class="arrow right icon" onClick={rightClick(index, card)} style={{ color: 'blue'}}></i>*/}
</Card.Footer>
</Card>
</div>
)
}
handleClick(index) {
this.props.removeCard(index)
}
leftClick(index, card) {
this.props.moveLeft(index,card)
}
rightClick(index, card) {
this.props.moveRight(index, card)
}
}
export default CardItem;
How can I best pass down the necessary index as props? Thank you
Edit #1
I made an error in my addCard method, I never assigned the index to the card. I have fixed this and added a key property in my map return function but am now getting an error saying "×
TypeError: Cannot read property 'name' of undefined"
Please see the updated CardList.js below:
import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';
class CardList extends React.Component {
state = {
cards: JSON.parse(localStorage.getItem(`cards`)) || []
// when the component mounts, read from localStorage and set/initialize the state
};
componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
localStorage.setItem(
`cards`,
JSON.stringify(this.state.cards)
);
}
render() {
const cards = this.getCards();
const cardNodes = (
<div style={{ display: 'flex' }}>{cards}</div>
);
return (
<div>
<CardForm addCard={this.addCard.bind(this)} />
<div className="container">
<div className="card-collection">
{cardNodes}
</div>
</div>
</div>
);
}
addCard(name, index) {
const card = {
name,
index
};
this.setState({
cards: this.state.cards.concat([card])
}); // new array references help React stay fast, so concat works better than push here.
}
removeCard(index) {
this.state.cards.splice(index, 1)
this.setState({
cards: this.state.cards.filter(i => i !== index)
})
}
moveLeft(index, card) {
this.setState((prevState, prevProps) => {
return {cards: prevState.cards.map(( c, i)=> {
// also handle case when index == 0
if (i === index) {
return prevState.cards[index - 1];
} else if (i === index - 1) {
return prevState.cards[index];
}
})};
});
}
//moveRight(index, card) {
// ?
// }
getCards() {
return this.state.cards.map((card) => {
return (
<CardItem
card={card}
key={card.index}
name={card.name}
removeCard={this.removeCard.bind(this)}
moveLeft={this.moveLeft.bind(this)}
// moveRight={this.moveRight}
/>
);
});
}
}
export default CardList;
There is a problem with your addCard & removeCard functions. State updates may be asynchronous in React, due to which you should not use this.state inside this.setState.
Eg: addCard should be as follows:
addCard(name, index) {
let card = {name,index};
this.setState((prevState, prevProps)=> {
return prevState.cards.concat(card);
})
}
removeCard should be modified likewise. The splice should be removed too, as the filter does the removing.
removeCard(index) {
this.setState(function(prevState,prevProps) {
return {cards: prevState.cards.filter(function(card,i) {
return i != index;
})};
});
}

I want to click a parent component to render a child component in react

I want to click on an option in the menu. This options should then display all the items associated with that option in the child component. I know I am going wrong in two places. Firstly, I am going wrong in the onClick function. Secondly, I am not sure how to display all the items of ONLY the option (Eg.Brand, Holiday destination, Film) that is clicked in the child component. Any help would be greatly appreciated.
horizantalscroll.js
import React from 'react';
import ScrollMenu from 'react-horizontal-scrolling-menu';
import './hrizontalscroll.css';
import Items from './items';
// list of items
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return (
<div
className="menu-item"
>
{text}
</div>
);
};
onclick(){
this.onClick(name)}
}
// All items component
// Important! add unique key
export const Menu = (list) => list.map(el => {
const { name } = el;
return (
<MenuItem
text={name}
key={name}
onclick={this.onclick.name}
/>
);
});
const Arrow = ({ text, className }) => {
return (
<div
className={className}
>{text}</div>
);
};
const ArrowLeft = Arrow({ text: '<', className: 'arrow-prev' });
const ArrowRight = Arrow({ text: '>', className: 'arrow-next' });
class HorizantScroller extends React.Component {
state = {
selected: 0,
statelist: [
{name: "Brands",
items: ["1", "2", "3"]
},
{name: "Films",
items: ["f1", "f2", "f3"]
},
{name: "Holiday Destination",
items: ["f1", "f2", "f3"]
}
]
};
onSelect = key => {
this.setState({ selected: key });
}
render() {
const { selected } = this.state;
// Create menu from items
const menu = Menu(this.state.statelist, selected);
const {statelist} = this.state;
return (
<div className="HorizantScroller">
<ScrollMenu
data={menu}
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={this.onSelect}
/>
<items items={items}/>
</div>
);
}
}
export default HorizantScroller;
items.js
import React, { Component } from "react";
import HorizontalScroller from "horizontalscroll.js";
class Items extends React.Component{
render() {
return (
<div>
{this.statelist.items.map({items, name}) =>
name === this.statelist.name && <div>{items}</div>
}
</div>
);
}
}
export default Items;
The answer is in passing to your Items component the correct array it needs to show. You are already creating a state variable for what's selected. So it would be along the lines of:
<Items items={items[selected]}/>

React Native: Component rerender but props has not changed

I'm encountering this strange issue that I can figure out why is happing.
This should not be happening since the prop passed down to the History component has not been updated.
./components/History.js
...
const History = ({ previousLevels }) => {
return (
<ScrollView style={styles.container}>
{previousLevels.reverse().map(({ date, stressValue, tirednessValue }) => {
return (
<CardKBT
key={date}
date={date}
stressValue={stressValue}
tirednessValue={tirednessValue}
/>
)
})}
</ScrollView>
)
}
...
export default History
As can be seen in this code (below), the prop to the History is only updated once the user press Save.
App.js
import React from 'react'
import { View, ScrollView, StyleSheet } from 'react-native'
import { AppLoading, Font } from 'expo'
import Store from 'react-native-simple-store'
import { debounce } from 'lodash'
import CurrentLevels from './components/CurrentLevels'
import History from './components/History'
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoadingComplete: false,
currentLevels: {
stressValue: 1,
tirednessValue: 1,
},
previousLevels: [],
}
this.debounceUpdateStressValue = debounce(this.onChangeStressValue, 50)
this.debounceUpdateTirednessValue = debounce(
this.onChangeTirednessValue,
50
)
}
async componentDidMount() {
const previousLevels = await Store.get('previousLevels')
if (previousLevels) {
this.setState({ previousLevels })
}
}
render() {
const { stressValue, tirednessValue } = this.state.currentLevels
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
...
/>
)
} else {
return (
<View style={{ flex: 1 }}>
<CurrentLevels
stressValue={stressValue}
onChangeStressValue={this.debounceUpdateStressValue}
tirednessValue={tirednessValue}
onChangeTirednessValue={this.debounceUpdateTirednessValue}
onSave={this.onSave}
/>
<History previousLevels={this.state.previousLevels} />
</View>
)
}
}
...
onChangeStressValue = stressValue => {
const { tirednessValue } = this.state.currentLevels
this.setState({ currentLevels: { stressValue, tirednessValue } })
}
onChangeTirednessValue = tirednessValue => {
const { stressValue } = this.state.currentLevels
this.setState({ currentLevels: { stressValue, tirednessValue } })
}
onSave = () => {
Store.push('previousLevels', {
date: `${new Date()}`,
...this.state.currentLevels,
}).then(() => {
Store.get('previousLevels').then(previousLevels => {
this.setState({
currentLevels: { stressValue: 1, tirednessValue: 1 },
previousLevels,
})
})
})
}
}
The component will re-render when one of the props or state changes, try using PureComponent or implement shouldComponentUpdate() and handle decide when to re-render.
Keep in mind, PureComponent does shallow object comparison, which means, if your props have nested object structure. It won't work as expected. So your component will re-render if the nested property changes.
In that case, you can have a normal Component and implement the shouldComponentUpdate() where you can tell React to re-render based on comparing the nested properties changes.

How to implement search logic in javascript/react native/redux

I want to implement search logic that will be in some method, and then I would pass it to the TextInput props 'onChangeText'. I guess that I should iterate through array 'popularMovies' and find if my input value match the specific title. The problem is that I am not sure how that should look.
Thank you in advance!
import React, { Component } from 'react';
import { FlatList, View, StatusBar, TextInput } from 'react-native';
import { bindActionCreators } from 'redux';
import type { Dispatch as ReduxDispatch } from 'redux';
import { connect } from 'react-redux';
import { fetchPopularMovies } from '../../actions/PopularMovieActions';
import addToFavourite from '../../actions/FavouriteMovieActions';
import MovieCard from '../../components/movieCard/MovieCard';
type Props = {
fetchPopularMovies: Function,
popularMovies: Object,
navigation: Object,
}
class ListOfPopularContainer extends Component<Props> {
state = {
refreshing: false,
text: '',
}
componentDidMount() {
this.props.fetchPopularMovies();
}
search(text) {
this.setState({ text });
// SEARCH LOGIC SHOULD GO HERE
}
onRefresh() {
this.setState({ refreshing: true });
this.props.fetchPopularMovies();
this.setState({ refreshing: false });
}
render() {
const { popularMovies, navigation } = this.props;
return (
<View>
<TextInput
placeholder="Search movie"
onChangeText={ (text) => this.search(text) }
value={this.state.text}
/>
<StatusBar
translucent
backgroundColor="transparent"
barStyle="light-content"
/>
<FlatList
onRefresh={() => this.onRefresh()}
refreshing={this.state.refreshing}
data={popularMovies}
keyExtractor={item => item.title}
renderItem={({ item }) => (
<MovieCard
addToFavourite={() => this.props.addToFavourite(item)}
navigation={navigation}
card={item}
/>
)}
/>
</View>
);
}
}
const mapStateToProps = state => ({
popularMovies: state.popularMovies.popularMovies,
});
const mapDispatchToProps = (dispatch: ReduxDispatch): Function => (
bindActionCreators({ fetchPopularMovies, addToFavourite }, dispatch)
);
export default connect(mapStateToProps, mapDispatchToProps)(ListOfPopularContainer);
I'm unsure what you're asking, your setup is correct and this looks good. Do you want to know how to filter through your popular movies? This would be one way of implementing it with vanilla JS.
search(text) {
this.setState({ text });
// SEARCH LOGIC SHOULD GO HERE
let searchedMovies = this.state.popularMovies.filter(ele =>
ele.title.includes(text))
this.setState({searchedMovies})
}
you should check out lodash. two functions in particular:
Includes
https://lodash.com/docs/4.17.10#includes
this function checks if one string is included in another string.
import { includes } from 'lodash';
search(text) {
var searchResults = [];
var { popularMovies } = this.props;
for (var i = popularMovies.length -1; i >= 0; i--) {
includes(popularMovies[i], text) && searchResults.push(popularMovies[i])
}
return searchResults;
}
debounce
https://lodash.com/docs/4.17.10#debounce
this function throttles a function so as the user is typing it will regularly search at the phrase at reasonable intervals
debounce(search(text))

React Native multiple switches

On React-Native, I'm trying to create a screen with multiple switch components, with the possibility of selecting only one at once. When the component loads, only the first switch in on. if you click on it, it turns to off, but if you turn on another one, all the others turn to off.
I'm not sure I have the right approach here, as I'm confused about how to use the component state to do this.
In JS, I would do something like a function that turns all switch to off, but turn on the clicked one, but I don't understand how to this with state.
thanks in advance
import React from 'react'
import { ScrollView, Text, View, Switch } from 'react-native'
class switchScreen extends React.Component {
constructor (props) {
super(props)
this.state = {
trueSwitchIsOn: true,
falseSwitchIsOn: false
}
}
switch = (value) => {
this.setState({ falseSwitchIsOn: value, trueSwitchIsOn: !value })
}
render () {
return (
<View>
<Switch
onValueChange={this.switch}
value={this.state.trueSwitchIsOn}
/>
<Switch
onValueChange={this.switch}
value={this.state.falseSwitchIsOn}
/>
<Switch
onValueChange={this.switch}
value={this.state.falseSwitchIsOn}
/>
</View>
)
}
}
I believe a more optimal solution would minimize the amount of state, and possibility of inconsistent data. Using one state variable to keep track of which switch is active (if any) can solve your problem pretty easily.
import React from 'react'
import { ScrollView, Text, View, Switch } from 'react-native'
class switchScreen extends React.Component {
constructor (props) {
super(props)
this.state = {
activeSwitch: null,
}
}
// A simple toggle method that takes a switch number
// And toggles it between on / off
toggleSwitch = (switchNumber) => {
this.setState({
activeSwitch: switchNumber === this.state.activeSwitch ? null : switchNumber
})
};
//
switchOne = (value) => { this.toggleSwitch(1) };
switchTwo = (value) => { this.toggleSwitch(2) };
switchThree = (value) => { this.toggleSwitch(3) };
render () {
return (
<View>
<Switch
onValueChange={this.switchOne}
value={this.state.activeSwitch === 1}
/>
<Switch
onValueChange={this.switchTwo}
value={this.state.activeSwitch === 2}
/>
<Switch
onValueChange={this.switchThree}
value={this.state.activeSwitch === 3}
/>
</View>
)
}
}
import React from 'react'
import { ScrollView, Text, View, Switch } from 'react-native'
class switchScreen extends React.Component {
constructor (props) {
super(props)
this.state = {
switchone:false,
switchtwo:false,
switchthree:false,
}
}
switchOne = (value) => {
this.setState({ switchone: !value,
switchtwo:false,switchthree:false,
})
}
switchTwo = (value) => {
this.setState({ switchtwo: !value,
switchone:false,switchthree:false,
})
}
switchThree = (value) => {
this.setState({ switchtree: !value,
switchone:false,switchtwo:false,
})
}
render () {
return (
<View>
<Switch
onValueChange={this.switchOne}
value={this.state.switchone}
/>
<Switch
onValueChange={this.switchTwo}
value={this.state.switchtwo}
/>
<Switch
onValueChange={this.switchThree}
value={this.state.switchthree}
/>
</View>
)
}
}
You can try something like below, you can keep array of switch states, in the example its an associative key, but you can change according to your need, with multi level switch states. (pardon the code formatting but it give you the idea)
constructor(props) {
super(props);
this.state = {
switchStates: {
companyName: true
}
}
}
toggle(item, index) {
const switchStates = this.state.switchStates;
switchStates[index] = !switchStates[index];
console.log(switchStates);
this.setState({ switchStates });}
render switch
<Switch
onValueChange={isSwitchOn =>
this.toggle({ isSwitchOn }, "companyName")
}
value={this.state.switchStates.companyName}
/>

Categories

Resources