How to convert react native signature canvas functional component to class component - react-native

I am new to react native. I do not know how to convert Functional component to class component. please help. here is my code of react native signature canvas code which is in functional component I want to convert it to class component please help thanks.
const ref = useRef();
const handleSignature = signature => {
const path = FileSystem.cacheDirectory + 'sign.png';
FileSystem.writeAsStringAsync(path, signature.replace('data:image/png;base64,', ''), {encoding: FileSystem.EncodingType.Base64}).then(res => {
// console.log(res);
// FileSystem.getInfoAsync(path, {size: true, md5: true}).then(file => {
FileSystem.getInfoAsync(path).then(file => {
console.log(file);
setSingleFileSIGN({ singleFileSIGN: file.uri});
console.log(singleFileSIGN)
})
}).catch(err => {
console.log("err", err);
})
};
const handleEmpty = () => {
console.log('Empty');
};
const handleClear = () => {
console.log('clear success!');
};
const handleEnd = () => {
ref.current.readSignature();
};
<View style={{flex: 1, width:355,
...Platform.select({
android: {
marginBottom:-80,
borderColor: '#FF8C00',
borderWidth:1
// marginBottom:-150
},
}),
}}>
<SignatureScreen style={{height: '400%'}}
ref={ref}
onEnd={handleEnd}
onOK={handleSignature}
onEmpty={handleEmpty}
onClear={handleClear}
descriptionText={'Sign here!'}
/>
</View>

const signatureRef = createRef()
clearSignature = async () => {
await signatureRef.current?.clearSignature()
}
handleOK = async (signature) => {
const sign = signature.split(",").pop()
await this.setState({
signatureVal: sign
})
// onOK(signature); // Callback from Component props
};
handleEnd = async () => {
await this.setState({isScrollEnabled: true})
await signatureRef.current?.readSignature()
}
render(){
<View>
<SignatureScreen
ref={signatureRef}
onOK={this.handleOK}
onEnd={this.handleEnd}
androidHardwareAccelerationDisabled={true}
onBegin={async () => {await this.setState({
isScrollEnabled: false
})}}
/>
</View>
}

Related

Flatlist is very slow in using big data in react native

i have a big data list of products thats paginate, in every page it load 10 item, but when i add new items to itemlist,flatlist gets very slow,As the number of pages increases, so does the loading time of new products,The function of the choose button is also slowed down.
How to speed up loading I tried all the best methods but it still did not work. Did not React Native really solve this problem?
export default function Products(props) {
const toast = useToast();
const [isLoading, setSetIsLoading] = useState(true);
const [items, setItems] = useState([]);
const [fetchStatus, setFetchStatus] = useState(false);
const [page, setPage] = useState(1);
const [sending, setSending] = useState(false);
async function getProducts() {
let token = await AsyncStorage.getItem('#token');
let data = {
token: token,
page: page,
};
await get_products(data)
.then(res => {
setItems([...items, ...res.data.data.docs]);
setPage(res.data.data.nextPage);
})
.catch(err => {
console.log(err);
});
}
async function getNextPage() {
let token = await AsyncStorage.getItem('#token');
let data = {
token: token,
page: page,
};
await get_products(data)
.then(res => {
setItems([...items, ...res.data.data.docs]);
setPage(res.data.data.nextPage);
})
.catch(err => {
console.log(err);
});
}
async function selectProduct(id) {
setSending(true);
console.log({id});
let token = await AsyncStorage.getItem('#token');
let data = {
product_id: id
};
await select_products(data,token).then(res => {
toast.show({
description:res.data.message
})
setSending(false);
}).catch(rej => {
console.log({rej})
toast.show({
description:rej?.response?.data.message,
})
setSending(false);
})
}
useFocusEffect(
React.useCallback(() => {
getProducts();
return () => {
setItems([]);
setPage();
};
}, []),
);
renderItem =({item}) => (
<Card
selectProduct={id => selectProduct(id)}
sending={sending}
obj={item}
/>
)
return (
<View mb={20}>
<FlatList
data={items}
extraData={items}
removeClippedSubviews={true}
renderItem={renderItem}
keyExtractor={(item) => `${item._id}-item`}
onEndReached={getNextPage}
maxToRenderPerBatch="13"
ListFooterComponent={() => {
return <ActivityIndicator color="orange" size="large" />;
}}></FlatList>
</View>
);
}
Did you use **map method **?
It can help you for more easily loading data

Invalid hook call. Hooks can only be called inside of the body of a function component. in react native signature canvas

I am new to react native. I have react native signature canvas code which is in functional component but now I write that code in my class component code. then I am getting error like this = Invalid hook call. Hooks can only be called inside of the body of a function component. so whats the issue please help.
here is code
export default class Kyc extends Component {
constructor(props) {
super(props);
this.state = {
singleFileSIGN:''
};
}
ref = useRef();
handleSignature = (signature) => {
const path = FileSystem.cacheDirectory + 'sign.png';
FileSystem.writeAsStringAsync(path, signature.replace('data:image/png;base64,', ''), {encoding: FileSystem.EncodingType.Base64}).then(res => {
// console.log(res);
// FileSystem.getInfoAsync(path, {size: true, md5: true}).then(file => {
FileSystem.getInfoAsync(path).then(file => {
console.log(file);
this.setState({ singleFileSIGN: file.uri});
console.log(singleFileSIGN)
})
}).catch(err => {
console.log("err", err);
})
};
handleEmpty () {
console.log('Empty');
};
handleClear () {
console.log('clear success!');
};
handleEnd () {
ref.current.readSignature();
};
render () {
return (
<View style={styles.container}>
<View style={{flex: 1, width:355,
...Platform.select({
android: {
marginBottom:-80,
borderColor: '#FF8C00',
borderWidth:1
// marginBottom:-150
},
}),
}}>
<SignatureScreen style={{height: '400%'}}
ref={this.ref}
onEnd={this.handleEnd}
onOK={this.handleSignature}
onEmpty={this.handleEmpty}
onClear={this.handleClear}
descriptionText={'Sign here!'}
/>
</View>
</View>
);
}
}
Hooks only used in function components. In class use like this:
constructor(props) {
super(props);
this.ref = React.createRef();
}

React Apollo fetch once

Im working on simply COVID-19 tracker and i have a problem.
Is there any option in Apollo for React to fetch graphql data once per button press?
Now i have TextInput and Button but when i fetch data once i can't type another country in input because i have immediately error.
const Tile = () => {
const [country, setCountry] = useState('Poland');
const [cases, setCases] = useState(0);
const MY_QUERY = gql`
query getCountryStats($country: String!) {
country(name: $country) {
todayCases
}
}
`;
const [getCountryStats, {data, loading, error}] = useLazyQuery(MY_QUERY, {
variables: {
country: country,
},
onCompleted: (data) => {
setCases(data.country.todayCases);
},
});
if (loading) return <Text>LOADING...</Text>;
if (error) return <Text>Error!</Text>;
return (
<View>
<CasesNumber>{cases}</CasesNumber>
<FinderWrapper>
<FinderInput
onChangeText={(text) => {
setCountry(text);
}}
/>
<FinderButton
onPress={() => {
getCountryStats();
}}>
<Text>FIND</Text>
</FinderButton>
</FinderWrapper>
</View>
);
};
export default Tile;
Try using this
const Tile = () => {
const [country, setCountry] = useState('Poland');
const [cases, setCases] = useState(0);
let inputValue = ‘’;
const MY_QUERY = gql`
query getCountryStats($country: String!) {
country(name: $country) {
todayCases
}
}
`;
const [getCountryStats, {data, loading, error}] = useLazyQuery(MY_QUERY, {
variables: {
country: country,
},
onCompleted: (data) => {
setCases(data.country.todayCases);
},
});
const onGetCountryStats = () => {
setCountry(inputValue);
getCountryStats();
}
if (loading) return <Text>LOADING...</Text>;
if (error) return <Text>Error!</Text>;
return (
<View>
<CasesNumber>{cases}</CasesNumber>
<FinderWrapper>
<FinderInput
onChangeText={(text) => {
inputValue = text;
}}
/>
<FinderButton
onPress={() => {
onGetCountryStats();
}}>
<Text>FIND</Text>
</FinderButton>
</FinderWrapper>
</View>
);
};
export default Tile;

What is the best way to implement CRUD functionality with pagination in a FlatList?

I am currently building a very basic CRUD app in which users can seen post, delete, and update different recommendations. In the long term, it is best to use pagination rendering with lots of data just so I don't have to fetch a lot of recommendations at once.
I have an issue when I add or create a new recommendation. My app gets out of sync with my backend's pagination because it removes/adds an item from the database. What is the best way to implement the CRUD flow with a FlatList and Pagination?
Here is my recommendations reducer, actions, and FlatList:
Reducer:
import {
SET_INITIAL_RECOMMENDATIONS,
SET_NEXT_RECOMMENDATIONS,
CREATE_RECOMMENDATION,
UPDATE_RECOMMENDATION,
DELETE_RECOMMENDATION,
} from "../actions/recommendations";
const recommendations = (state = [], action) => {
switch (action.type) {
case SET_INITIAL_RECOMMENDATIONS:
return action.recommendations;
case SET_NEXT_RECOMMENDATIONS:
return [...state, ...action.nextRecommendations];
case CREATE_RECOMMENDATION:
state.pop();
return [action.recommendation, ...state];
case UPDATE_RECOMMENDATION:
const recommendationIndex = state.findIndex(
(recommendation) => recommendation.id === action.recommendationId
);
const updatedRecommendations = [...state];
updatedRecommendations[recommendationIndex] = action.recommendation;
return updatedRecommendations;
case DELETE_RECOMMENDATION:
return state.filter(
(recommendation) => recommendation.id !== action.recommendationId
);
default:
return state;
}
};
export default recommendations;
Actions:
export const fetchInitialRecommendations = () => {
return (dispatch) => {
dispatch({ type: IS_LOADING });
fetch(`${BASE_URL}/recommendations?page=1`)
.then((resp) => resp.json())
.then((recommendations) => {
dispatch({
type: SET_INITIAL_RECOMMENDATIONS,
recommendations: recommendations,
});
dispatch({ type: IS_NOT_LOADING });
})
.catch((err) => console.log(err));
};
};
export const fetchNextRecommendations = (pageNumber) => {
return (dispatch) => {
dispatch({ type: RECOMMENDATIONS_ARE_LOADING });
fetch(`${BASE_URL}/recommendations?page=${pageNumber}`)
.then((resp) => resp.json())
.then((recommendations) => {
dispatch({
type: SET_NEXT_RECOMMENDATIONS,
nextRecommendations: recommendations,
});
dispatch({ type: RECOMMENDATIONS_ARE_NOT_LOADING });
})
.catch((err) => console.log(err));
};
};
export const deleteRecommendation = (recommendationId, navigation) => {
return (dispatch, getState) => {
const userToken = getState().loggedInUser.jwt;
const reqObj = {
method: "DELETE",
headers: {
Authorization: `Berear ${userToken}`,
Accepts: "application/json",
},
};
fetch(`${BASE_URL}/recommendations/${recommendationId}`, reqObj)
.then((resp) => resp.json())
.then((data) => {
if (data.status !== 200) {
Alert.alert("Please Try Again.", data.error_messages[0], [
{ title: "OK" },
]);
} else {
dispatch({
type: DELETE_RECOMMENDATION,
recommendationId: recommendationId,
});
navigation.goBack();
}
})
.catch((err) => console.log(err));
};
};
My FlatList implementation:
import React, { Component, Fragment } from "react";
import {
View,
StyleSheet,
FlatList,
ActivityIndicator,
RefreshControl,
} from "react-native";
import { connect } from "react-redux";
import {
fetchInitialRecommendations,
refreshInitialRecommendations,
fetchNextRecommendations,
} from "../../../store/actions/recommendations";
import RecommendationCard from "../../../components/UI/browser/recommendations/RecommendationCard";
import colors from "../../../constants/colors";
class BrowserRecommendationsScreen extends Component {
constructor() {
super();
this.state = {
page: 1,
refreshing: false,
};
}
componentDidMount() {
this.props.fetchInitialRecommendations();
}
handleRefresh = () => {
this.setState({ refershing: true }, () =>
this.props.refreshInitialRecommendations()
);
this.setState({ page: 1, refreshing: false });
};
loadMoreRecommendations = () => {
this.setState(
{
page: this.state.page + 1,
},
() => this.props.fetchNextRecommendations(this.state.page)
);
};
newRecommedationOnPress = () => {
this.props.navigation.navigate("BrowserNewRecommendation");
};
render() {
if (this.props.loader) {
return (
<View style={styles.activityIndicatorScreen}>
<ActivityIndicator size="large" color={colors.primaryColor} />
</View>
);
}
return (
<View style={styles.container}>
<FlatList
contentContainerStyle={styles.flatList}
data={this.props.recommendations}
renderItem={(item) => (
<RecommendationCard
key={item.id}
id={item.id}
recommendationData={item}
navigation={this.props.navigation}
/>
)}
keyExtractor={(item) => item.id.toString()}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
onRefresh={() => this.handleRefresh()}
refreshing={this.props.recommendationsAreRefreshing}
/>
}
onEndReachedThreshold={0}
onEndReached={this.loadMoreRecommendations}
extraData={this.props.recommendations}
/>
</View>
);
}
}
const styles = StyleSheet.create({
activityIndicatorScreen: {
flex: 1,
justifyContent: "center",
alignContent: "center",
backgroundColor: colors.secondaryLight,
},
container: {
flex: 1,
width: "100%",
backgroundColor: colors.secondaryLight,
},
flatList: {
justifyContent: "center",
alignItems: "center",
width: "100%",
paddingBottom: 20,
},
});
const mapStateToProps = (state) => {
return {
loader: state.loader,
recommendationsAreLoading:
state.recommendationsLoader.recommendationsAreLoading,
recommendationsAreRefreshing:
state.recommendationsLoader.recommendationsAreRefreshing,
recommendations: state.recommendations,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchInitialRecommendations: () => dispatch(fetchInitialRecommendations()),
refreshInitialRecommendations: () =>
dispatch(refreshInitialRecommendations()),
fetchNextRecommendations: (pageNumber) =>
dispatch(fetchNextRecommendations(pageNumber)),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(BrowserRecommendationsScreen);
This goes back to my original question, what is the best way to implement CRUD functionality with FlatList and pagination? Or is there any work arounds? Thanks!
In my case, every time there is an action like edit, delete or add new item. I always load data from the begining (page 1) after I get response success from that action.

Enzyme + React Native + Jest: How to `console.log()` the content of a `<Text />`?

I'm testing the content of a <Text /> tag in React Native using Enzyme and Jest. My problem is the that the test is failing (even though everything empirically works and even though I feel like I wrote the test correctly). Here is the test:
describe("when less than minimum mandatory chosen", () => {
it("should render a label saying choose at least X items", () => {
console.log(wrapper);
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${props.minChoices}.`)
).toBe(true);
});
});
I would like to check what the actual string within the <Text /> tag is. How could I achieve this?
As per request by Brian, here is the full test code:
import React from "react";
import { shallow } from "enzyme";
import { Text, View } from "react-native";
import {
SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY,
SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST
} from "../../config/constants/screenTexts";
import { AccordionList } from "./AccordionList";
import styles from "./styles";
const createTestProps = props => ({
header: "Kekse",
items: [
{
uuid: "1057e751-8ef1-4524-a743-1b4ba7b33d7b",
name: "Haferkeks",
price: "2.00",
priceCurrency: "EUR"
},
{
uuid: "f41f8e1a-b526-490e-ba4a-3d6acb3f3c16",
name: "Schokosojakeks",
price: "1.50",
priceCurrency: "EUR"
}
],
chosenItems: [],
onItemPressed: jest.fn(),
...props
});
describe("AccordionList", () => {
describe("rendering", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<AccordionList {...props} />);
});
it("should render container", () => {
expect(
wrapper
.find(View)
.at(0)
.prop("style")
).toContain(styles.container);
});
it("should render a <Collapsible />", () => {
expect(wrapper.find("Collapsible")).toHaveLength(1);
});
it("should give the header's <TouchableOpacity /> the headerbutton style", () => {
expect(
wrapper
.find("TouchableOpacity")
.at(0)
.prop("style")
).toEqual(styles.headerButton);
});
it("should render a header", () => {
expect(
wrapper
.find(Text)
.at(0)
.contains(props.header)
).toBe(true);
});
it("should give the header the header style", () => {
expect(
wrapper
.find(Text)
.at(0)
.prop("style")
).toEqual(styles.header);
});
it("should render a subheader", () => {
expect(
wrapper
.find(Text)
.at(1)
.prop("style")
).toContain(styles.subHeader);
});
it("should render a <TouchableOpacity /> for each of it's items", () => {
expect(wrapper.find("TouchableOpacity")).toHaveLength(props.items.length + 1);
});
describe("folded", () => {
it("should render an arrow pointing to the right", () => {
expect(wrapper.find("Image").prop("source")).toEqual(
require("../../assets/icons/rightArrow.png")
);
});
it("should render the folded arrow with the default style", () => {
expect(wrapper.find("Image").prop("style")).toEqual([styles.arrowIcon, styles.inActive]);
});
describe("mandatory", () => {
beforeEach(() => {
props = createTestProps({ minChoices: 1 });
wrapper = shallow(<AccordionList {...props} />);
});
it("should render a mandatory label with the minimum number of mandatory items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`(${SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY}, ${props.minChoices})`)
).toBe(true);
});
});
describe("optional", () => {
it("should render an optional label", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`(${SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL})`)
).toBe(true);
});
});
});
describe("expanded", () => {
beforeEach(() => {
wrapper.setState({ collapsed: false });
});
it("should render an arrow pointing down", () => {
expect(wrapper.find("Image").prop("source")).toEqual(
require("../../assets/icons/downArrow.png")
);
});
it("should render the expanded arrow with the default style", () => {
expect(wrapper.find("Image").prop("style")).toEqual([styles.arrowIcon, styles.inActive]);
});
// FIXME: These tests should also work but don't for some reason.
describe("mandatory", () => {
beforeEach(() => {
props = createTestProps({ minChoices: 1 });
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
describe("when less than minimum mandatory chosen", () => {
it("should render a label saying choose at least X items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${props.minChoices}.`)
).toBe(true);
});
});
describe("when more than minimum mandatory chosen", () => {
beforeEach(() => {
props = createTestProps({
minChoices: 1,
chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"]
});
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
it("should render a label saying choose up to X items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
).toBe(true);
});
});
});
describe("optional", () => {
it("should render a label saying choose up to X items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
).toBe(true);
});
});
});
describe("item chosen", () => {
beforeEach(() => {
props = createTestProps({ chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"] });
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
it("should render a checkmark for the item", () => {
expect(
wrapper
.find("Image")
.at(1)
.prop("source")
).toEqual(require("../../assets/icons/checkmark.png"));
});
it("should render the checkmark with the checkmarkIcon and active style", () => {
expect(
wrapper
.find("Image")
.at(1)
.prop("style")
).toEqual([styles.checkmarkIcon, styles.active]);
});
it("should render the folded arrow with the primary style", () => {
expect(
wrapper
.find("Image")
.at(0)
.prop("style")
).toContain(styles.active);
});
it("should render the expanded arrow with the primary style", () => {
wrapper.setState({ collapsed: false });
expect(
wrapper
.find("Image")
.at(0)
.prop("style")
).toContain(styles.active);
});
});
describe("max items chosen", () => {
beforeEach(() => {
props = createTestProps({
maxChoices: 1,
chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"]
});
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
it("should disable all items but the chosen", () => {
expect(
wrapper
.find("TouchableOpacity")
.at(2)
.prop("disabled")
).toEqual(true);
});
});
});
describe("interaction", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<AccordionList {...props} />);
});
// FIXME: This test does not work for some reason...
// describe("pressing the header", () => {
// beforeEach(() => {
// wrapper.instance().toggleExpanded = jest.fn();
// wrapper
// .find("TouchableOpacity")
// .first()
// .prop("onPress")();
// });
//
// it("should call the toggleExpanded() instance function", () => {
// expect(wrapper.instance().toggleExpanded).toHaveBeenCalledTimes(1);
// });
// });
describe("pressing an item", () => {
beforeEach(() => {
wrapper
.find("TouchableOpacity")
.at(1)
.prop("onPress")();
});
it("should call the onItemPressed callback", () => {
expect(props.onItemPressed).toHaveBeenCalledTimes(1);
});
});
});
describe("component methods", () => {
describe("toggleExpanded", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<AccordionList {...props} />);
wrapper.instance().toggleExpanded();
});
it("should change the state of the component to collapsed=false", () => {
expect(wrapper.instance().state.collapsed).toBe(false);
});
});
});
});
And here is the full code of the component:
import React, { PureComponent } from "react";
import { Image, Text, TouchableOpacity, View } from "react-native";
import Collapsible from "react-native-collapsible";
import PropTypes from "prop-types";
import {
SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY,
SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST
} from "../../config/constants/screenTexts";
import styles from "./styles";
export class AccordionList extends PureComponent {
static propTypes = {
header: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
chosenItems: PropTypes.array.isRequired,
onItemPressed: PropTypes.func.isRequired,
minChoices: PropTypes.number,
maxChoices: PropTypes.number,
borderTop: PropTypes.bool,
borderBottom: PropTypes.bool
};
static defaultProps = {
minChoices: 0,
maxChoices: 1,
borderTop: false,
borderBottom: false
};
state = { collapsed: true };
toggleExpanded = () => {
this.setState(state => ({ collapsed: !state.collapsed }));
};
renderContent = () => {
const { items, onItemPressed, chosenItems, maxChoices } = this.props;
return (
<View>
{items.map(item => {
const disabled =
!chosenItems.includes(item.uuid) &&
chosenItems.filter(item => items.map(item => item.uuid).includes(item)).length ===
maxChoices;
return (
<View style={styles.itemContainer} key={item.uuid}>
<TouchableOpacity
onPress={() => onItemPressed(item.uuid)}
style={[styles.itemButton, disabled ? styles.opaque : null]}
disabled={disabled}
>
{item.name && <Text style={styles.itemText}>{item.name}</Text>}
{item.price && (
<View style={styles.priceContainer}>
<Text style={styles.sizeText}>{item.label ? `${item.label} ` : ""}</Text>
<Text style={styles.sizeText}>
{item.size ? `${item.size.size}${item.size.unit}: ` : ""}
</Text>
<Text style={styles.priceText}>{item.price} €</Text>
</View>
)}
<View style={styles.checkMarkContainer}>
{chosenItems.includes(item.uuid) ? (
<Image
source={require("../../assets/icons/checkmark.png")}
resizeMode="contain"
style={[styles.checkmarkIcon, styles.active]}
/>
) : null}
</View>
</TouchableOpacity>
</View>
);
})}
</View>
);
};
render() {
const {
header,
items,
maxChoices,
minChoices,
chosenItems,
borderTop,
borderBottom
} = this.props;
const { collapsed } = this.state;
return (
<View
style={[
styles.container,
borderTop ? styles.borderTop : null,
borderBottom ? styles.borderBottom : null
]}
>
<TouchableOpacity onPress={this.toggleExpanded} style={styles.headerButton}>
<Text style={styles.header}>{header}</Text>
<Text style={[styles.subHeader, minChoices > 0 ? styles.mandatory : styles.optional]}>
{minChoices > 0
? collapsed
? `(${SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY}, ${minChoices})`
: chosenItems.filter(item => items.map(item => item.uuid).includes(item)).length >=
maxChoices
? `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${maxChoices}.`
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}`
: collapsed
? `(${SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL})`
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${maxChoices}.`}
</Text>
<Image
source={
collapsed
? require("../../assets/icons/rightArrow.png")
: require("../../assets/icons/downArrow.png")
}
resizeMode="contain"
style={[
styles.arrowIcon,
chosenItems.length > 0 &&
chosenItems.some(item => items.map(item => item.uuid).includes(item))
? styles.active
: styles.inActive
]}
/>
</TouchableOpacity>
<Collapsible collapsed={collapsed}>{this.renderContent()}</Collapsible>
</View>
);
}
}
export default AccordionList;
Looks like it's just a typo, you're missing a . in the component code.
Change this line:
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}`
...to this:
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}.`
One other thing I noticed, your component uses a default for maxChoices of 1, but in your test there are two spots where you are referencing props.maxChoices where it hasn't been set. You'll probably want to change the two lines like this:
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
..to this:
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices || 1}.`)
to reflect the default assigned by the component.