state inside each flatlist component not being remembered - reactjs

I have this flatlist which lists all the jobs,uponclick we are taken to a Job details page where you can apply to the job. On apply, the button name changes to "applied" and it is disabled. I got this part working,. however when I go back to homepage and click on the same job again, the button is clickable and still says "apply now", instead of "applied". Does this have anything to do with flatlist and navigation?
Home.js:
export default function Home(params) {
const{ navigation}=params;
...
return(
<FlatList
data={getCompanies(companies, filter)}
renderItem={({ item }) => {
return(
<TouchableOpacity onPress={()=>{ navigation.navigate('Job Details',item)}}>
<View style={styles.listingCard}>
<View style={styles.listingCardBody}>
<Text style={styles.listingCardTitle}>{item.title}</Text>
<Text style={styles.listingCardDescription}>
Here is a really simple description from {item.title} about the
role being advertized...
</Text>
</View>
</View>
</TouchableOpacity>
);
}}
keyExtractor={(item, index) => { return item.id; }}
contentContainerStyle={styles.content}
/>
);}
Job Details:
function Jobdetails({route, navigation}){
const{ title, Category, rateHourly, responsibilities,location, postedBy,id,jobOverview, jobType, startDate, endDate, latitude, longitude}= route.params;
const [ butText, setButText] = useState("Apply Now");
const [ clickable, setClickable ]= useState(false);
<TouchableOpacity disabled={ clickable } style={page.button} onPress={()=>{
//apply() works fine
apply(id).then(async (result)=>{
if(result.succeeded()){
setButText('Application Successful!');
setClickable(true);
}
});
}}>
<Text style={page.text}>{ butText }</Text>
</TouchableOpacity>
Why isn't 'clickable' value being remembered by the app?

Following up on my comment,
Create a CLickableContext.js with
import { createContext } from "react";
export const CLickableContext= createContext(null);
On a component higher up in your app, set up the provider with the usestate hook.
import { CLickableContext} from "../CLickableContext";
...
const [clickable, setClickable] = useState(false);
...
<CLickableContext.Provider value={{ clickable, setClickable }}>
<YourApp/>
</CLickableContext.Provider>
Then include the context in any compenent throughout the app
import { useContext } from "react";
import { CLickableContext} from "../CLickableContext";
//Home.js
export default function Home(params) {
const { clickable, setClickable } = useContext(CLickableContext);
const{ navigation}=params;
...
return(
<FlatList
data={getCompanies(companies, filter)}
renderItem={({ item }) => {
return(
<TouchableOpacity onPress={()=>{ navigation.navigate('Job Details',item)}}>
<View style={styles.listingCard}>
<View style={styles.listingCardBody}>
<Text style={styles.listingCardTitle}>{item.title}</Text>
<Text style={styles.listingCardDescription}>
Here is a really simple description from {item.title} about the
role being advertized...
</Text>
</View>
</View>
</TouchableOpacity>
);
}}
keyExtractor={(item, index) => { return item.id; }}
contentContainerStyle={styles.content}
/>
);}
And
//Jobdetails.js
import { useContext } from "react";
import { CLickableContext} from "../CLickableContext";
function Jobdetails({route, navigation}){
const { clickable, setClickable } = useContext(CLickableContext);
const{ title, Category, rateHourly, responsibilities,location, postedBy,id,jobOverview, jobType, startDate, endDate, latitude, longitude}= route.params;
const [ butText, setButText] = useState("Apply Now");
<TouchableOpacity disabled={ clickable } style={page.button} onPress={()=>{
//apply() works fine
apply(id).then(async (result)=>{
if(result.succeeded()){
setButText('Application Successful!');
setClickable(true);
}
});
}}>
<Text style={page.text}>{ butText }</Text>
</TouchableOpacity>

Related

How can I maintain the data of a child component? ReactNative

I am starting in React Native and I would like to know how I can maintain the data that is in the state of a child component.
I am making an application that generates a certain amount of tables. Inside these tables, I have a menu, and inside this menu, I have components that are in charge of the logic to be able to make the sum of the dishes that were served, and how much is the whole. But when I return to the page where my tables are, all the results of my accounts that were in each table, were deleted.
This is the component that is responsible for creating each table
export default function Cuentas() {
const { mesa, handleClick } = useContext(MesaContext);
return (
<View style={style.container}>
<View style={style.bottonContainer}>
<TouchableOpacity style={style.FAB} onPress={handleClick}>
<Text style={style.FabText}> Nueva mesa</Text>
</TouchableOpacity>
</View>
<FlatList
data={mesa}
keyExtractor={(mesa) => String(mesa.id)}
renderItem={({ item }) => <Mesa mesas={item} />}
horizontal={false}
style={style.FlatList}
showsVerticalScrollIndicator={false}
/>
</View>
);
}
This is the Component that renders a component that is in charge of rendering all the decks
export default function CuentaMesa(props) {
const {
route: { params },
navigation,
} = props;
const { mesa } = useContext(MesaContext);
return (
<View style={style.container}>
<Text style={style.TitleMesa}>{params.name}</Text>
<Platos />
</View>
);
}
This is where all the dishes are rendered
export default function Platos() {
const [platos, setPlatos] = useState(platosService);
const [total, setTotal] = useState(null);
return (
<View style={style.container}>
<View style={style.Text}>
{platos.map(plato => {
return (
<View key={plato.id} style={style.platosContainer}>
<View style={style.platosView}>
<Text style={style.platoTitle}>{plato.name}</Text>
<Text style={style.platoPrice}>${plato.price}</Text>
</View>
<View>
<Plato price={plato.price} />
</View>
</View>
);
})}
</View>
</View>
);
This is where the inputs are, which are used to do all the logic, I want the data that is here (The result of each operation that was done with the input) to be saved, so that when leaving the array, where are they the tables and when you go back in, that information is there
export default function Plato(props) {
const [input, setInput] = useState(null)
const [suma, setSuma] = useState(null)
const { price } = props
console.log(price);
console.log(input)
const sumaPrecios = () => {
setSuma(Number(input) * price)
}
return (
<View>
<Text>{price}</Text>
<Text>Suma:{suma}</Text>
<TextInput placeholder='Cantidad' style={style.input} value={input} onChangeText={setInput} />
<Button title='Total' onPress={sumaPrecios} />
</View>
)
}
Thank you very much in advance for your help.

Calling function of class based component from another component in react native

I have two components one is Messages and other component is Navigation it's a stack navigator component. I would like to call function named onRefresh of Messages component from component Navigation header buttons. Please see my code how can I achieve this.
Messages.js (component file)
export default class Messages extends Component {
// Constructor
constructor(props) {
super(props)
this.state = {
messages: [],
isLoading: true
};
}
// End Constructor
// Getting Messages from server
async getMessages() {
try {
const response = await fetch('https://reactnative.dev/movies.json');
const json = await response.json();
this.setState({ messages: json.movies });
} catch (error) {
console.log(error);
} finally {
this.setState({ isLoading: false });
}
}
// End Getting messages from server
componentDidMount() {
this.getMessages();
}
// On refresh the messages
onRefresh = async () => {
this.setState({isLoading: true,}, () => {this.getMessages();});
}
// Send now sms using button.
sendNowMessage = async (title) => {
Alert.alert(title, "asdasdas");
}
render() {
const { messages, isLoading } = this.state;
return (
<SafeAreaView style={styles.container}>
{isLoading ? <ActivityIndicator size="large" color="#0000ff" style={ styles.horizontal } /> : (
<FlatList
data={ messages }
keyExtractor={({ id }, index) => id}
onRefresh={() => this.onRefresh()}
refreshing={this.state.isLoading}
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={{ marginBottom: 12, }}>{item.title}, {item.releaseYear}</Text>
<Button
onPress={() => this.sendNowMessage(item.title)}
title="Send SMS"
style={styles.sendSMS}
color="#841584"
accessibilityLabel="Learn more about this purple button"
/>
</View>
)}
/>
)}
</SafeAreaView>
);
}
}
Navigation.js (component file)
const Stack = createStackNavigator();
export default function Navigation() {
return (
<Stack.Navigator>
<Stack.Screen
name="AuthScreen"
component={AuthScreen}
options={{
title: "Welcome",
}}
/>
<Stack.Screen
name="Messages"
component={Messages}
options={{
headerTitle: 'Messages',
headerRight: () => {
return (
<View style={styles.headerButtons}>
<View style={{ marginRight:10, }}>
<TouchableOpacity
onPress={() => {
new Messages().onRefresh() <---- I am calling it like this.
}}
>
<Text
style={styles.actionButtons}
>
<Ionicons name="reload-sharp" size={20} color="black" />
</Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity
onPress={() => {
alert("working")
}}
>
<Text
style={styles.actionButtons}
>
<Ionicons name="filter" size={20} color="black" />
</Text>
</TouchableOpacity>
</View>
</View>
)
}
}}
/>
</Stack.Navigator>
);
}
I am calling onRefresh function like this new Messages().onRefresh() and getting the following error.
Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the Messages component.
I want to learn what is the best way to call onRefresh function.

cannot navigate to same route with different params

Is it possible in react-native to navigate through the same route with different params? When I tried it, it keeps getting me an error saying that the params are undefined. Here is my code:
function Home({ route, navigation }){
const { newText } = route.params;
return (
<View style={styles.container}>
<Text style={{fontSize:30, paddingVertical:150, fontWeight:'bold'}}>Current Balance: {JSON.stringify(newText)} PHP</Text>
<TouchableOpacity style={styles.btnLogin1} onPress={()=>navigation.navigate("AddBalance")}>
<Text style={styles.btnLogin}>Add Balance</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btnLogin1} onPress={()=>navigation.navigate("ViewCode")}>
<Text style={styles.btnLogin}>View Code To Pay</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btnLogin1} onPress={()=>navigation.navigate("Login")}>
<Text style={styles.btnLogin}>Logout</Text>
</TouchableOpacity>
</View>
);
}
function AddBalance({ navigation }){
const[balance, newBalance] = useState(0)
const[amount, setAmount] = useState()
function addTogether(){
const Total = balance + amount;
newBalance(Total);
MyFunction();
}
return (
<View style={styles.container}>
<Text style={{fontSize:30, paddingVertical:20, fontWeight:'bold'}}>Add Balance</Text>
<TextInput style={styles.inputBox} placeholder="Enter Amount" keyboardType={'numeric'}
onChangeText={(text) => setAmount(Number.parseInt(text))}></TextInput>
<TouchableOpacity style={styles.btnLogin1} onPress ={addTogether}>
<Text style={styles.btnLogin}>Continue</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btnLogin1} onPress={()=>navigation.navigate("Home", {newText:
balance})}>
<Text style={styles.btnLogin}>Back</Text>
</TouchableOpacity>
</View>
);
}
function ViewCode({ navigation }){
return (
<View style={styles.container}>
<Text style={{fontSize:30, fontWeight:'bold'}}>Your Code is:</Text>
<Text style={{fontSize:20, textAlign:'center'}}>R3fGH7X95iW</Text>
<TouchableOpacity style={styles.btnLogin1} onPress={()=>navigation.navigate("Home")}>
<Text style={styles.btnLogin}>Back</Text>
</TouchableOpacity>
</View>
);
}
So the error goes when I navigate to ViewCode to pay and click the back button. How can I solve this? I also created a duplicate screen for a home to manage to go back to the home screen but when I tried to navigate to the duplicate home screen, the current balance became 0 again. What I want to do is whenever the user go back to home screen the current balance will base upon the balance the users added.
The problem is in Home, you assume route.params always there (codes below)
const { newText } = route.params;
but, when you press the back button in ViewCode, it will call navigate.navigation("Home"); without any params. (codes below)
<TouchableOpacity style={styles.btnLogin1} onPress={()=>navigation.navigate("Home")}>
<Text style={styles.btnLogin}>Back</Text>
</TouchableOpacity>
This will lead to the error that you got.
The solution is to make the param optional. You can replace this code:
const { newText } = route.params;
to
const { newText } = route.params || {newText: ""};
You can replace "" with any other text as the default newText.
For saving the value globally, I recommend using Context from React. You can read more about it here
Here is the example of a Provider component you can use:
const ExampleContext = React.createContext({
setBalance: null,
balance: "",
});
const ExampleProvider = (props) => {
const [balance, setBalance] = useState("");
return (
<ExampleContext.Provider
value={{
balance: balance,
setBalance: setBalance,
}}
>
{props.children}
</ExampleContext.Provider>
);
};
and wrap your navigator with ExampleProvider
then call setBalance from the new context like this:
function AddBalance({ navigation }){
const balanceContext = React.useContext(ExampleContext);
const[amount, setAmount] = useState()
function addTogether(){
const Total = balance + amount;
balanceContext.setBalance(Total);
MyFunction();
}
and get balance from context instead from the route.params
function Home({ route, navigation }){
const balanceContext = React.useContext(ExampleContext);
const newText = balanceContext.balance;

How to close modal when navigating back to my screen?

So I have a homescreen that renders a modal component. I use some states for modal visibility. However, the problem is that when I navigate from the modal to another screen and then back to my home screen, the modal is still opened and I cannot figure out how to close it. I tried using useEffect but it does not do anything. Any tips?
This is home screen.
The AddButton component is a simple TouchableOpacity that onPress call the toggleModal function
const [isModalVisible, setIsModalVisible] = useState(false);
const toggleModal = () => {
setIsModalVisible(!isModalVisible);
};
useEffect(() => {
setIsModalVisible(false)
}, [navigation])
return (
<AddButton
title="ADD BOOK"
toggleModal={toggleModal}
/>
)
And the Modal Component is this:
const ModalComponent = ({navigation, isModalVisible, toggleModal, title, author, save, onNavigate }) => {
return (
<Modal isVisible={isModalVisible}>
<View style={styles.container}>
<Text>Modal</Text>
<View style={styles.footer}>
<TouchableOpacity onPress={toggleModal}>
<Text>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.navigate("AddBookScreen")}>
<Text>{save}</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
);
};
Modify this code
<TouchableOpacity onPress={() => navigation.navigate("AddBookScreen")}>
<Text>{save}</Text>
</TouchableOpacity>
to
<TouchableOpacity onPress={() => {
navigation.navigate("AddBookScreen")
toggleModal();
}}>
<Text>{save}</Text>
</TouchableOpacity>
extract onPress function and move it up to your page and pass it as onSaveClick callback to the modal component.
// on your page
const onSaveClick = () => {
navigation.navigate("AddBookScreen")
setIsModalVisible(false)
}
then in the modal
<TouchableOpacity onPress={onSaveClick}>
<Text>{save}</Text>
</TouchableOpacity>

navigation.navigate('DrawerOpen') doesn't work from inside a HOC

Calling navigation.navigate('DrawerOpen') from inside a HOC doesn't open the drawer menu
I have written a higher order component which returns a screen with header for drawer navigator.
Header
const Header = (props)=>{
return (
<View style={{flex:1,backgroundColor:"red",justifyContent:"center"}}>
<TouchableWithoutFeedback onPress={() => {
props.navigation.navigate('DrawerOpen');
if (props.navigation.state.index === 0) {
props.navigation.navigate('DrawerOpen')
} else {
props.navigation.navigate('DrawerClose')
}
}}>
<View >
<Text style={{fontSize:20,color:"white"}}>{"Menu"}</Text>
</View>
</TouchableWithoutFeedback>
</View>
);
}
HOC
const createCompWithHeader = (Comp)=>{
return (props)=>{
return (
<View style={{flex:1,marginTop:20}}>
<Header {...props}/>
<View style={{flex:9}}>
<Comp {...props}/>
</View>
</View>
);
}
}
Drawer Navigator
const MyApp = DrawerNavigator({
Home: {
screen: createCompWithHeader(MyHomeScreen),
},
Notifications: {
screen: createCompWithHeader(MyNotificationsScreen),
},
});
Components(Screens)
class MyHomeScreen extends Component {
static navigationOptions = {
drawerLabel: 'Home'
};
render() {
return (
<View>
<Button
onPress={()=>this.props.navigation.navigate('DrawerOpen')}
title={"Open menu"}
/>
<Button
onPress={() => this.props.navigation.navigate('Notifications')}
title="Go to notifications"
/>
</View>
);
}
}
class MyNotificationsScreen extends Component {
static navigationOptions = {
drawerLabel: 'Notifications'
};
render() {
return (
<View>
<Button
onPress={()=>this.props.navigation.navigate('DrawerOpen')}
title={"Open Menu"}
/>
<Button
onPress={() => this.props.navigation.goBack()}
title="Go home"
/></View>
);
}
}
Current Behavior
when I click on the button in header(props.navigation.navigate('DrawerOpen')), the drawer menu doesn't open
When I click on the button inside the component, menu opens
Expected Behavior
On click of the button in the header, menu should open.
How to reproduce
Expo Url : Snack
Environment
| react-navigation |1.5.8
| react-native |0.55.2
After 2.0 release, rather than opening a drawer with navigation.navigate(‘DrawerOpen’), you can now call navigation.openDrawer().
Other methods are closeDrawer() and toggleDrawer().
I've checked your snack expo and the problem lies in this snippet
if (props.navigation.state.index === 0) {
props.navigation.navigate('DrawerOpen')
} else {
props.navigation.navigate('DrawerClose')
}
The navigation does not add an index to the state, therefore it's value remains undefined
So what you are doing is , Opening and Closing the drawer yourself
Therefore remove this snippet to see it work.
onPress={() => props.navigation.navigate('DrawerOpen')}

Resources