Mobx store do not update with observer - reactjs

I have a simple react native app with two screens.
First screen is the list, where you see your selected groups, and you can remove them, by clicking on trash icon:
export const Settings: NavioScreen = observer(({ }) => {
...
return (
<View flex bg-bgColor padding-20>
<FlashList
contentInsetAdjustmentBehavior="always"
data={toJS(ui.savedGroups)}
renderItem={({item}) => <ListItem item={item} />}
estimatedItemSize={20}
/>
</View>
);
});
};
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<TouchableOpacity onPress={() => ui.deleteGroup(item)}>
<Icon name={'trash'}/>
</TouchableOpacity>
</View>
);
};
The second screen is also the list, where you can add and remove the subjects from the list:
export const Playground: NavioScreen = observer(() => {
...
const groupsToShow =
ui.search && ui.search.length > 0
? ui.groups.filter((p) =>
p.name.toLowerCase().includes(ui.search.toLowerCase())
)
: ui.groups;
return (
<View >
<FlashList
data={toJS(groupsToShow)}
renderItem={({item}) => <ListItem item={item} />}
/>
</View>
);
});
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<View>
<If
_={ui.isGroupSaved(item)}
_then={
<TouchableOpacity onPress={(e) => {ui.deleteGroup(item)}}>
<AntDesign name="heart" size={20} color={Colors.primary} />
</TouchableOpacity>
}
_else={
<TouchableOpacity onPress={(e) => {ui.addGroup(item)}}>
<AntDesign name="hearto" size={20} color={Colors.primary} />
</TouchableOpacity>
}
/>
</View>
</View>
);
};
And now when I remove the group from the first list, the heart icon do not update on the second list. But it should, because there is an if statement on second list, that checks if the group is saved. And if it is not, the heart should have the name="hearto"
I have tried to use the state instead mobx library but it does not also help.
Here is my store written with mobx:
export class UIStore implements IStore {
savedGroups = [];
constructor() {
makeAutoObservable(this);
makePersistable(this, {
name: UIStore.name,
properties: ['savedGroups'],
});
}
addGroup = (group: any) => {
if (true === this.isGroupSaved(group)) {
return;
}
this.savedGroups.push(group);
}
isGroupSaved = (group: any) => {
return -1 !== this.savedGroups.findIndex(g => g.id === group.id);
}
deleteGroup = (groupToDelete: any) => {
this.savedGroups = this.savedGroups.filter((group) => group.id !== groupToDelete.id);
}
}

Related

React Native - Passing navigation into a child component

I have a screen with the following:
function Intereset ({ navigation }) {
function ReturnMyFunction () {
if (!var.length) {
return ( <NoILikes /> )
} else {
return (
<FlatList
data={Ilike}
keyExtractor={item => item._id}
ItemSeparatorComponent={() => <Divider />}
renderItem={UserRow}
/>
)
}
}
return ( <ReturnILikeOrNoILike /> )
}
export default Interest
Here is my UserRow component below:
const UserRow = ({ item, navigation }) => (
<TouchableOpacity onPress={() => navigation.navigate("ProfileDetailScreenSingle", { userID: item.likeTo })}>
<View style={styles.row}>
<Image style={styles.avatar}
resizeMode={"cover"}
source={{ uri: item.likeToProfileImage }}
/>
<View style={styles.textContainer}>
<Text style={styles.name}>{item.likeToName}, <Text>{item.likeToAge}</Text></Text>
</View>
<Text style={styles.viewProfileText}>View Profile</Text>
<AntDesign name="right" size={14} color="black" />
</View>
</TouchableOpacity>
)
When I click on the UserRow to navigate I get the following issue.
I'm using useNavigation to redirect screen in stack:
import { useNavigation } from "#react-navigation/native";
const UserRow = ({ item }) => {
const navigation = useNavigation()
const onGoToProfileDetailScreenSingle = () => navigation.navigate("ProfileDetailScreenSingle", { userID: item.likeTo })
return (
<TouchableOpacity onPress={onGoToProfileDetailScreenSingle}>
...
</TouchableOpacity>
);
}
const renderItem = ({ item }) => <UserRow item={item} />
return (
<FlatList
data={Ilike}
renderItem={renderItem}
...
/>
)
Use onPress handler instead and perform navigation on the screen.
const UserRow = ({ item, onPress }) => (
<TouchableOpacity onPress={onPress}>
...
</TouchableOpacity>
);
const renderItem = ({ item }) => {
return (
<UserRow
item={item}
onPress={() => navigation.navigate(...)}
/>
);
};
<FlatList
data={Ilike}
renderItem={renderItem}
...
/>

react native conditionally fetch data too much re-rendering issue

I want to achieve such scenario in my application:
when the user taps on a Company for checking its details I want to show all products of the company first.
when the user taps on a Category in the Details page I want to show only products related to that category.
for both cases I need to call API with specific arguments so here's my code:
Card Details Page:
const CardDetails = (props) => {
const dispatch = useDispatch();
const companyId = props.route.params.id;
const companyName = props.route.params.companyName;
const companyImage = props.route.params.companyImage;
const companyLocation = props.route.params.companyLocation;
const [isCatLoading, setIsCatLoading] = useState(true);
const [isProductLoading, setIsProductLoading] = useState(true);
const [CompanyCategories, setCompanyCategories] = useState([]);
let products = [];
console.log(products)
const fetchCategories = async () => {
const response = await fetch(`myURL`);
const data = await response.json();
setCompanyCategories(data)
setIsCatLoading(false)
};
const fetchProducts = () => {
dispatch(SelectedProductsByCat.fetchSelectedCatProducts(companyId, [7, 8, 10, 11]))
.then(
setIsProductLoading(false)
)
}
if (setIsProductLoading) {
products = useSelector(state => state.products.availableSelectedCatProducts)
}
useEffect(() => {
fetchCategories()
}, [products])
useEffect(() => {
fetchProducts()
}, [products])
return (
<ScrollView showsVerticalScrollIndicator={false} style={styles.screen}>
<View style={styles.titleContainer}>
<Text style={styles.titleText}>{companyName}</Text>
</View>
<View style={styles.imageContainer}>
<View style={styles.mainImageContainer}>
<Image style={styles.mainImage} source={{ uri: companyImage }} />
</View>
</View>
<View style={styles.optionsContainer}>
<Option title={companyLocation} />
</View>
<View style={styles.typeSelectionContainer}>
{
isCatLoading ?
<DotIndicator color='#253D59' size={20} count={5} />
:
<ScrollView contentContainerStyle={styles.horizontalScrollContainer} horizontal={true} showsHorizontalScrollIndicator={false}>
<CategorySelection
companyId={companyId}
options={CompanyCategories.map((el, i) => {
return { id: el.id, name: el.title, icon: el.icon, selected: false }
})}
/>
</ScrollView>
}
</View>
{
isProductLoading ?
<View>
<DotIndicator color='#253D59' size={20} count={5} />
</View>
:
<View style={styles.resultsContainer}>
{
products != 'undefined' && products.length > 0 ?
products.map((el, index) => {
return (
<TouchableOpacity
key={el.id.toString()}
style={styles.resultImageContainer}
onPress={() => props.navigation.navigate('OfficeDetails', {
productId: el.id.toString(),
productName: el.productName,
productPrice: el.productPrice,
productImages: el.groupOfImage
})}
>
<Image style={styles.mainImage} source={{ uri: el.groupOfImage[0].imageAddress }} />
<View style={styles.seatsContainer}>
<Text style={styles.seatsText}>{el.productPrice} <Text style={styles.price}>₺</Text></Text>
</View>
</TouchableOpacity>
)
})
:
<View style={{ marginTop: 40 }}>
<NoData size={wp(40)} />
</View>
}
</View>
}
</ScrollView>
)
}
and this is the Categories Component:
const CategorySelection = (props) => {
const dispatch = useDispatch();
const companyId = props.companyId
const [options, setOptions] = useState(props.options.map((el, index) => {
return {
id: el.id,
name: el.name,
selected: el.selected
}
}))
const selectedCategoriesId = options.filter(el => el.selected).map(el => el.id);
useEffect(() => {
fetchProductsHandler();
}, [options])
const fetchProductsHandler = () => {
if(selectedCategoriesId.length == 0) {
dispatch(SelectedProductsByCat.fetchSelectedCatProducts(companyId, options.map((el, index) => el.id)));
}
}
const onRadioBtnClick = (item) => {
let updatedState = options.map((el) =>{
if(el.id == item.id) {
return { ...el, selected: !el.selected }
} else {
return { ...el, selected: el.selected }
}
}
);
setOptions(updatedState)
};
return (
<View style={styles.mainContainer}>
{
options.map((item, index) => {
return (
<TouchableOpacity
onPress={() => {
onRadioBtnClick(item)
}
}
key={item.id}
selected={item.selected}
style={[styles.buttonContainer, props.buttonStyle]}>
<View style={[styles.button,
item.selected == true ? styles.selectedButton : null
]}>
<Text style={[styles.buttonText,
item.selected == true ? styles.selectedButtonText : null,
{fontSize : item.name.split(' ')[0].length > 10 ? hp(1.5) : hp(2.1)}
]}>{item.name}</Text>
</View>
</TouchableOpacity>
)
})
}
</View>
)
}
and here's the screenshot of the page:
again what I need to achieve here is to show all products at first, but when the user tap on categories I need to show related products, and also when the user deselects categories all products show again.
if you need more code for the project just let me know in the comments. any help would be appreciated <3

React Native: Render different component based on button press

I am new to React/React Native and am currently building a screen that consists of a section consisting of 3 buttons and a space below to render different components depending on which tab button is pressed. The tabs are add, remove, and history.
Basically the intent is to recreate something like the tabs component from React Bootstrap, but for React Native. I have seen the React Navigation tabs component, but I do not want the tabs to navigate to a different screen, just render a different component depending on which is clicked and have it fade into the section below the buttons.
Below is a rough idea of how I am thinking of approaching the problem.
const ChoiceContainer = props => {
const {children} = props;
render(<View>{children}</View>);
};
const AddEntry = () => (
<ChoiceContainer>
<Card>
<View style={{paddingLeft: 5}}>
<Text style={styles.cardTitle}>Component 1</Text>
</View>
</Card>
</ChoiceContainer>
);
const RemoveEntry = () => (
<ChoiceContainer>
<Card>
<View style={{paddingLeft: 5}}>
<Text style={styles.cardTitle}>Component 2</Text>
</View>
</Card>
</ChoiceContainer>
);
const EntryHistory = () => (
<ChoiceContainer>
<Card>
<View style={{paddingLeft: 5}}>
<Text style={styles.cardTitle}>Component 3</Text>
</View>
</Card>
</ChoiceContainer>
);
export default class EntryTabs extends Component {
showAdd = () => {
this.setState({sceneType: 'add'});
};
showRemove = () => {
this.setState({sceneType: 'receive'});
};
showHistory = () => {
this.setState({sceneType: 'history'});
};
renderScene = type => {
if (type === 'add') {
return <AddEntry />;
}
if (type === 'remove') {
return <RemoveEntrty />;
}
if (type === 'history') {
return <EntryHistory />;
}
};
render() {
const {sceneType} = this.state;
render(
<SafeAreaView>
<View style={{flex: 1}}>
return (
<EntryCard
/>
);
})}
<View style={{flex:1}}>
<View>
<TouchableOpacity onPress={this.showAdd}>
<Text> Add </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showRemove}>
<Text> Remove </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showHistory}>
<Text> History </Text>
</TouchableOpacity>
</View>
<View>{this.renderScene(sceneType)}</View>
</View>,
</View>
</SafeAreaView>
);
}
}
Any help/guidance would huge hugely appreciated. Thanks!
You have not defined the state, And you have to use map to show the entries.
The working version of you code should be as below.
const value1 = 1,
value2 = 2,
value3 = 3;
const entries = [
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
{
key1: value1,
key2: value2,
key3: value3,
},
];
const ChoiceContainer = (props) => {
const { children } = props;
return <View>{children}</View>;
};
const AddEntry = () => (
<ChoiceContainer>
<Card>
<View style={{ paddingLeft: 5 }}>
<Text style={styles.cardTitle}>Add Entry</Text>
</View>
</Card>
<TextInput value={entries.key1} />
</ChoiceContainer>
);
const RemoveEntry = () => (
<ChoiceContainer>
<Card>
<View style={{ paddingLeft: 5 }}>
<Text style={styles.cardTitle}>Remove Entry</Text>
</View>
</Card>
<TextInput value={entries.key2} />
</ChoiceContainer>
);
const EntryHistory = () => (
<ChoiceContainer>
<Card>
<View style={{ paddingLeft: 5 }}>
<Text style={styles.cardTitle}>Entry History</Text>
</View>
</Card>
<TextInput value={entries.key3} />
</ChoiceContainer>
);
class EntryTabs extends React.Component {
state = {
sceneType: 'add',
};
showAdd = () => {
this.setState({ sceneType: 'add' });
};
showRemove = () => {
this.setState({ sceneType: 'receive' });
};
showHistory = () => {
this.setState({ sceneType: 'history' });
};
renderScene = (type) => {
if (type === 'add') {
return <AddEntry />;
}
if (type === 'remove') {
return <RemoveEntry />;
}
if (type === 'history') {
return <EntryHistory />;
}
};
render() {
const { sceneType } = this.state;
return (
<SafeAreaView>
<View style={{ flex: 1 }}>
{entries.map((item) => {
return (
<EntryHistory
baseAsset={item.key1}
logo={item.key2}
screen={item.key3}
/>
);
})}
<View style={{ flex: 1 }}>
<View>
<TouchableOpacity onPress={this.showAdd}>
<Text> Add </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showRemove}>
<Text> Remove </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.showHistory}>
<Text> History </Text>
</TouchableOpacity>
</View>
<View>{this.renderScene(sceneType)}</View>
</View>
,
</View>
</SafeAreaView>
);
}
}
function CustomText(props) {
const [showMore, setShowMore] = React.useState(false);
const [lines, setLines] = React.useState(props.numberOfLines);
const onTextLayout = (e) => {
setShowMore(
e.nativeEvent.lines.length > props.numberOfLines && lines !== 0
);
};
return (
<View>
<Text numberOfLines={lines} onTextLayout={onTextLayout}>
{props.children}
</Text>
&&{' '}
{showMore && (
<Button
title="Show More"
onPress={() => {
setLines(0);
setShowMore(false);
}}
/>
)}
{!showMore && (
<Button
title="Hide More"
onPress={() => {
setLines(props.numberOfLines);
}}
/>
)}
</View>
);
}

Invalid use of hooks when calling component with onPress

I'm trying to work with modals when I click on a button from the header.
Say I have this component List, and List is using custom navigation options:
import { CustomModal } from './components/Modal';
const List = (props) => {
const [enteredUrl, setEnteredUrl] = useState('');
const urlInputHandler = (enteredUrl) => {
setEnteredUrl(enteredUrl);
};
const addUrlHander = () => {
console.log(enteredUrl);
}
return (
<View></View>
);
};
List.navigationOptions = (navData) => {
return {
headerTitle: 'Workouts',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Add'
iconName='md-add'
onPress={() => {
CustomModal(); //here is the modal
}}
/>
</HeaderButtons>
),
headerBackTitle: null
};
};
My Modal component has this:
export const CustomModal = (props) => {
const [modalVisible, setModalVisible] = useState(false);
console.log(props);
return (
<Modal
animationType='slide'
transparent={false}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{ marginTop: 22 }}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
);
}
But it is giving me the invalid hook error. Why is it that my onPress in my navigationOptions giving me this? Am I doing this wrong?
onPress is a callback, you can't put components in it. Probably what you want is something like this:
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<CustomModal/>
</HeaderButtons>
and the modal looks like
export const CustomModal = (props) => {
const [modalVisible, setModalVisible] = useState(false);
console.log(props);
return modalVisible?(
<Modal
animationType='slide'
transparent={false}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{ marginTop: 22 }}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
):(
<Item
title='Add'
iconName='md-add'
onPress={() => setModalVisible(!modalVisible)}
/>
);
}

Show View when scroll up Scrollview

How to limit the quantity of View inside of a scrollview.
My component take too much time to render, because the map function renders too many views. I need to show only 10 views, and when scroll up, renders more 10.
I'm using react native, hooks and typescript.
First of all, if you have a large number of list data don't use scrollview. Because initially, it loads all the data to scrollview component & it costs performance as well.
Use flatlist in react-native instead of scrollview & you can limit the number of items to render in the initially using initialNumToRender. When you reach the end of the scroll position you can call onEndReached method to load more data.
A sample will like this
import React, { Component } from "react";
import { View, Text, FlatList, ActivityIndicator } from "react-native";
import { List, ListItem, SearchBar } from "react-native-elements";
class FlatListDemo extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const { page, seed } = this.state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
this.setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: page === 1 ? res.results : [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({ error, loading: false });
});
};
handleRefresh = () => {
this.setState(
{
page: 1,
seed: this.state.seed + 1,
refreshing: true
},
() => {
this.makeRemoteRequest();
}
);
};
handleLoadMore = () => {
this.setState(
{
page: this.state.page + 1
},
() => {
this.makeRemoteRequest();
}
);
};
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "#CED0CE",
marginLeft: "14%"
}}
/>
);
};
renderHeader = () => {
return <SearchBar placeholder="Type Here..." lightTheme round />;
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
render() {
return (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
keyExtractor={item => item.email}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
onRefresh={this.handleRefresh}
refreshing={this.state.refreshing}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={50}
/>
</List>
);
}
}
export default FlatListDemo;
Check this for more informations.
I changed to Flatlist! But initialNumToRender is not working as expected.
The flatlist is rendering all 150 transactions, not only 15, and i have no idea what to do.
I'm running .map() from another array with all others transactions to create newMonths with only those transactions that i want to use data={newMonths}.
let newMonths = [];
const createArrayMonth = histInfos.map(function (info) {
if (info.created_at.slice(5, 7) === month) {
newMonths = [info].concat(newMonths);
}
});
them, i created my component:
function Item({ value }: { value: any }) {
let createdDay = value.item.created_at.slice(8, 10);
let createdMonth = value.item.created_at.slice(5, 7);
let createdYear = value.item.created_at.slice(2, 4);
function dateSelected() {
if (
month === createdMonth &&
year === createdYear &&
(day === '00' || day == createdDay)
) {
console.log('foi dateSelected');
const [on, setOn] = useState(false);
const Details = (on: any) => {
if (on === true) {
return (
<View style={styles.infos}>
<Text style={styles.TextInfos}>
{' '}
CPF/CNPJ: {value.item.cpf_cnpj}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Criado em: {value.item.created_at}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Endereço da carteira: {value.item.address}{' '}
</Text>
<Text style={styles.TextInfos}> Valor: {value.item.amount}BTC </Text>
</View>
);
} else {
return <View />;
}
};
return (
<View>
<TouchableOpacity
style={styles.card}
onPress={() => setOn(oldState => !oldState)}>
<View style={styles.dateStyleView}>
<Text style={styles.dateStyle}>{createdDay}</Text>
</View>
<View style={styles.left}>
<Text style={styles.title}>Venda rápida</Text>
<Text style={styles.semiTitle}>
{
{
0: 'Pendente',
1: 'Aguardando conclusão',
2: 'Cancelado',
100: 'Completo',
}[value.item.status]
}
</Text>
</View>
<View style={styles.right2}>
<Text style={styles.price}>R$ {value.item.requested_value}</Text>
</View>
</TouchableOpacity>
<View>{Details(on)}</View>
</View>
);
}
}
return dateSelected();}
and i call it here
return (
<ScrollView>
...
<View style={styles.center}>
...
<View style={styles.middle2}>
...
<FlatList
extraData={[refresh, newMonths]}
data={newMonths}
renderItem={(item: any) => <Item value={item} />}
keyExtractor={(item, index) => index.toString()}
initialNumToRender={15}
/>
</View>
</View>
</ScrollView>);}
The scroll bar in the right, start to increase until renders all transactions from the data:
App scroll bar

Resources