Iterate over values of Map to render all icon components but don't work however render one icon works - react-native

I am developing react-native project.
I have a function which set icon metadata into a Map :
export function getIconsMetadata() {
// a map of icons' metadata
const iconsMetadata = new Map();
...
// code to set icon metadata to the map
iconsMetadata.set("foo", "Foo");
iconsMetadata.set("bar", "Bar");
...
return iconsMetadata;
}
There is another function which returns the actual icon component based on the icon type (i.e. the value of iconsMetadata holds the icon type):
export function getMyIcon(iconType) {
switch (iconType) {
case 'Foo':
return <Foo />;
case 'Bar':
return <Bar />;
...
}
In my screen, I have a function to show icon component by iterating over the values of the above icons' metadata Map, and try to render each icon component:
export const MyScreen() => {
const showIcons = () => {
[...getIconsMetadata().values()].map((iconType, index) => {
const iconComponent = getMyIcon(iconType);
return <View key={index}>
{iconComponent}
</View>;
});
};
return (
<View style={styles.container}>
{/*I call the showIcons function here to render icons*/}
{showIcons()}
</View>
)
}
Problem is the icons are not shown on screen.
But if I directly return one icon component in my screen:
export const MyScreen = () => {
...
const showOneIcon = () => {
return <View>
<Foo />
</View>;
});
}
return (
<View style={styles.container}>
{/*I show one icon*/}
{showOneIcon()}
</View>
)
}
The <Foo /> icon component is rendered successfully on the screen.
So, why iterating the map to show all icons don't work?

The problem is that you’re not returning anything from showIcons. Either you remove { } from there
const showIcons = () =>
[...getIconsMetadata().values()].map((iconType, index) => {
const iconComponent = getMyIcon(iconType);
return <View key={index}>{iconComponent}</View>;
});
or add return before [...getIconsMetadata().values()].map
const showIcons = () => {
return [...getIconsMetadata().values()].map((iconType, index) => {
const iconComponent = getMyIcon(iconType);
return <View key={index}>{iconComponent}</View>;
});
};

Related

TextInput component suggests

enter image description here
https://i.stack.imgur.com/AEGy7.png
I have a TextInput component how can I write content and it suggests the next word that is highlighted as the image
when i enter F and it automatically adds b.com is blacked out
You can have a list with common keywords that you use in your app, something like this:
[
{
"id": 1,
"name": "chicken",
}
{
"id": 2,
"name": "banana",
}
]
Note that ideally you would be getting this data from an API and not manually add it yourself.
Then, make a simple search bar and declare a new variable using useState:
const [filter, setFilter] = useState([])
...
<TextInput style={styles.textinput} placeholder='Search food...' onChangeText={handleChange}/>
The handleChange function is in charge of setting the new data Filter:
const handleChange = (e) => {
const currentFilter = data.filter((item) => {
return item.name.toLowerCase().includes(e.toLowerCase())
})
setFilter(currentFilter)
}
For displaying the data that you want, you can use conditional rendering. Just insert the following snippet inside the View in which you want to display your data:
{
filter.length != 0 && (
<View>
{
filter.map((item, key) => {
return <Text key={item.id} style={styles.textstyle}>{item.name}</Text>
})
}
</View>
)
}
Here is the full code:
export default function FilteringTest() {
const [filter, setFilter] = useState([])
const handleChange = (e) => {
const currentFilter = data.filter((item) => {
return item.name.toLowerCase().includes(e.toLowerCase())
})
setFilter(currentFilter)
}
return (
<View style={styles.background}>
<TextInput style={styles.textinput} placeholder='Search food...' onChangeText={handleChange} />
{
filter.length != 0 && (
<View>
{
filter.map((item, key) => {
return <Text key={item.id} style={styles.textstyle}>{item.name}</Text>
})
}
</View>
)
}
</View>
)
}
As for highlighting the values, you can simply create a new Text on top of the searchBar that contains the closest match. I suggest you read this article on filtering data in react native: https://www.freecodecamp.org/news/how-to-make-a-filter-component-in-react/
TextInput for IOS - https://snack.expo.dev/JrWw1Zj5Z
TextInput for android - https://snack.expo.dev/piRXBQv6aM
For better view you can disable borders on TextInput or make TextInput transparent, and set borders and other styles to Text component

How to access value calculated in `useEffect` hook from renderer

I am developing a React-Native project with functional component.
Here is a very simple screen which renders a calculated result list. Since I need to calculation to be called only once so I put it inside the useEffect hook.
import {doCalculation} from '../util/helper'
const MyScreen = ({navigation}) => {
useEffect(() => {
// I call a function from a helper module here.
// The result is a list of object.
const result = doCalculation();
// eslint-disable-next-line
}, []);
// renderer
return (
<View>
// Problem is 'result' is not accessible here, but I need to render it here
{result.map(item=> <Text key={item.id}> {item.value} </Text>)}
</View>
)
}
export default MyScreen;
As you can see I have called the doCalculation() to get the result inside useEffect hook. My question is how can I render the result in the return part? Since the result is calculated inside the hook, it is not accessible in the renderer.
P.S. Moving the const result = doCalculation() outside the useEffect hook is not an option since I need the calculation to be called only once.
Below is an example. According to the above comments it looks like you want it to be called once on component mount. All you really need to do is add a useState
import {doCalculation} from '../util/helper'
const MyScreen = ({navigation}) => {
const [calculatedData, setCalculatedData] = useState([])
useEffect(() => {
// I call a function from a helper module here.
// The result is a list of object.
const result = doCalculation();
setCalculatedData(result)
// eslint-disable-next-line
}, []);
// renderer
return (
<View>
// Problem is 'result' is not accessible here, but I need to render it here
{calculatedData.map(item=> <Text key={item.id}> {item.value} </Text>)}
</View>
)
}
export default MyScreen;
const [calculatedData, setCalculatedData] = useState([])
useState is a hook used to store variable state. When calling setCalculatedData inside the useEffect with empty dependency array it will act similar to a componentDidMount() and run only on first mount. If you add variables to the dependency array it will re-run every-time one of those dep. change.
You can change the data inside the calculatedData at anytime by calling setCalculatedData with input data to change to.
Make use of useState to save the calculation result and then use the variable inside return. See https://reactjs.org/docs/hooks-state.html.
Code snippet:
import {doCalculation} from '../util/helper'
const MyScreen = ({navigation}) => {
const [result, setResult] = useState([]);
useEffect(() => {
// I call a function from a helper module here.
// The result is a list of object.
const tempRes = doCalculation();
setResult(tempRes);
// eslint-disable-next-line
}, []);
// renderer
return (
<View>
// Problem is 'result' is not accessible here, but I need to render it here
{result.map(item=> <Text key={item.id}> {item.value} </Text>)}
</View>
)
}
export default MyScreen;
Is async function?
if the function is not async (not wating for respond like from api) - you don't need useEffect.
import React from 'react';
import { Text, View } from 'react-native';
import {doCalculation} from '../util/helper'
const results = doCalculation();
const MyScreen = () => {
return (
<View>
{results.map(item=> <Text key={item.id}> {item.value} </Text>)}
</View>
)
}
export default MyScreen;
else you should wait until the results come from the server..
import React, { useState, useEffect } from 'react';
import { Text, View } from 'react-native';
import { doCalculation } from '../util/helper';
const MyScreen = () => {
const [results, setResults] = useState(null) // or empty array
useEffect(() => {
(async () => {
setResults(await doCalculation());
})();
}, []);
return (
<View>
{results?.map(item => <Text key={item.id}> {item.value} </Text>) || "Loading..."}
</View>
)
}
export default MyScreen;
and I can use more readable code:
if (!results) {
return <View>Loading...</View>
}
return (
<View>
{results.map(item => <Text key={item.id}> {item.value} </Text>)}
</View>
)
the async function can be like:
const doCalculation = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ id: 1, value: 1 }]);
}, 2000);
});
};

loading on navigating between screens

I'm new in RN. When I want to navigate between screens I create this function:
displayScreen2 = () => {
this.props.navigation.navigate("screen2")
}
and I call it in onPress={this.displayScreen2}
with TouchableOpacity or any Touchable when the user clicks he has to wait 1 second or 2 before displaying the screen. So what I want is to change the Touchable icon to an loader.
It's simple if I use a conditional rendering but I don't know how to do it now, when I have to change my state? Any suggestions?
this is my approach:
<TouchableOpacity
style={Styles.topButton}
onPress= {() => {
this.setState({loading: 'load'},
() => {
displayScoreListView()
// this.setState({loading: 'icone'})
}
)
}}
>
<Text style={Styles.scoreListButtonTextRed}>{this.state.loading}</Text>
that not work, tha state change but visualy not because if I return to the first screen I have 'load' in the text component
You could create a custom component wrapping whatever Touchable you prefer, I've used this technique in my production apps before. The button has it's own state which allows you to automatically display a loading indicator when necessary.
export class ButtonWorker extends Component {
state = {
working: false
}
onButtonPress = () => {
this.setState(
{ working: true },
() => {
this.props.onPress(this.onWorkFinished);
}
);
}
onWorkFinished = () => {
this.setState({ working: false });
}
render() {
return (
<TouchableOpacity>
{this.state.working ? (
<ActivityIndicator />
) : (
this.props.children
)}
</TouchableOpacity>
);
}
}
And then use it like a normal button with additional logic!
export class NavigationScreen extends Component {
navigate = (done) => {
// ... Asynchronous logic goes here
done();
this.props.navigation.navigate("Screen2");
}
render() {
return (
<Fragment>
{/* ... */}
<ButtonWorker onPress={this.navigate} />
</Frament>
);
}
}

React-Native & Redux Raw text cannot be used outside of a <Text> tag. Not rendering string: ''

I am working on a React-Native app that has multiple screens and uses react-native-router-flux for navigation. One of the screens is supposed to change the background image of the main screen so in this background settings screen I have a list of images with a switch button. Currently it looks like this:
If i try to click on any of the other switches I get the error below:
And here is the code for the main screen:
class MainScreen extends Component {
changedBackground(){
switch(this.props.backgroundImage){
case 'straw':
return (
<Image source={require('../assets/img/paie.png')} style={mainScreenStyle.bgImg}/>
);
case 'rabbit fur':
return (
<Image source={require('../assets/img/rabbit_fur.jpg')} style={mainScreenStyle.bgImg}/>
);
case 'bear fur':
return(
<Image source={require('../assets/img/bear_fur.jpeg')} style={mainScreenStyle.bgImg}/>
);
case 'fox fur':
return (
<Image source={require('../assets/img/fox_fur.jpg')} style={mainScreenStyle.bgImg}/>
);
default:
return ''
}
}
render() {
return (
<View style={mainScreenStyle.container}>
<View style={mainScreenStyle.menu}>
{this.changedBackground()}
</View>
<TargetComponent style={mainScreenStyle.targetD}/>
<ScoreBadges/>
</View>
);
}
}
const mapStateToProps = state => {
return {
backgroundImage: state.mainScreen.backgroundImage,
};
};
export default connect(mapStateToProps, {changeBackground})(MainScreen);
And the code for the background settings screen:
const straw = () => {
return <Image source={require('../../assets/img/paie.png')}
style={[background_list_img.background_img_icon]}/>;
};
const rabbit = () => {
return <Image source={require('../../assets/img/rabbit_fur.jpg')}
style={[background_list_img.background_img_icon]}/>;
};
const bear = () => {
return <Image source={require('../../assets/img/bear_fur.jpeg')}
style={[background_list_img.background_img_icon]}/>;
};
const fox = () => {
return <Image source={require('../../assets/img/fox_fur.jpg')}
style={[background_list_img.background_img_icon]}/>;
};
const backgrounds_list = [
{id:'0', name:'straw', img:straw()},
{id:'1', name:'rabbit', img:rabbit()},
{id:'2', name:'bear', img:bear()},
{id:'3', name:'fox', img:fox()}
];
class BackgroundSettings extends Component {
render(){
return <FlatList data={backgrounds_list} keyExtractor={item=>item.id}
renderItem={({item})=>{return(
<ListItem leftIcon={item.img}
title={" " + item.name}
hideChevron
switchButton
switched={this.props.currentBackground === item.name}
onSwitch={()=>{this.props.changeBackground(item.name)}}/>);}}
/>;
}
}
mapStateToProps = state => {
return {
currentBackground: state.mainScreen.backgroundImage,
};
};
export default connect(mapStateToProps, {changeBackground})(BackgroundSettings);
The reducer is very simple:
const INITIAL_STATE = {backgroundImage:'straw'};
export default MainScreenReducer = (state = INITIAL_STATE, action) => {
switch (action.type){
case BACKGROUND_CHANGE:
return { ...state, backgroundImage:action.payload};
default:
return state;
}
}
And the action creator is simple as well:
export const changeBackground = (imageName) =>{
return{
type:BACKGROUND_CHANGE,
payload:imageName
};
};
Any idea what am I missing? I spent two days trying to figure this out...
change the switch statement default to any component
changedBackground(){
switch(this.props.backgroundImage){
case 'straw':
return (
<Image source={require('../assets/img/paie.png')} style={mainScreenStyle.bgImg}/>
);
case 'rabbit fur':
return (
<Image source={require('../assets/img/rabbit_fur.jpg')} style={mainScreenStyle.bgImg}/>
);
case 'bear fur':
return(
<Image source={require('../assets/img/bear_fur.jpeg')} style={mainScreenStyle.bgImg}/>
);
case 'fox fur':
return (
<Image source={require('../assets/img/fox_fur.jpg')} style={mainScreenStyle.bgImg}/>
);
/**
here change like this:
return <View/>
or
return null
*/
default:
return ''
}
}
Instead of returning '', you should try returning null. Because '' is a string and needs Text around it. However null is an object and can be used instead of DOM objects.

How to read props from a React Native touchable event currentTarget?

I have the following React Native code that runs the press() method when a user taps an image. I want to get the itemIndex prop from the event object. I set a break point in the press method and added some expressions to the Watch. From the Watch I determined that the target (event origination) from the event is the Image which is correct. The itemIndex prop is also available. The element being processed is the currentTarget, the Watch sees it's a "RCTView" and I was expecting a TouchableOpacity, so maybe underneath TouchableOpacity is a View? The currentTarget itemIndex prop is undefined, why? How can I get the props from the currentTarget?
I want to do it this way to avoid creating addition methods for each rendered item.
FYI,
ref={(c) => this._input = c} will not work because it's being run in a loop.
onPress={(e) => this.press(e, i)} creates a new function which I'm trying to avoid.
Watch
target._currentElement.props.itemIndex: 2
target._currentElement.type.displayName: "RCTImageView"
currentTarget._currentElement.props.itemIndex: undefined
currentTarget._currentElement.type.displayName: "RCTView"
press: function(event){
var currentTarget = ReactNativeComponentTree.getInstanceFromNode(event.currentTarget);
var target = ReactNativeComponentTree.getInstanceFromNode(event.target);
var currentTargetIndex = currentTarget._currentElement.props.itemIndex;
var targetIndex = target._currentElement.props.itemIndex;
var url = this.state.data.items[currentTargetIndex].url;
Linking.openURL(url).catch(err => console.error('An error occurred', err));
},
render: function() {
return (
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false} style={styles.galleryView}>
{
this.state.data.items.map((data, i) =>
<TouchableOpacity itemIndex={i} key={i} activeOpacity={0.5} onPress={this.press} >
<Image itemIndex={i} key={i} source={{uri:data.previewImageUri}} style={styles.galleryImage} />
</TouchableOpacity>
)
}
</ScrollView>
);
}
I actually came across this same issue recently, I found two different ways you could approach this. The easier way of doing it is altering your onPress to pass an index to your press function, this is the 2nd way of doing it:
press: function(event, index){
var url = this.state.data.items[index].url;
Linking.openURL(url).catch(err => console.error('An error occurred', err));
},
render: function() {
return (
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
style={styles.galleryView}
>
{
this.state.data.items.map((data, i) =>
<Images data={data} key={i} index={i} press={this.press} />
)
}
</ScrollView>
);
}
const Images = (props) => {
const imageClicked = (e) => {
props.press(e, props.index);
}
return (
<TouchableOpacity activeOpacity={0.5} onPress={imageClicked} >
<Image source={{uri:props.data.previewImageUri}} style={styles.galleryImage} />
</TouchableOpacity>
)
}
You could make your event handler a curried function that accepts extra parameters.
//Curried function for onPress event handler
handleOnPress = (someId, someProp) => event => {
//USE someProp ABOVE TO ACCESS PASSED PROP, WHICH WOULD BE undefined IN THIS CASE
//Use event parameter above to access event object if you need
console.log(someProp)
this.setState({
touchedId: someId
})
}
Checkout the working snack below
https://snack.expo.io/#prashand/accessing-props-from-react-native-touch-event
Binding the needed information to a callback and assigning one to each child avoids recreating the callback on every render of children.
class Hello extends React.Component{
state = { names: this.props.names.map((name, i) => {
return Object.assign({
onClick: this._onClick.bind(this, i, this.props),
}, name)
}),
};
_onClick(ind, _props, e) {
alert('props:' + JSON.stringify(_props));
}
render() {
const { names } = this.state;
return (
<div>
{ names.map((name, i) => (
<div key={i}>Name: <input value={ name.first } onClick={ name.onClick } /></div>
))}
</div>
)}
}
ReactDOM.render(
<Hello names={[{first:'aaa'},{first:'bbb'},{first:'ccc'}]}/>,
document.getElementById('container')
);
JS Fiddle