i am building a simple application using react native and redux with react-native-router-flux.
I am facing a dead-end because of the approach i've chosen to solve the problem.
My app has 2 big datasets Products and Categories.
I have three screens
Home screen
Category screen
Product screen
When the app boots up , i am making two ajax requests , one to fetch the products and one to fetch the categories. When the requests resolve i am storing data in store
{ products: [...], categories: [...]}
The problem i'm facing is in the Categories Screen. From the categories screen the app renders a list of items and by clicking each of them i dispatch an action to execute Actions['Scene name'] , routing back to the same list component but this time providing the clicked category id.
So i'm presenting the whole category tree from parent to children using this technique.
The problem arises when there aren't any sub categories and i have to switch to another component and display products.
Every time an action is occurs i have to filter products array ( each item contains an array with product categories ids ) to find the category products
I feel i've dealt with the problem very wrong and i am asking for some guidance for how to store data in redux and navigate between Activities using the router.
A don't know if i've explained the problem enough. Bellow some snippets of my work so far
action.js
export const selectCategory = ({ title, id }) => {
Actions.subCategoryList({ title, id });
return ({
type: SELECT_CATEGORY_PRODUCTS,
payload: id
});
};
subCategoryList.js
render() {
return (
<FlatList
style={{ backgroundColor: '#FFF' }}
data={this.props.categories}
renderItem={({ item }) => <CategoryListItem category={item} />}
keyExtractor={(item, index) => index}
/>
);
}
const mapStateToProps = (state, ownProps) => {
const categories = state.categories.filter((item) => {
if (item.parentid === ownProps.id) return true;
return false;
});
return {
categories,
category_products: state.products.selected_category_products
};
};
categoryListItem.js
categoryItemPressed() {
this.props.selectCategory(this.props.category);
}
render() {
const { category } = this.props;
return (
<TouchableOpacity
style={this.styles.buttonStyle}
onPress={() => this.categoryItemPressed()}
>
<View style={this.styles.containerStyle}>
<Text style={this.styles.textStyle}>{category.title}</Text>
</View>
</TouchableOpacity>
);
}
export default connect(null, { selectCategory })(CategoryListItem);
Related
My app sends stickers to WhatsaApp and it shows a dialog box. How to wait for the user action on the dialog box and perform action after the dialog box gone.
export default function CreateScreen({route, navigation}){
const [PreviewImages, setPreviewImages] = React.useState([]);
const [isAnimated, setAnimated] = React.useState(false);
const [PackName, setPackName] = React.useState(Date.now().toString());
const [AuthorName, setAuthorName] = React.useState('');
const [Version, setVersion] = React.useState(1);
const context = React.useContext(AppContext);
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<Button onPress={send} title="Create" color="#00ff00"/>
),
});
});
const removePreview=(x)=>{
setPreviewImages(PreviewImages.filter((i,index)=>index!==x));
}
const src = 'file://'+RNFS.DownloadDirectoryPath+'/'+'file10.jpg';
const dest = 'file://'+RNFS.DownloadDirectoryPath+'/'+'file10_1.jpg';
const send =async()=>{
log('sending...')
try{
const packData = {
authorName:AuthorName,
packName:PackName,
isAnimated:isAnimated,
version:Version,
previewImages:PreviewImages,
}
let jsonData = await DecoratePack(packData)
await RNWhatsAppStickers.prepare(jsonData)
.then(res=>res.slice(1))
.then(str=>JSON.parse(str))
.then(obj=>{
log('pack ',obj['identifier'])
return RNWhatsAppStickers.send(obj['identifier'],obj['identifier'])
})
.then(r2=>log(r2))
.catch(err=>Alert.alert('Could not create pack',err.toString()))
}catch(e){
Alert.alert('Problem with pack',e.toString())
}
}
return(
<View style={root.main}>
<Screen1/>
</View>
);
}
If I set the create button to navigate back to home screen after creatnig pack then whatsapp dialoge box will appear on home screen. It is kind of ok but dialog box is appeared to tell there is issue in pack or pack name already exist then it will be annoying for user to go to the create screen again.
Thanks for your help
By onActivityResult we can detect the activity of third party apps fired by your app.
You'll need to listen to onActivityResult if you want to get results from an activity you started with startActivityForResult. To do this, you must extend BaseActivityEventListener or implement ActivityEventListener.
http://reactnative.dev/docs/native-modules-android#getting-activity-result-from-startactivityforresult
I have big problem with my first React Native app. I've spend few hours today to find the solution but unfortunately nothing helps me.
I have two views:
<Router>
<Stack>
<Scene key="objectsList"
component={ ObjectsList } title="Objects"
onRight={() => Actions.addObject()} rightTitle="Add object" />
<Scene key="addObject" component={ AddObject } title="Add" />
</Stack>
</Router>
First view is a FlatList that is displaying data loaded from AsyncStorage. User can add new object by pressing the right button Add object on the navigation bar. AddObject Scene will be present on the screen. There is another FlatList with some options. User can tap on the cell and then the data is save to the AsyncStorage and after that I call Actions.pop(); function.
The view is gone but I couldn't refresh first screen Objects to reload data and display new value. I've tried a lot solutions from git, stackoverflow and other forums. Nothing works for me.
I am using react-native-router-flux for routing.
I will be glad for help or example code because I am stuck on this problem and it block my project.
P.S. I want to refresh view, send new props or do something that will not reload whole Component. There is a map on it and if I am reloading whole View it is loading from the beginning. I want only to inform that there is some change and run some function to refresh it.
EDIT:
After implemented the solution proposed by Phyo unfortunately I couldn't refresh the data in proper way. When user choose something on AddObject scene he comes back to first view but nothing happened. The change is implemented after user open AddObject scene second time. The function from props is running only when scene 2 is appear again.
My second attempt looks like that:
ObjectsList
state = { selectedObject: "" }
componentDidMount() {
this.loadSelectedObject();
}
refreshState() {
this.loadSelectedObject();
}
loadSelectedObject() {
AsyncStorage.getItem('ObjectKey', (err, object) => {
if (object) {
this.setState({ selectedObject: object })
}
});
}
render() {
return(
<Button onPress={() => Actions.addObject({onBack: refreshState()})}>
);
}
Add Object
onPressItem = (object) => {
AsyncStorage.setItem('ObjectKey', object, () => {
this.props.onBack;
Actions.pop();
});
};
render() {
return (
<FlatList
data={this.state.objects}
keyExtractor={(item, index) => item}
key={this.state.numColumns}
numColumns={this.state.numColumns}
renderItem={(data) =>
<LineCell onPressItem={this.onPressItem} object={String(data.item.object)} />
);
}
I found a solution with friend's help. The code:
ObjectsList
state = { selectedObject: "" }
componentDidMount() {
this.loadSelectedObject();
}
refreshState() {
this.loadSelectedObject();
}
loadSelectedObject() {
AsyncStorage.getItem('ObjectKey', (err, object) => {
if (object) {
this.setState({ selectedObject: object })
}
});
}
render() {
return(
<Button onPress={() => Actions.addObject()}>
);
}
Add Object
onPressItem = (object) => {
AsyncStorage.setItem('ObjectKey', object, () => {
Actions.pop();
Actions.refs.objectsList.refreshState(); //Solution
});
};
On your first view, create a function e.g., refreshFirstView() and pass that function to the second view.
On your second view, call that function this.props.refreshFristView() before you called Actions.pop()
In your refreshFirstView() on the first view, simply call all the function you called to retrieve from AsyncStorage.
First View
componentDidMount(){
retrieveAsynStorageItems()
doSomeOtherLikeFilteringOrSorting()
}
refreshFirstView(){
retrieveAsynStorageItems()//by calling this func again you will get newly saved item
doSomeOtherLikeFilteringOrSorting()
}
I hope this is what you need.
ScrollToEnd() (on a FlatList) appears to have no effect in Android (or in the iOS simulator). The refs appear to be correct, and my own scrollEnd function is being called (I was desperate) but nothing changes on the screen. The effects of all the scroll functions appear to be really inconsistent - I can get scrollToOffset to work on iOS but not Android. I read that this may be because Android doesn't know the height of the items in the flatlist, but it still doesn't work with getItemLayout implemented.
There's no feedback/errors I can see which would explain why this wouldn't work. Note that I am developing with Redux, using Android 7.0 to test and am trying this in Debugging mode (using react-native run-android). The FlatList is inside a normal View (not a ScrollView).
The logic in the code is correct as far as I can tell, but calling scrollToEnd on the FlatList has no visible effect.
My render() function:
<View style={styles.container}>
<View style={styles.inner}>
<FlatList
ref={(ref) => { this.listRef = ref; }}
data = {this.getConversation().messages || []}
renderItem = {this.renderRow}
keyExtractor = {(item) => item.hash + ''}
numColumns={1}
initialNumToRender={1000}
onContentSizeChange={() => {
this.scrollToEnd();
}}
getItemLayout={(data, index) => (
{length: 50, offset: 50 * index, index}
)}
onContentSizeChange={this.scrollToEnd()}
onLayout={ this.scrollToEnd()}
onScroll={ this.scrollToEnd()}
/>
</View>
</View>
this.scrollToEnd():
scrollToEnd = () => {
console.log("scrolling func"); // This is printed
const wait = new Promise((resolve) => setTimeout(resolve, 0));
wait.then( () => {
console.log("scrolling"); // This is also printed
this.listRef.scrollToEnd(); // Throws no errors, has no effect?
});
};
Thanks so much.
Your code seems correct to me, but which version of React are you using? Starting from v16.3 the recommended way to use refs is through React.createRef(), so your code would be change to:
// constructor
constructor(props) {
super(props)
this.flatlistRef = React.createRef();
}
// inside render
<FlatList
ref={this.flatlistRef}
...
/>
// scrollToEnd
scrollToEnd = () => {
this.flatlistRef.current.scrollToEnd(); // ref.current refers to component
};
Note that this might not solve your problem, since the older usage of ref should still be valid
Here is the code in index.js:
render() {
return (
<Navigator initialRoute = {{
id: 'firstPage'
}}
renderScene={
this.navigatorRenderScene
} />
);
}
navigatorRenderScene(route, navigator) {
switch (route.id) {
case 'firstPage':
return(<FirstPage navigator={navigator} title="First Page"/>);
case 'secondPage':
return(<SecondPage navigator={navigator} title="Second Page"/>);
}
}
Inside firstPage.js
class FirstPage extends Component {
...
<TouchableHighlight onPress={() => this.onFirstButtonPress()}>
</TouchableHighlight>
onFirstButtonPress() {
this.props.navigator.push({
id:'secondPage'
})
}
...
}
Inside secondPage.js:
<TouchableHighlight onPress={ () => this.onSecondButtonPress() } > </TouchableHighlight>
onSecondButtonPress() {
this.props.navigator.pop();
}
My intend here is after click FirstButton on FirstPage, I navigate to SecondPage. After clicking SecondButton, I return to FirstPage.
My code works, but when I click on FirstButton, I see the first page slowly disappears, and the 2nd page slowly shows up, and there is 1 or 2 seconds, they overlap each other. Is there a way I can make a clear and quick switch between the two?
The code looks fine to me. Can you check couple of things here, please :
1) You are running on Debug mode.(That boosts the performance)
2) See if you have not triggered Slow Animations in your simulator. Try clicking on Slow Animation from Debug->Slow Animation.
I'm currently fiddling around with react-native-maps to simulate various moving objects around a map space (simulating real time tracking of items) with a callout showing each object's name beside the marker denoting it.
I'm currently able to show the callout for each marker whenever I press it. However, what I intend to do is create a button which toggles on or off the callouts for every marker on the map.
I'm currently using the react-native-maps library for my map.
What I've done are as such:
/* this.props.trackedObjects is an array of objects containing
coordinate information retrieved from database with the following format
trackedObjects=[{
coordinate:{
latitude,
longitude
},
userName
}, ...]
*/
/* inside render() */
{this.props.trackedObjects.map((eachObject) => (
<View>
<MapView.Marker
ref={ref => {this.marker = ref;}}
key={eachObject.userName}
coordinate={eachObject.coordinate}
>
/*Custom Callout that displays eachObject.userName as a <Text>*/
</MapView.Marker>
</View>
))}
/* Button onPress method */
onPress(){
if(toggledOn){
this.marker.showCallout();
}else{
this.marker.hideCallout();
}
}
It seems that when I render a single Marker component, this method works. However, I can't quite crack my head to get around using showCallout() to show the callouts for an entire group of markers.
Would anyone be able to shed some light on how to go about doing this?
1. Wrap the component MapView.Marker into a custom Marker:
class Marker extends React.Component {
marker
render () {
return (
<MapView.Marker {...this.props} ref={_marker => {this.marker = _marker}} />
)
}
componentDidUpdate (prevProps) {
if (prevProps.calloutVisible !== this.props.calloutVisible) {
this.updateCallout();
}
}
updateCallout () {
if (this.props.calloutVisible) {
this.marker.showCallout()
} else {
this.marker.hideCallout()
}
}
}
2. Update your higher level component accordingly in order to provide the callout visibility filter via prop calloutVisible:
/* inside render() */
{this.props.trackedObjects.map((eachObject) => (
<View>
<Marker
key={eachObject.userName}
coordinate={eachObject.coordinate}
calloutVisible={eachObject.calloutVisible} // visibility filter here
>
/*Custom Callout that displays eachObject.userName as a <Text>*/
</MapView.Marker>
</View>
))}