How to loop and render elements in React-native? - react-native

Is it possible to loop an identical component in Render function?
Something like this:
...
onPress = () => {
...
};
initialArr = [["blue","text1"],["red","text2"]];
buttonsListArr = [];
for (let i = 0; i < initialArr.length; i++)
{
buttonsListArr.push(
<Button style={{borderColor:{initialArr[i][0]}}} onPress={this.onPress.bind(this)}>{initialArr[i][1]}</Button>
);
}
...
render() {
return (
<View style={...}>
{buttonsListArr}
</View>
)};
I mean this is just finite list of components, so any components like ListView/ScrollView etc is not applicable in this particular case. This is just syntax question.

You would usually use map for that kind of thing.
buttonsListArr = initialArr.map(buttonInfo => (
<Button ... key={buttonInfo[0]}>{buttonInfo[1]}</Button>
);
(key is a necessary prop whenever you do mapping in React. The key needs to be a unique identifier for the generated component)
As a side, I would use an object instead of an array. I find it looks nicer:
initialArr = [
{
id: 1,
color: "blue",
text: "text1"
},
{
id: 2,
color: "red",
text: "text2"
},
];
buttonsListArr = initialArr.map(buttonInfo => (
<Button ... key={buttonInfo.id}>{buttonInfo.text}</Button>
);

render() {
return (
<View style={...}>
{initialArr.map((prop, key) => {
return (
<Button style={{borderColor: prop[0]}} key={key}>{prop[1]}</Button>
);
})}
</View>
)
}
should do the trick

For initial array, better use object instead of array, as then you won't be worrying about the indexes and it will be much more clear what is what:
const initialArr = [{
color: "blue",
text: "text1"
}, {
color: "red",
text: "text2"
}];
For actual mapping, use JS Array map instead of for loop - for loop should be used in cases when there's no actual array defined, like displaying something a certain number of times:
onPress = () => {
...
};
renderButtons() {
return initialArr.map((item) => {
return (
<Button
style={{ borderColor: item.color }}
onPress={this.onPress}
>
{item.text}
</Button>
);
});
}
...
render() {
return (
<View style={...}>
{
this.renderButtons()
}
</View>
)
}
I moved the mapping to separate function outside of render method for more readable code.
There are many other ways to loop through list of elements in react native, and which way you'll use depends on what do you need to do. Most of these ways are covered in this article about React JSX loops, and although it's using React examples, everything from it can be used in React Native. Please check it out if you're interested in this topic!
Also, not on the topic on the looping, but as you're already using the array syntax for defining the onPress function, there's no need to bind it again. This, again, applies only if the function is defined using this syntax within the component, as the arrow syntax auto binds the function.

If u want a direct/ quick away, without assing to variables:
{
urArray.map((prop, key) => {
console.log(emp);
return <Picker.Item label={emp.Name} value={emp.id} />;
})
}

Related

React Native - Appended component in hook not responding as expected

I am working on a project that uses Google autocomplete to set locations. The project allows users to set pickup and destination location, and then they can also enter stop-by places up to additional 3, making it a total of 5.
Here's my sample code:
const placesRef = useRef([]);
const [stopspots, setStopSpots] = useState([]);
const [state, setState] = useState({
defaultPlacesInput: 'flex',
//and others
});
useEffect(() => {
placesRef.current = placesRef.current.slice(0, 5);
}, []);
const placesComponent = (i, placeholder) => {
return (<PlacesFrame key={i}>
...
<GooglePlacesAutocomplete
placeholder={placeholder}
minLength={2}
ref={el => placesRef.current[i] = el}
onPress={(data, details = null) => {
placesRef.current[i]?.setAddressText(data?.structured_formatting?.main_text);
setState({...state, defaultPlacesInput: 'flex'})
}}
enablePoweredByContainer={false}
fetchDetails
styles={{
textInput: [styles.input1,{paddingLeft:30}],
container: [styles.autocompleteContainer,{display:placesRef.current[i]?.isFocused() ? 'flex' : state.defaultPlacesInput}],
listView: styles.listView,
listView: styles.listView,
row: styles.row,
predefinedPlacesDescription: {
color: '#1faadb',
},
}}
query={{
key: GOOGLE_PLACES_API_KEY,
language: profile.language,
components: 'country:' + profile.iso,
}}
textInputProps={{
//value: '',
onChangeText: alterOtherFields
}}
renderRow={(data) => <PlaceRow data={data} />}
/>
...
</PlacesFrame>)
}
const stopByLocation = () => {
var counter = stopspots.length, obj = placesComponent(counter + 2, 'Drop off location');
setStopSpots([...stopspots, {
id: counter,
place: obj
}
])
}
And here is how the autocomplete component is rendered
return(
...
<View>
{placesComponent(0, 'Pick up location')}
{placesComponent(1, 'Drop off location')}
</View>
...
)
The output look like this
Everything works perfect when I call the placesComponent() function directly. But like I mentioned earlier, I want the users to be able to add up to 3 additional stop by locations, and because it is optional, additional fields is added by appending to hook, and then rendered. the code looks like this.
return(
...
<View>
{placesComponent(0, 'Pick up location')}
{placesComponent(1, 'Drop off location')}
//This will append more placed fields
{stopspots != '' ?
stopspots.map((item : {}) => ((item.place)))
: null}
<ClickableButton>
<TouchableOpacity activeOpacity={0.6} onPress={() => stopByLocation()}><AddPlaces><AntDesign name="plus" size={10} color="#444" /> Add</AddPlaces></TouchableOpacity>
</ClickableButton>
</View>
...
)
The outcome looks like this
I observed that each component binded to the hooks takes the early properties, and does not effect additional changes. While the first two fields rendered by calling the function directly does.
When I make changes to state.defaultPlacesInput (observe this in styles property of GooglePlacesAutocomplete), the changes only effect on the two components called directly.
Is there a module, or a systematic way to append the renderer function call, without using useState hooks to append the 3 additional fields?
Is it possible to expose stored properties in useState hooks to respond as the other two which observe the state changes? If yes, how?
Any contribution, suggestion will be accepted

React-Native-Material-Dropdown not showing data on Front-end

I have used react-native material dropdown to fetch data from my API as follows:
<Dropdown
label='colors'
data={this.state.data.colors}
containerStyle={{width: 50}}
/>
{console.log("sbHASB",this.state.data.colors)}
However when I implement thi, I do get the colors on my log but they do not seem to appear on the list, it seems to be blank, can anyone please tell me why is ot so?
Any help would be great, thank you.
my logs after implementing are as follows:
sbHASB ["Blue", "White", "Blue", "White", "Blue", "White", "Blue", "White"]
Do tell me if you require anything else.
Assuming you are using react-native-material-dropdown, the documentation on their github suggests that the data prop should be a list of objects with a value key. See here the example given.
import React, { Component } from 'react';
import { Dropdown } from 'react-native-material-dropdown';
class Example extends Component {
render() {
let data = [{
value: 'Banana',
}, {
value: 'Mango',
}, {
value: 'Pear',
}];
return (
<Dropdown
label='Favorite Fruit'
data={data}
/>
);
}
}
For your list to work you should transform it to match this format, for example
const data = this.state.data.colors.map((color) => ({value: color}))
Given your example above that could look like
<Dropdown
label='colors'
data={this.state.data.colors.map((color) => ({value: color}))}
containerStyle={{width: 50}}
/>
However I would advise transforming the data before this step, for example when you receive the response from the api.
see this example on snack.io, the dropdown will work best if you preview it on a device since the animation doesn't display properly on the web preview.
https://snack.expo.io/#dannyhw/dropdown
UPDATE:
Here is the updated example that includes an example of how it can be used dynamically
export default class App extends React.Component {
state = {
data: {colors: []}
}
getSomeData() {
// imagine this is your api call here and it returns a promise that resolves to be a list of colours
return Promise.resolve(["green", "White", "Blue", "White", "Blue", "White", "Blue", "White"])
}
componentDidMount(){
this.getSomeData().then((result)=> {
this.setState(state => state.data.colors = result)
})
}
render() {
return (
<View style={styles.container}>
<Dropdown
label='colors'
data={this.state.data.colors.map((color) => ({value: color}))}
containerStyle={{width: 150}}
/>
</View>
);
}
}

How do i call a function outside of Field render method in React-native?

Hi Experts i'm a newbie in react-native, help will appreciated.
I need to call a method name openModel() which is globally declared in Component and i have a method renderInput which renders each Input passed in Field tag. When openModel() is call inside renderInput of its Inputs on Focus. Error shows _this4.openModel is not a function. Its clearly understood that this is getting incremented because of multiple time renderInput method is called.
How do i fix this ?
Below is short code
class AddPatientForm extends Component {
constructor(props) {
super(props);
openModel = () => {
this.refs.bGroup.open();
}
renderInput({ input, label, type, meta: { touched, error, warning } }) {
return (
<View style={{ flexDirection: "row", height: 25, paddingRight: 5, }}>
<Input
ref={c => { this.textInput = c }}
returnKeyType={input.name === "Password" ? "" : "next"}
onSubmitEditing={() => { this.textInput._root.focus(); }}
blurOnSubmit={false}
secureTextEntry={input.name === "Password"}
{...input}
onFocus={() => this.openModel()}
keyboardType={input.name === "mobile" || input.name === "age" ? "numeric" : "default"}
autoCapitalize="none"
/>
</View>
);
}
<Field name="patientId" component={this.renderInput} type="" validate={[alphaNumeric,required]} />
}
you can simply write OpenModel() like this -
openModel() {
this.refs.bGroup.open();
}
Hope it works !
Please remove your openModel() function from constructor it will work for you if you define your function outside of constructor
openModel = () => {
this.refs.modal2.open();
}
you can call your function and define inside of class then directly using this.openModel();
if you have globally function and define outside of class then you don't need to use this keywords.

Update Input Value while mapping React Native

I am creating react-native mobile app. I have an array with some values. I want to set array's value into input field. I have added value in the fields but i can't able to update these values. I have set my values in a qty variable like this:
constructor(props) {
super(props);
this.state = {
qty:[],
}
}
componentWillMount() {
var ids = [];
this.props.data.map((dataImage,Index) => {
dataImage['pro-name'] != undefined && (
ids.push({'qty':dataImage['pro-qty']})
)
})
this.setState({qty:ids})
}
render() {
return {
this.props.data.map((dataImage,Index)=>(
<View key={Index} style={productStyle.cartview}>
{dataImage['pro-name'] && (
<View style={productStyle.cartmain}>
<Input value={this.state.qty[Index]['qty']} onChange={this.handleChangeInput.bind(this)} style={{width:40,height:40,textAlign:'left'}} />
</View>
)}
</View>
))
}
}
Its showing values properly into the input field but i can't able to type anything into the field to update the values. what can i do for this
I will suggest you to move your input container into separate class, its better approach and each component will handle its own state. Its easy to handle and will result better in performance too.
components = []
render() {
return this.props.data.map((item, index) => (
<CustomComponent data={item} index={index} ref={(ref) => this.components[index] = ref} />
))
}
You can then get child (CustomComponent) value from its ref.
this.components[index].getValue()
this.components[index].setValue('Value');
You will need to create these functions (getValue & setValue) in CustomComponent class.
solution
Here is solution to your query. You need to install lodash or find other solution to make a new copy qty.
<Input onChangeText={(text) => this.handleChangeText(text, index)} />
handleChangeText = (text, index) => {
const qty = _.cloneDeep(this.state.qty);
qty[index] = {
qty: text
}
this.setState({qty})
}
Your Input's value is set to this.state.qty[Index]['qty']. And to change it on text edit, you can do it like this. You do not need to bind the function, instead use an arrow function like this.
onChangeText={ (newValue) => {
this.setState({ <your-input-value-state>:newValue })
}}
You have to update the value of each Input individually on onChange event.
Replace your with Input with this
<Input value={this.state.qty[Index]['qty']}
onChange={this.handleChangeInput.bind(this, Index)}
style={{width:40,height:40,textAlign:'left'}}
/>
and update the state accordingly with the Index when the event is called
handleChangeInput(index, value){
let {qty} = this.state;
let qty_update = qty.slice();
qty_update[index]['qty'] = value;
this.setState({qty: qty_update});
}

How to hide some elements in JSX

I want to display on my app a list of meal 'tags'. So based on the code below, I was able to do that. So as a result of the code, I will get a list or a set of mealTags displayed.
Question: I want to only show the first 2 tags, hide the rest and put a link 'show more where the rest will appear when I click it . How can I do this in ReactJS?
return (
<View {...otherprops} style={styles.mainContainer} elevation={3}>
<View style={styles.contentContainer}>
<MealTagsSection mealTags={post.mealTags} />
</View>
type MealTagsProps = {
mealTags: Array<MealTag>;
};
export function MealTagsSection(props: MealTagsProps) {
let {mealTags} = props;
return (
<View style={styles.mealTagsContainer}>
{
mealTags.map((mealTag) => {
let tagStyle = '';
if (mealTag.category === 1) {
tagStyle = styles.tag_healthy;
} else {
tagStyle = styles.tag_improve;
}
return (
<View style={tagStyle}>
<Text style={styles.tagText}>{mealTag.description}</Text>
</View>
);
})
}
</View>
);
}
You can use set visible count in state
this.state= {
visibleCount:2
}
and use slice function before map, for example
mealTags.slice(0, this.state.visibleCount).map(...)
Then you can increase visible count as you want in button onClick funtion.
Another option is to track the index in your .map block
mealTags.map((mealTag, idx) => ...
and style accordingly e.g. display:none when idx >= 2