useState-hook is not updating my state in an onPress() function - reactjs

I am building a tab-view and I cannot figure out why the useState-hook is not updating my state. I am sure it is something easy, but I have been stumped here a while now...
I can see that the onPress function is fired and that item.label is the correct one if I log that out. However, setState does not change, even if I hardcode the parameter.
const TabContainer = ({ tabs }) => {
const [selected, setSelected] = useState('');
function renderItem(item, index) {
return item.label === selected ? (
<View style={styles.selectedTab}>
<CustomText style={styles.tabText}>{item.label}</CustomText>
</View>
) : (
<TouchableWithoutFeedback
key={index}
onPress={() => {
console.log(selected);
setSelected(item.label);
}}
>
<View style={styles.tab}>
<CustomText style={styles.tabText}>{item.label}</CustomText>
</View>
</TouchableWithoutFeedback>
);
}
return (
<View style={styles.tabContainer}>
<FlatList
keyExtractor={(item, index) => index.toString()}
data={tabs}
renderItem={({ item, index }) => {
return renderItem(item, index);
}}
style={styles.listContainer}
/>
</View>
);
};
export default TabContainer;

Try this :
<
TouchableWithoutFeedback
key={index}
onPress={() => onPressItem(item)}
>
const onPressItem =(item)=>{
setSelected(item.label)}

Sometimes, using useRef instead of useState when you want to set a value at a different stage in the lifecycle than the constructor.
Please take a look at this and try to implement this in your code:
[https://codesandbox.io/s/v6948pww5y?from-embed]

Okey I kinda solved this. I realized that I had to lift my state up for my tabs in order to render the other stuff properly. So then I just implemented in the same way in the parent component, and then it worked...
Important rows from parent:
const [selectedTab, setSelectedTab] = useState('');
function handleTabPress(tab) {
setSelectedTab(tab);
}
...
<TabContainer
tabs={[
{ label: 'Label 1' },
{ label: 'Label 2' },
{ label: 'Label 3' },
]}
selectedTab={selectedTab}
handleTabPress={handleTabPress}
/>
New child
const TabContainer = ({ tabs, selectedTab, handleTabPress }) => {
function renderItem(item, index) {
return item.label === selectedTab ? (
<View style={styles.selectedTab}>
<CustomText style={styles.selectedTabText}>{item.label}</CustomText>
</View>
) : (
<TouchableWithoutFeedback
key={index}
onPress={() => {
handleTabPress(item.label);
}}
>
<View style={styles.tab}>
<CustomText style={styles.tabText}>{item.label}</CustomText>
</View>
</TouchableWithoutFeedback>
);
}
return (
<View style={styles.tabContainer}>
<FlatList
keyExtractor={(item, index) => index.toString()}
data={tabs}
renderItem={({ item, index }) => {
return renderItem(item, index);
}}
style={styles.listContainer}
/>
</View>
);
};

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}
...
/>

Mobx store do not update with observer

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);
}
}

How can I pass a variable to the previous screen by using React Navigation?

I'm using React Native. I tried to write this code:
//Screen A
//ignored the "import"
export function Hoge(Props) {
let onSelect = false
return(
<View>
<View>
<Text>{onSelect ? 'Selected' : 'Not Selected' }</Text>
</View>
<TouchableOpacity
onPress={() => {
navigation.navigate('ScreenB', { onSelect: onSelect });
}};>
<View><Text>Screen B</Text></View>
</TouchableOpacity>
</View>
)
}
//ScreenB
export function HogeHoge(Props) {
const [hoge, setHoge ] = useState(route.params.onSelect)
function goBack() {
setHoge(true)
navigation.goBack();
}
let screen = navigation.setOptions({
headerLeft: () => (
<TouchableOpacity onPress={() => {
goBack();
}}>
<Image
source={require('../../images/back_btn.png')}
style={commonStyles.back_btn}
/>
</TouchableOpacity>
),
})
return (
<View>{screen}</View>
)
};
referring to the following: https://github.com/react-navigation/react-navigation/issues/288
I hoped the "onSelect" is true, but it was false. How can I fix it?

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>
);
}

Is it possible to prevent a modal from hiding if it's visibility prop rerenders?

I have a component with four different lists of items. An item is placed in a list depending on the date. When the user clicks a button to change the date of that item a modal appears with a date picker (the item's dateModalVisbility prop switches to true). This works as expected but then if an item's date change causes it to switch lists, the date picker modal disappears without the prop switching to false. Then when you click the button to reopen it switches to false and then after another press switches back to true and reopens. I believe this is caused by the rerender of the item when it switches into a different list.
I would like to force the modal to stay open even when it switches lists. I have tried making the different lists functions that I call before anything else renders but this did not solve the issue. I am out of ideas and unsure if this is even possible to do.
My flatlists (this.props.todos comes from redux):
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (moment().isSame(item.date, 'day')) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
<View style={styles.headerViewStyle}>
<Text style={styles.headerTextStyle}>Tomorrow</Text>
</View>
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (
moment()
.add(1, 'day')
.isSame(item.date, 'day')
) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
<View style={styles.headerViewStyle}>
<Text style={styles.headerTextStyle}>Upcoming</Text>
</View>
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (
moment()
.add(1, 'day')
.isBefore(item.date, 'day')
) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
<View style={styles.headerViewStyle}>
<Text style={styles.headerTextStyle}>Sometime</Text>
</View>
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (moment().isAfter(item.date, 'day') || item.date === null) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
The todoItem component:
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<ItemSwipeRow item={todoItem} completeItem={this.props.deleteTodo}>
<TouchableHighlight
onPress={() => this.props.toggleItemMenu(todoItem)}
underlayColor={null}>
<ListItem
containerStyle={styles.todoItem}
contentContainerStyle={styles.contentStyle}
title={todoItem.text}
titleStyle={{ color: '#FCEFEF', fontSize: 16 }}
rightElement={todoItem.date ? this.renderDate.bind(this)() : null}
/>
</TouchableHighlight>
</ItemSwipeRow>
{todoItem.itemMenuToggled ? <ItemMenuBar item={todoItem} /> : null}
{this.props.reminderToggleActive && todoItem.date ? (
<ReminderToggleButtons item={todoItem} />
) : null}
<NotesModal item={todoItem} />
{todoItem.dateModalVisible ? <DatePickerModal item={todoItem} /> : null}
</View> //this line above is responsible for displaying the date picker modal
);
}
}
And the DatePickerModal:
class DatePickerModal extends Component {
render() {
return (
<Modal transparent animationType="fade" visible>
<View style={styles.containerStyle}>
<View style={styles.modalContainer}>
<View style={{ justifyContent: 'flex-end', flexDirection: 'row' }}>
<View style={{ padding: 5 }}>
<Feather
name="x-square"
size={35}
color={'#db5461'}
onPress={() => this.props.toggleDateModal(this.props.item)}
/>
</View>
</View>
<View style={{ padding: 5 }}>
{Platform.OS === 'ios' ? (
<IosDatePicker item={this.props.item} />
) : (
<AndroidDatePicker />
)}
</View>
</View>
</View>
</Modal>
);
}
}
I can provide the actions and button that opens the item but I don't think that's where the issue is coming from. The button/actions are doing their job right.
I would re-arrange the way you are using the modal. Instead of each component having a modal add one modal to the top level component and then use props to set the items that was selected. Somthing like this:
export default class TopComponent extends React.Component {
this.state = {
selectedItem: null,
modalvisible: false,
}
render = () => {
return (
<FlatList renderItem={({item, key} => (<MyItem onItemSelected={item => this.setState({ selectedItem: item, modalVisible: true}))} />} />
<Modal visible={this.state.modalVisible}>
<Text>this.state.selectedItem</Text>
</Modal>
}
}
Here is a quick codesandbox to show some more details: https://codesandbox.io/s/react-native-6518p

Resources