Calling a redux action using props, from stack-navigator-options - react-native

I want to call an action from redux inside the stack navigator options. This is my code. When I use the action using this.props.action_name, it says this is not a function. What am I doing wrong here? I used the navigation.params to achieve this, but still it doesn't work.
componentDidMount() {
this.props.navigation.setParams({
referencedSharePost: this.sharePost,
referencedDoShare: this.props.doShare
});
}
sharePost = () => {
this.props.doShare();
console.log("DONE");
};
render() {
return (
<View style={styles.container}>
<ScrollView>
<WritePost profile={this.state.loggedUserProfile} />
<View style={styles.sharePostWrapper}>
<PostProfileBar profile={this.state.postedUserProfile} />
<Image
source={{
uri: "https://pbs.twimg.com/media/DWvRLbBVoAA4CCM.jpg"
}}
resizeMode={"stretch"}
style={styles.image}
/>
</View>
</ScrollView>
</View>
);
}
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
headerTitle: "Share To Feed",
headerTitleStyle: {
paddingLeft: "20%",
paddingRight: "20%"
},
headerStyle: {
paddingRight: 10,
paddingLeft: 10
},
headerLeft: (
<Icon
name={"close"}
size={30}
onPress={() => {
navigation.goBack();
}}
/>
),
headerRight: (
<ButtonWithoutBackground
buttonText={styles.buttonText}
onPress={() => params.referencedSharePost()}
>
Post
</ButtonWithoutBackground>
)
};
};
This is how I map my state to props.
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import SharePostScreen from "./SharePostScreen";
import { doShare } from "../../config/actions";
const mapStateToProps = state => {
return {
sharedPost: state.mainFeed.updatedPost
};
};
const mapDispatchToProps = dispatch => {
return {
doShare: bindActionCreators(doShare, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SharePostScreen);
I want to use this.props.doShare inside onPress() of stackNavigationOptions.

You don't need to separately connect SharePostScreen after exporting it as a default, since it has got no instance to the redux states, during the time component's execution happens.
Therefore you can move your code in the second snippet, to the SharePostScreen and use it there, to have an access to the props.
const mapStateToProps = state => {
return {
sharedPost: state.mainFeed.updatedPost
};
};
const mapDispatchToProps = dispatch => {
return {
doShare: bindActionCreators(doShare, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SharePostScreen);

Related

how to use react native redux in react navigation

I am using redux persist with asyncstorage save items in a bookmarks list. The items are in a flatlist and when I click on one item, it navigates me to another screen. I would like to implement the functional bookmark in the header of that screen.
When I tried doing this, and clicked the bookmark in the header, and go back to the bookmarks, it just shows a blank card. It looks like it is not updating the state properly. How can I fix this?
StackNavigator.tsx
const MainStackNavigator = () => {
const { books, bookmarks } = useAppSelector((state) => state.booksReducer);
const dispatch = useDispatch();
const fetchBooks = () => dispatch(getBooks());
const addToBookmarkList = (book) => dispatch(addBookmark(book));
const removeFromBookmarkList = (book) => dispatch(removeBookmark(book));
useEffect(() => {
fetchBooks();
}, []);
const handleAddBookmark = (book) => {
addToBookmarkList(book);
};
const handleRemoveBookmark = (book) => {
removeFromBookmarkList(book);
};
const handleSwapBookmark = (book) => {
removeFromBookmarkList(book);
};
const RenderItem = () => {
const ifExists = (book) => {
if (bookmarks.filter((item) => item.id === book.id).length > 0) {
return true;
}
return false;
};
return (
<TouchableOpacity
onPress={() =>
ifExists(i) ? handleRemoveBookmark(i) : handleAddBookmark(i)
}
activeOpacity={0.7}
style={{
flexDirection: "row",
padding: 2,
backgroundColor: ifExists(i) ? "#F96D41" : "#2D3038",
borderRadius: 20,
alignItems: "center",
justifyContent: "center",
height: 40,
width: 40,
}}
>
<MaterialCommunityIcons
color={ifExists(i) ? "white" : "#64676D"}
size={24}
name={ifExists(i) ? "bookmark-outline" : "bookmark"}
/>
</TouchableOpacity>
);
};
return (
<AppStack.Navigator>
<AppStack.Screen
name="BookmarksScreen"
component={BookmarksScreen}
options={{
title: "Search",
statusBarColor: isDarkMode ? "white" : "black",
headerLargeTitle: true,
headerTranslucent: true,
headerLargeTitleHideShadow: true,
}}
/>
<AppStack.Screen
name="Screen2"
component={Screen2}
options={({ route }) => ({
headerLargeTitle: false,
title: route.params.name,
headerTranslucent: true,
headerRight: () => <RenderItem item={route.params.name} />,
})}
/>
</AppStack.Navigator>
);
};
actions.js
import axios from "axios";
import { BASE_URL } from "../config";
// Define action types
export const GET_BOOKS = "GET_BOOKS";
export const ADD_TO_BOOKMARK_LIST = "ADD_TO_BOOKMARK_LIST";
export const REMOVE_FROM_BOOKMARK_LIST = "REMOVE_FROM_BOOKMARK_LIST";
export const SWAP_IN_BOOKMARK_LIST = "SWAP_IN_BOOKMARK_LIST";
export const getBooks = () => {
try {
return async (dispatch) => {
const response = await axios.get(`${BASE_URL}`);
if (response.data) {
dispatch({
type: GET_BOOKS,
payload: response.data,
});
} else {
console.log("Unable to fetch data from the API BASE URL!");
}
};
} catch (error) {
console.log(error);
}
};
export const addBookmark = (book) => (dispatch) => {
dispatch({
type: ADD_TO_BOOKMARK_LIST,
payload: book,
});
};
export const removeBookmark = (book) => (dispatch) => {
dispatch({
type: REMOVE_FROM_BOOKMARK_LIST,
payload: book,
});
};
hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
reducers.ts
import {
GET_BOOKS,
ADD_TO_BOOKMARK_LIST,
REMOVE_FROM_BOOKMARK_LIST,
} from "./actions";
const initialState = {
books: [],
bookmarks: [],
};
function booksReducer(state = initialState, action) {
switch (action.type) {
case GET_BOOKS:
return { ...state, books: action.payload };
case ADD_TO_BOOKMARK_LIST:
return { ...state, bookmarks: [...state.bookmarks, action.payload] };
case REMOVE_FROM_BOOKMARK_LIST:
return {
...state,
bookmarks: state.bookmarks.filter(
(book) => book.id !== action.payload.id
),
};
default:
return state;
}
}
export default booksReducer;
store.ts
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import AsyncStorage from "#react-native-async-storage/async-storage";
import { persistStore, persistReducer } from "redux-persist";
import booksReducer from "./reducers";
const persistConfig = {
key: "root",
storage: AsyncStorage,
whitelist: ["bookmarks"],
};
const rootReducer = combineReducers({
booksReducer: persistReducer(persistConfig, booksReducer),
});
export const store = createStore(rootReducer, applyMiddleware(thunk));
export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
BookmarksScreen.tsx
const BookmarksScreen = () => {
return (
<View>
<FlatList
data={bookmarks}
keyExtractor={(item) => item.id}
renderItem={renderItem}
showsVerticalScrollIndicator={false}
/>
</View>
);
}
renderItem
const renderItem = ({ item }) => {
return (
<TouchableOpacity
onPress={() =>
navigation.navigate("Screen2", {name: item.name})}
>
<View style={{ flexDirection: "row", flex: 1 }}>
<View>
<Text
style={{
fontSize: 22,
paddingRight: 16,
color: "black",
fontFamily: "Medium",
left: 45,
top: 6,
}}
>
{item.country}
</Text>
</View>
</View>
</View>
</TouchableOpacity>
);
};
I think you forgot to access props that's the reason booked-marked not working i change some code please check it's working or not.
StackNavigator.tsx
const RenderItem = (item) => {
const ifExists = (book) => {
if (bookmarks.filter((item) => item.id === book.id).length > 0) {
return true;
}
return false;
};
return (
<TouchableOpacity
onPress={() =>
ifExists(item) ? handleRemoveBookmark(item) : handleAddBookmark(item)
}
activeOpacity={0.7}
style={{
flexDirection: "row",
padding: 2,
backgroundColor: ifExists(i) ? "#F96D41" : "#2D3038",
borderRadius: 20,
alignItems: "center",
justifyContent: "center",
height: 40,
width: 40,
}}
>
<MaterialCommunityIcons
color={ifExists(i) ? "white" : "#64676D"}
size={24}
name={ifExists(i) ? "bookmark-outline" : "bookmark"}
/>
</TouchableOpacity>
);
};

Redux reducer not changing prop

I am making a todo list application with redux. I am able to add todos perfectly fine with redux however my toggle todos and remove todos are having problems.
The toggle todo action gets called by the redux store (I see it happening in the debugger), however, it does not update the prop to be the opposite of completed and I am not sure why.
I have tried playing around with the syntax and modeling other people's redux todo lists for hours but have not been able to solve this issue.
My toggleTodo and removeTodo actions:
export const toggleTodo = (item) => {
return {
type: TOGGLE_TODO,
id: item.id
};
};
export const removeTodo = (item) => {
return {
type: REMOVE_TODO,
id: item.id
};
};
My TodoReducer: // this is where I suspect the problem is
const initialState = {
todos: []
};
const todos = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_TODO:
if (state.id !== action.id) {
return state;
}
return {
...state, completed: !state.todos.completed
};
case REMOVE_TODO: {
const newState = [...state];
newState.splice(action.id, 1);
return { ...newState };
}
My main flatlist where I call the actions:
render() {
return (
<View style={{ height: HEIGHT }}>
<FlatList
data={this.props.todos}
extraData={this.state}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return (
<TodoItem
todoItem={item}
pressToToggle={() => this.props.toggleTodo(item)}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}}
/>
</View>
);
}
}
export default connect(mapStateToProps, { addTodo, toggleTodo, removeTodo })(MainTodo);
// I call the actions I am using here and don't use mapDispatchToProps
And my TodoItem component where I pass in the props:
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<TouchableOpacity
style={styles.todoItem}
onPress={this.props.pressToToggle}
>
<Text
style={{
color: todoItem.completed ? '#aaaaaa' : '#f5f5f5',
textDecorationLine: todoItem.completed ? 'line-through' : 'none',
fontSize: 16 }}
>
{todoItem.text}
</Text>
<Button
title='Remove'
color='#ff5330'
onPress={this.props.deleteTodo}
/>
</TouchableOpacity>
</View>
);
}
}
When I hit toggle todo instead of the prop changing and the line coming through over the text nothing happens.
And when I try to remove a todo I get this error- "invalid attempt to spread non-iterable instance."
when you pass a function to component, try to pass it's reference, instead of
<TodoItem
todoItem={item}
pressToToggle={() => this.props.toggleTodo(item)}
deleteTodo={() => this.props.removeTodo(item)}
/>
try
<TodoItem
todoItem={item}
pressToToggle={this.props.toggleTodo.bind(this)}
deleteTodo={this.props.removeTodo.bind(this)}
/>
and in your TodoItem component call the function like
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<TouchableOpacity
style={styles.todoItem}
onPress={() => this.props.pressToToggle(todoItem)} /* this line */
>
<Text
style={{
color: todoItem.completed ? '#aaaaaa' : '#f5f5f5',
textDecorationLine: todoItem.completed ? 'line-through' : 'none',
fontSize: 16 }}
>
{todoItem.text}
</Text>
<Button
title='Remove'
color='#ff5330'
onPress={this.props.deleteTodo}
/>
</TouchableOpacity>
</View>
);
}
}

Expo SDK 29 FlatList onRefresh not calling

Using Expo SDK 29 for a react native application.
I would like to use a flat list component. This makes up the entirety of a SafeAreaView component. I make this point as there are lots of issues relating to a flat list inside of a scroll view which this is not.
The flat list shows a list of jobs.
I have added a jobLoading boolean to the redux state to manage when the list should show as refreshing and can confirm that this toggles as expected when firing the actions to fetch the data and the success.
When i add the props to the flat list for onRefresh and refreshing the component seems to work by showing the activity indicator in the UI but does not fire the onRefresh function. I have tried implementing the call in numerous ways but nothing happens. The result is that the activity indicator shows itself and never disappears.
As it's Expo SDK 29 the React Native version is 0.55.4
Anyone have any ideas of what to try. I've spent a couple of hours looking at this trying various things but suggestions are welcome.
Thanks in advance.
EDIT: Added the code for reference. Reducer for refreshing sets true when fetchJobs() is dispatched and false when a success or error is recieved. The console log for onRefresh never triggers.
import * as React from 'react'
import * as actions from '../../redux/actions'
import { ActivityIndicator, FlatList, KeyboardAvoidingView, Dimensions, SafeAreaView, StyleSheet, View } from 'react-native'
import { ApplicationState, JobState, Job } from '../../redux'
import { Button, Form, Input, Item, Text, Icon } from 'native-base'
import { JobListItem } from './jobListItem'
import { StateHandlerMap, compose, lifecycle, withPropsOnChange, withStateHandlers } from 'recompose'
import { connect } from 'react-redux'
interface ReduxStateProps {
jobs: JobState
refreshing: boolean
screenOrientation: string
}
interface ReduxDispatchProps {
fetchJobs: (param?: string) => any
}
export interface DataItem {
key: string
data: Job
}
interface ListProps {
jobList: DataItem[]
}
interface SearchStateProps {
timer: number | undefined
searchString: string
}
interface SearchHandlerProps extends StateHandlerMap<SearchStateProps> {
updateSearch: (searchString: string) => any
setTimer: (timer: number | undefined) => any
}
type OuterProps = {}
type InnerProps = OuterProps & ReduxStateProps & ReduxDispatchProps & ListProps & SearchStateProps & SearchHandlerProps
const enhance = compose<InnerProps, OuterProps>(
connect<ReduxStateProps, ReduxDispatchProps, OuterProps, ApplicationState>(
state => ({
jobs: state.job,
refreshing: state.jobLoading,
screenOrientation: state.screenOrientation
}),
dispatch => ({
fetchJobs: (param?: string) => dispatch(actions.jobs.request({ param }))
})
),
withPropsOnChange<ListProps, OuterProps & ReduxStateProps & ReduxDispatchProps>(
['jobs', 'screenOrientation'],
props => ({
jobList: props.jobs && Object.keys(props.jobs).map(job => ({ key: job, data: props.jobs[Number(job)] }))
})
),
withStateHandlers<SearchStateProps, SearchHandlerProps, OuterProps>(
{
timer: undefined,
searchString: ''
},
{
updateSearch: state => (searchString: string) => ({ searchString }),
setTimer: state => (timer: number | undefined) => ({ timer })
}
),
lifecycle<InnerProps, {}>({
componentDidMount() {
this.props.fetchJobs()
}
})
)
export const JobList = enhance(({ fetchJobs, jobList, refreshing, screenOrientation, searchString, setTimer, timer, updateSearch }) => {
const onSearchChange = (search: string) => {
clearTimeout(timer)
updateSearch(search)
const timing = setTimeout(() => {
fetchJobs(search)
}, 500)
setTimer(timing)
}
const onRefresh = () => {
console.log('requesting refresh')
fetchJobs()
}
return (
<SafeAreaView style={{ flex: 1}}>
<KeyboardAvoidingView style={{ flexDirection: 'row', justifyContent: 'space-evenly', paddingTop: 3, paddingRight: 3 }}>
<Form style={{ flex: 1, paddingLeft: 10, paddingRight: 10 }}>
<Item>
<Input
value={searchString}
onChangeText={(text: string) => onSearchChange(text)}
placeholder='Search'
/>
</Item>
</Form>
<Button onPress={() => {fetchJobs(); updateSearch('')}}>
<Icon name='refresh' />
</Button>
</KeyboardAvoidingView>
{refreshing &&
<View style={styles.refreshContainer}>
<Text style={{ paddingBottom: 10 }}>Fetching Data</Text>
<ActivityIndicator />
</View>
}
<FlatList
keyExtractor={item => item.key}
data={jobList}
renderItem={({ item }) =>
<JobListItem
screenOrientation={screenOrientation}
item={item}
/>
}
onRefresh={onRefresh}
refreshing={refreshing}
/>
</SafeAreaView>
)
})
const styles = StyleSheet.create({
refreshContainer: {
height: 60,
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}
})
I'm having the exact same issue and I'm using expo SDK 30. But my case is a little bit different. The onRefresh function is called everytime I pull, however if I scroll down my list, and scroll back up fast, the loading indicator shows up, but my onRefresh function is not called.
My refreshing prop is set on my reducer, and my onRefresh function dispatches an action that fetches data and set refreshing true and false.
Here is my code:
class NoticiasScreen extends Component {
static navigationOptions = {
header: <Header
title='Notícias Alego'
leftComponent={<Image source={require('../../../assets/images/play_grande.png')} style={imageStyle} resizeMode='contain'/>}
/>
}
constructor(props) {
super(props);
this.renderItem = this.renderItem.bind(this);
this.keyExtractor = this.keyExtractor.bind(this);
this.renderContent = this.renderContent.bind(this);
this.navigateToNoticias = this.navigateToNoticias.bind(this);
this.carregarMaisNoticias = this.carregarMaisNoticias.bind(this);
this.onRefresh = this.onRefresh.bind(this);
}
componentDidMount() {
this.props.carregarNoticias(this.props.pagina);
}
renderItem({item}) {
return (
<NoticiaListItem noticia={item} abrirNoticia={this.navigateToNoticias} />
);
}
keyExtractor(item) {
return item.id.toString();
}
navigateToNoticias(noticia) {
this.props.navigation.navigate('NoticiasExibir', { id: noticia.id });
}
onRefresh() {
console.log('onRfresh');
this.props.carregarNoticias(1, true);
}
carregarMaisNoticias() {
const { carregarNoticias, pagina } = this.props;
carregarNoticias(pagina + 1);
}
renderContent() {
const { noticias, carregandoNoticias, erroNoticias } = this.props;
if(noticias.length === 0 && carregandoNoticias) {
return (
<View style={styles.containerCenter}>
<ActivityIndicator size="large" color={colors.verde}/>
</View>
);
}
if(erroNoticias) {
return (
<View style={styles.containerCenter}>
<Text style={styles.message}>{erroNoticias}</Text>
<TouchableOpacity hitSlop={hitSlop15}>
<Text>Recarregar</Text>
</TouchableOpacity>
</View>
)
}
return (
[<TextInput
style={styles.textInput}
placeholder='Pesquise'
key='pesquisa'
underlineColorAndroid='transparent'
/>,
<FlatList
data={noticias}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
style={styles.list}
key='lista'
onRefresh={this.onRefresh}
refreshing={carregandoNoticias}
onEndReached={this.carregarMaisNoticias}
onEndReachedThreshold={0.1}
/>]
)
}
render() {
return (
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
{this.renderContent()}
</View>
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return {
noticias: state.intranet.noticias,
pagina: state.intranet.pagina,
erroNoticias: state.intranet.erroNoticias,
carregandoNoticias: state.intranet.carregandoNoticias
}
}
function mapDispatchToProps(dispatch) {
return {
carregarNoticias: (pagina, recarregar) => dispatch(ActionCreator.carregarNoticias(pagina, recarregar))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NoticiasScreen);
No idea what's going on. Any help is appreciated.
EDIT:
I fixed it somehow. I added the onMomentScrollBegin prop to prevent my flatList from rendering twice on Render, and that fixed this issue.
here is what I added:
constructor(props) {
super(props);
...
this.onRefresh = this.onRefresh.bind(this);
this.onMomentumScrollBegin = this.onMomentumScrollBegin.bind(this);
this.onEndReachedCalledDuringMomentum = true; //PUT THIS HERE
}
onRefresh() {
this.props.carregarNoticias(1, true);
}
carregarMaisNoticias() {
if(!this.onEndReachedCalledDuringMomentum){
const { carregarNoticias, pagina } = this.props;
carregarNoticias(pagina + 1);
this.onEndReachedCalledDuringMomentum = true;
}
}
onMomentumScrollBegin() {
this.onEndReachedCalledDuringMomentum = false;
}
render() {
<OptimizedFlatList
data={noticias}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
style={styles.list}
key='lista'
onRefresh={this.onRefresh}
refreshing={carregandoNoticias}
onMomentumScrollBegin={this.onMomentumScrollBegin} //PUT THIS HERE
onEndReached={this.carregarMaisNoticias}
onEndReachedThreshold={0.1}
/>
}

function is not a function - react-native

In my project, I used stack navigator as the navigator. Inside navigationOptions, at headerRight, I used a custom button. When I try to call the onPress, it says the function is not a function.
this is the function
sharePost() {
// this.props.doShare();
console.log("DONE");
}
I have put the full code here. I want to use sharePost function inside the rightHeader of navigationOptions.
import React, { Component } from "react";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import {
View,
Text,
Image,
TouchableOpacity,
Animated,
ScrollView,
StyleSheet,
Dimensions
} from "react-native";
import { PostProfileBar, WritePost } from "../../components";
import { ButtonWithoutBackground } from "../../mixing/UI";
const width = Dimensions.get("window").width;
const height = Dimensions.get("window").height / 3;
class SharePostScreen extends Component {
constructor(props) {
super(props);
sharePost() {
// this.props.doShare();
console.log("DONE");
}
render() {
return (
<View style={styles.container}>
<ScrollView>
<WritePost profile={this.state.loggedUserProfile} />
<View style={styles.sharePostWrapper}>
<PostProfileBar profile={this.state.postedUserProfile} />
<Image
source={{
uri: "https://pbs.twimg.com/media/DWvRLbBVoAA4CCM.jpg"
}}
resizeMode={"stretch"}
style={styles.image}
/>
</View>
</ScrollView>
</View>
);
}
static navigationOptions = ({ navigation }) => ({
headerTitle: "Share To Feed",
headerTitleStyle: {
paddingLeft: "20%",
paddingRight: "20%"
},
headerStyle: {
paddingRight: 10,
paddingLeft: 10
},
headerLeft: (
<Icon
name={"close"}
size={30}
onPress={() => {
navigation.goBack();
}}
/>
),
headerRight: (
<ButtonWithoutBackground
buttonText={styles.buttonText}
onPress={this.sharePost()}
>
Post
</ButtonWithoutBackground>
)
});
}
In order to call the class level methods inside the static method navigationOptions, you can do the following,
// Bind function using setParams
componentDidMount() {
const { navigation } = this.props;
navigation.setParams({
referencedSharePost: this.sharePost,
});
}
sharePost = (parameters) => {
// this.props.doShare();
console.log("DONE" + parameters);
}
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
//... Other Options
headerRight: (
<ButtonWithoutBackground
buttonText={styles.buttonText}
onPress={() => params.referencedSharePost("I'm passing something")}
>
Post
</ButtonWithoutBackground>
)
}
}

How to call Screen / Component class method from react-navigation Header

I need to call SearchScreen class method from a React Navigation Header.
The Navigator look like this:
Search: {
screen: SearchScreen,
path: 'search/:query',
navigationOptions: {
title: 'Search',
header: {
right: (
<MaterialCommunityIcons
name="filter"
onPress={() => {
console.log(this);
}}
style={{marginRight: 15, color: 'white'}}
size={24}
/>
),
},
}
}
I've made it work by doing:
// declare static navigationOptions in the Component
static navigationOptions = {
title: 'Title',
header: ({ state }) => ({
right: (
<MaterialCommunityIcons
name="filter"
onPress={state.params.handleFilterPress}
style={{marginRight: 15, color: 'white'}}
size={24}
/>
),
}),
}
_handleFilterPress() {
// do something
}
componentDidMount() {
// set handler method with setParams
this.props.navigation.setParams({
handleFilterPress: this._handleFilterPress.bind(this)
});
}
I've resolved the issue the following way:
static navigationOptions = ({ navigation }) => {
return {
headerRight: () => (
<TouchableOpacity
onPress={navigation.getParam('onPressSyncButton')}>
<Text>Sync</Text>
</TouchableOpacity>
),
};
};
componentDidMount() {
this.props.navigation.setParams({ onPressSyncButton: this._onPressSyncButton });
}
_onPressSyncButton = () => {
console.log("function called");
}
Hooks solution with FunctionComponent, useState and useEffect
Ref the official docs (https://reactnavigation.org/docs/en/header-buttons.html#header-interaction-with-its-screen-component) it is done by:
class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: <LogoTitle />,
headerRight: (
<Button
onPress={navigation.getParam('increaseCount')}
title="+1"
color="#fff"
/>
),
};
};
componentDidMount() {
this.props.navigation.setParams({ increaseCount: this._increaseCount });
}
state = {
count: 0,
};
_increaseCount = () => {
this.setState({ count: this.state.count + 1 });
};
/* later in the render function we display the count */
}
However I could not get this to work when working with the hooks api. My state variables were always undefined, but after I thought about how the hooks api is implemented it all made sense, so the solution was to update the navigation param every time a significant state variable was changed:
const [count, setCount] = useState(0);
useEffect(() => {
props.navigation.setParams({ increaseCount });
}, [count]);
const increaseCount = () => setCount(count + 1);
I came across same issue and able to resolve the issue from below links.
class MyScreen extends React.Component {
static navigationOptions = {
header: {
right: <Button title={"Save"} onPress={() => this.saveDetails()} />
}
};
saveDetails() {
alert('Save Details');
}
render() {
return (
<View />
);
}
}
Source: react-native issues 145
Below is my code
import React, { Component } from "react";
import {
Container,
Header,
Item,
Input,
Icon,
Button,
Text,
Left,
Body,
Right,
Content,
Spinner,
List,
ListItem
} from "native-base";
import { View, Image, StyleSheet, Keyboard } from "react-native";
import { connect } from "react-redux";
import {
onClear,
onSearchTextChanged,
searchForProfiles
} from "../../actions/searchActions";
class SearchBar extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Header searchBar rounded>
<Button
iconLeft
style={{ paddingLeft: 0 }}
light
onPress={this.props.onBackPress}
>
<Icon style={{ marginLeft: 0, fontSize: 35 }} name="arrow-back" />
</Button>
<Item>
<Icon name="ios-search" />
<Input
placeholder="Search"
onChangeText={this.props.onChangeText}
value={this.props.searchText}
/>
<Button small transparent onPress={this.props.onClear}>
<Icon name="ios-close" />
</Button>
</Item>
<Button transparent onPress={this.props.onSearch}>
<Text>Search</Text>
</Button>
</Header>
);
}
}
class SearchWorld extends Component {
static navigationOptions = ({ navigation }) => ({
left: null,
header: () => {
const { state } = navigation;
return (
<SearchBar
onBackPress={() => navigation.goBack()}
onChangeText={text => {
state.params.onChangeText(text);
}}
onSearch={state.params.onSearch}
onClear={state.params.onClear}
searchText={state.params.searchText}
/>
);
}
});
onChangeText = text => {
this.props.navigation.setParams({
...this.props.navigation.state,
searchText: text
});
this.props.onSearchTextChanged(text);
};
onSearch = () => {
Keyboard.dismiss();
const profile = { search: "test" };
const token = this.props.token;
this.props.searchForProfiles(token, profile);
};
onClear = () => {
this.props.onClear();
this.props.navigation.setParams({
...this.props.navigation.state,
searchText: ""
});
};
componentDidMount() {
this.props.navigation.setParams({
onChangeText: this.onChangeText,
onSearch: this.onSearch,
onClear: this.onClear,
searchText: this.props.searchText
});
}
render() {
const { searchResults } = this.props;
let items = [];
if(searchResults && searchResults.data && searchResults.data.length > 0) {
items = [...searchResults.data];
}
return this.props.loading ? (
<Container style={{ alignItems: "center", justifyContent: "center" }}>
<Spinner color="#FE6320" />
</Container>
) : (
<Container>
<Content>
<List
style={{}}
dataArray={items}
renderRow={item => (
<ListItem style={{ marginLeft: 0}}>
<Text style={{paddingLeft: 10}}>{item.password}</Text>
</ListItem>
)}
/>
</Content>
</Container>
);
}
}
const mapStateToProps = state => {
const { token } = state.user;
const { searchText, searchResults, error, loading } = state.searchReaducer;
return {
token,
searchText,
searchResults,
error,
loading
};
};
export default connect(mapStateToProps, {
onClear,
onSearchTextChanged,
searchForProfiles
})(SearchWorld);
static navigationOptions = ({navigation}) => {
return {
headerTitle: () => <HeaderTitle />,
headerRight: () => (<Button iconLeft transparent small onPress = {navigation.getParam('onPressSyncButton')}>
<Icon style ={{color:'white', fontWeight:'bold'}} name='md-save' size = {32} />
<Text style ={{color:'white', fontWeight:'bold'}}>save</Text>
</Button>),
headerTintColor:'black',
headerStyle: {
backgroundColor: '#6200EE'
},
}
};
this.props.navigation.setParams({ onPressSyncButton: this.updateUserProfile });