How to pass function between multiple components in React Native - react-native

I have problem and I didnt find answer, so I try anyone who could help with:
I have main app component:
export default class App extends React.Component {
constructor(props) {...}
openSettings = () => { ....some changing state}
render(){
return (...
<Settings openSettings={() =>this.openSettings()}/>
);}
...};
My Settings file :
const Settings = ({openSetting}) => {
return (
<MyHeader openSetting={openSetting}></MyHeader>...
)}
export default Settings;
And I want to pass openSetting to another file:
const MyHeader = ({openSetting}) => {
return (
<Text onPress={openSetting}>Open it!</Text>
)}
export default MyHeader ;
Anyone knows why it doesnt work?

Its probably the typo on your code thats causing this issue, and its not working.
You created a function named openSettings, but you sent the props as openSetting. Try writing the prop name correctly without typo and it should work.
export default class App extends React.Component {
constructor(props) {...}
openSettings = () => { ....some changing state}
render(){
return (...
<Settings openSettings={() =>this.openSettings()}/> //<== Check this line
);}
...};
const Settings = ({openSettings}) => { //<== Check this line
return (
<MyHeader openSettings={openSettings}></MyHeader>... //<== Check this line
)}
export default Settings;
const MyHeader = ({openSettings}) => { //<== Check this line
return (
<Text onPress={openSettings}>Open it!</Text> //<== Check this line
)}
export default MyHeader ;

Related

How to open/close ExpandableCalendar of react-native-calendars programmatically?

I want to open/close ExpandableCalendar programmatically. I found there's only an initialPosition prop. It's not working when changing initialPosition. So I tried to use ref and call setPosition() function of ExpandableCalendar
export const MyScreen = () => {
const ref = useRef<ExpandableCalendar>()
return (
<CalendarProvider date={new Date()}>
<ExpandableCalendar ref={ref} />
</CalendarProvider>
)
}
when I use this code, the ref.current.contentRef is always undefined
My Question is
How to get the ref of ExpandableCalendar?
It seems the asCalendarConsumer saves the ref as contentRef:
function asCalendarConsumer(
WrappedComponent: React.ComponentType<any>
): React.ComponentClass {
class CalendarConsumer extends Component {
contentRef: any;
saveRef = (r: Ref<React.Component<any>>) => {
this.contentRef = r;
};
render() {
return (
<CalendarContext.Consumer>
{(context) => (
<WrappedComponent
ref={this.contentRef}
context={context}
{...this.props}
/>
)}
</CalendarContext.Consumer>
);
}
}
hoistNonReactStatic(CalendarConsumer, WrappedComponent);
return CalendarConsumer;
}
export default asCalendarConsumer;
/* ExpandableCalendar */
export default asCalendarConsumer(ExpandableCalendar);
Is there a way to open ExpandableCalendar programmatically? Does the setPosition() function work or there's another way to do this.
My Environment is
react-native-calendars#1.1266.0:
react-native#0.64.2:

How to access value calculated in `useEffect` hook from renderer

I am developing a React-Native project with functional component.
Here is a very simple screen which renders a calculated result list. Since I need to calculation to be called only once so I put it inside the useEffect hook.
import {doCalculation} from '../util/helper'
const MyScreen = ({navigation}) => {
useEffect(() => {
// I call a function from a helper module here.
// The result is a list of object.
const result = doCalculation();
// eslint-disable-next-line
}, []);
// renderer
return (
<View>
// Problem is 'result' is not accessible here, but I need to render it here
{result.map(item=> <Text key={item.id}> {item.value} </Text>)}
</View>
)
}
export default MyScreen;
As you can see I have called the doCalculation() to get the result inside useEffect hook. My question is how can I render the result in the return part? Since the result is calculated inside the hook, it is not accessible in the renderer.
P.S. Moving the const result = doCalculation() outside the useEffect hook is not an option since I need the calculation to be called only once.
Below is an example. According to the above comments it looks like you want it to be called once on component mount. All you really need to do is add a useState
import {doCalculation} from '../util/helper'
const MyScreen = ({navigation}) => {
const [calculatedData, setCalculatedData] = useState([])
useEffect(() => {
// I call a function from a helper module here.
// The result is a list of object.
const result = doCalculation();
setCalculatedData(result)
// eslint-disable-next-line
}, []);
// renderer
return (
<View>
// Problem is 'result' is not accessible here, but I need to render it here
{calculatedData.map(item=> <Text key={item.id}> {item.value} </Text>)}
</View>
)
}
export default MyScreen;
const [calculatedData, setCalculatedData] = useState([])
useState is a hook used to store variable state. When calling setCalculatedData inside the useEffect with empty dependency array it will act similar to a componentDidMount() and run only on first mount. If you add variables to the dependency array it will re-run every-time one of those dep. change.
You can change the data inside the calculatedData at anytime by calling setCalculatedData with input data to change to.
Make use of useState to save the calculation result and then use the variable inside return. See https://reactjs.org/docs/hooks-state.html.
Code snippet:
import {doCalculation} from '../util/helper'
const MyScreen = ({navigation}) => {
const [result, setResult] = useState([]);
useEffect(() => {
// I call a function from a helper module here.
// The result is a list of object.
const tempRes = doCalculation();
setResult(tempRes);
// eslint-disable-next-line
}, []);
// renderer
return (
<View>
// Problem is 'result' is not accessible here, but I need to render it here
{result.map(item=> <Text key={item.id}> {item.value} </Text>)}
</View>
)
}
export default MyScreen;
Is async function?
if the function is not async (not wating for respond like from api) - you don't need useEffect.
import React from 'react';
import { Text, View } from 'react-native';
import {doCalculation} from '../util/helper'
const results = doCalculation();
const MyScreen = () => {
return (
<View>
{results.map(item=> <Text key={item.id}> {item.value} </Text>)}
</View>
)
}
export default MyScreen;
else you should wait until the results come from the server..
import React, { useState, useEffect } from 'react';
import { Text, View } from 'react-native';
import { doCalculation } from '../util/helper';
const MyScreen = () => {
const [results, setResults] = useState(null) // or empty array
useEffect(() => {
(async () => {
setResults(await doCalculation());
})();
}, []);
return (
<View>
{results?.map(item => <Text key={item.id}> {item.value} </Text>) || "Loading..."}
</View>
)
}
export default MyScreen;
and I can use more readable code:
if (!results) {
return <View>Loading...</View>
}
return (
<View>
{results.map(item => <Text key={item.id}> {item.value} </Text>)}
</View>
)
the async function can be like:
const doCalculation = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ id: 1, value: 1 }]);
}, 2000);
});
};

Unstated store based React Navigation causing warning

I'm using react-navigation and Unstated in my react native project.
I have a situation where I would like use:
this.props.navigation.navigate("App")
after successfully signing in.
Problem is I don't want it done directly from a function assigned to a submit button. I want to navigate based upon a global Unstated store.
However, it means that I would need to use a conditional INSIDE of the Subscribe wrapper. That is what leads to the dreaded Warning: Cannot update during an existing state transition (such as within 'render').
render() {
const { username, password } = this.state;
return (
<Subscribe to={[MainStore]}>
{({ auth: { state, testLogin } }) => {
if (state.isAuthenticated) {
this.props.navigation.navigate("App");
return null;
}
console.log("rendering AuthScreen");
return (
<View style={styles.container}>
<TextInput
label="Username"
onChangeText={this.setUsername}
value={username}
style={styles.input}
/>
<TextInput
label="Password"
onChangeText={this.setPassword}
value={password}
style={styles.input}
/>
{state.error && (
<Text style={styles.error}>{state.error.message}</Text>
)}
<Button
onPress={() => testLogin({ username, password })}
color="#000"
style={styles.button}
>
Sign in!
</Button>
</View>
);
}}
</Subscribe>
);
It works. But what's the correct way to do it?
I don't have access to MainStore outside of Subscribe and therefore outside of render.
I'm not sure about the react-navigation patterns but you could use a wrapper around this component which subscribes to 'MainStore' and pass it down to this component as a prop. That way you'll have access to 'MainStore' outside the render method.
I have since found a better solution.
I created an HOC that I call now on any Component, functional or not, that requires access to the store. That give me access to the store's state and functions all in props. This means, I am free to use the component as it was intended, hooks and all.
Here's what it looks like:
WithUnstated.js
import React, { PureComponent } from "react";
import { Subscribe } from "unstated";
import MainStore from "../store/Main";
const withUnstated = (
WrappedComponent,
Stores = [MainStore],
navigationOptions
) =>
class extends PureComponent {
static navigationOptions = navigationOptions;
render() {
return (
<Subscribe to={Stores}>
{(...stores) => {
const allStores = stores.reduce(
// { ...v } to force the WrappedComponent to rerender
(acc, v) => ({ ...acc, [v.displayName]: { ...v } }),
{}
);
return <WrappedComponent {...allStores} {...this.props} />;
}}
</Subscribe>
);
}
};
export default withUnstated;
Used like so in this Header example:
import React from "react";
import { Text, View } from "react-native";
import styles from "./styles";
import { states } from "../../services/data";
import withUnstated from "../../components/WithUnstated";
import MainStore from "../../store/Main";
const Header = ({
MainStore: {
state: { vehicle }
}
}) => (
<View style={styles.plateInfo}>
<Text style={styles.plateTop}>{vehicle.plate}</Text>
<Text style={styles.plateBottom}>{states[vehicle.state]}</Text>
</View>
);
export default withUnstated(Header, [MainStore]);
So now you don't need to create a million wrapper components for all the times you need your store available outside of your render function.
As, as an added goodie, the HOC accepts an array of stores making it completely plug and play. AND - it works with your navigationOptions!
Just remember to add displayName to your stores (ES-Lint prompts you to anyway).
This is what a simple store looks like:
import { Container } from "unstated";
class NotificationStore extends Container {
state = {
notifications: [],
showNotifications: false
};
displayName = "NotificationStore";
setState = payload => {
console.log("notification store payload: ", payload);
super.setState(payload);
};
setStateProps = payload => this.setState(payload);
}
export default NotificationStore;

How can I call setState from a different class that is in the same file but different class?

I call a function that is in my Homepage class from my ProfileScreen class that is in the same .js file. I successfully did that, but in that function a setState is called, and when the function is called from the other class, the state doesn't change. How can I get this.state.user in HomePage to change from calling the onPressLogout function in the ProfileScreen class?
export default class HomePage extends Component<Props> {
state = {
email:'',
password:'',
firstname:'',
lastname:'',
user:true,
error: '',
}
onPressLogout(){
firebase = require('firebase');
firebase.auth().signOut()
.then(() => this.setState({
user:false
}))
.catch(() => this.setState({
error: 'Logout Failure',
}))
}
render(){
return <AppContainer>
</AppContainer>;
}
}
class ProfileScreen extends React.Component {
constructor(props) {
super(props);
Obj = new HomePage();
}
render() {
return (
...
<TouchableOpacity style={styles.button} onPress =
{()=>Obj.onPressLogout()}>
</TouchableOpacity>
...
}
}
const TabNavigator = createBottomTabNavigator({
Profile: ProfileScreen,
});
const AppContainer = createAppContainer(TabNavigator);
I get this warning when I run the code and the this.state.user doesn't change:
Warning: Can't call "setState" on a component that is not yet mentioned.
You should pass the function of the parent element into the child element as a prop. Then, you can call it in the child to manipulate the state of the parent class.
Here is an example,
class ChangeButton extends React.Component{
render(){
return (
<Button title="Change" onPress={this.props.updateMainState}/>
)
}
}
export default class App extends React.Component {
state = {
name: 'Fatih'
}
changeName = ()=> {
this.setState({
name: 'Faruk'
})
}
render() {
return (
<View style={styles.container}>
<Text>
{this.state.name}
</Text>
<ChangeButton updateMainState={this.changeName}/>
</View>
);
}
}
In the code above, we passed changeName function into the ChangeButton element. The Button in ChangeButton calls the function of the parent element when you press it, which manipulates the state of the main class.
Here is the working code: ProjectLink

React Native Redux store dispatches reducers correctly, but doesn't update UI component

Working on a cancer treatment app in react native:
Current functionality: when I move the sliders and change the date on my app, it dispatches changes to the redux store successfully. Unfortunately, my UI doesn't update, even though I am calling the same store from the presentational components that I called for dispatch.
That results in this:
GIF of redux store changing while UI is static
Printing via
store.getState();
store.subscribe(() =>
console.log(store.getState())
);
I tried using subscription, but it seems like this isn't the right way to go about this. Thoughts?
snippets from my code (all in one small file, linked below)
Action
//action
function set_num_treatments(num) {
return {
type: SET_NUM_TREATMENTS,
num: num
}
}
setting the title
SET_NUM_TREATMENTS = "SET_NUM_TREATMENTS"
main reducer
function main_reducer(state = initialState, action) {
switch (action.type) {
case SET_PAGE_VIEW:
return Object.assign({}, state, {
current_page: action.page_of_interest
})
case SET_NUM_TREATMENTS:
return Object.assign({}, state, {
num_treatments: action.num
})
case SET_INTER_TREATMENT_INTERVAL:
return Object.assign({}, state, {
inter_treatment_interval: action.weeks_between_treatments
})
case SET_TREATMENT_START_DATE:
return Object.assign({}, state, {
treatment_start_date: action.date
})
default:
return state
}
return state
}
Here's where I start the store & produce the printing functionality
let store = createStore(main_reducer);
store.getState();
store.subscribe(() =>
console.log(store.getState())
);
here's the presentational components
class TreatmentSettings extends React.Component {
constructor(props){
super(props)
}
render() {
const props = this.props
const {store} = props
const state = store.getState()
return(
<View style={styles.treatment_option_slider_card}>
<Text style={styles.my_font, styles.tx_settings_header}>{state.num_treatments} Treatments</Text>
<Slider step={1} minimumValue={1} maximumValue={20} value={12}
onValueChange={(num_treatments) => {store.dispatch(set_num_treatments(num_treatments))}} />
<Text style={styles.my_font, styles.tx_settings_header}>X Weeks Between Treatments</Text>
<Slider step={1} minimumValue={1} maximumValue={4} value={2} style={{marginBottom:60}}
onValueChange={(value) => {store.dispatch(set_inter_treatment_interval(value))}}
/>
</View>
)
}
}
These final two components hold the main containers for the app
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Provider store={createStore(main_reducer)}>
<AppContainer />
</Provider>
);
}
}
class AppContainer extends React.Component {
constructor(props){
super(props)
}
render(){
return(
<View style={styles.container}>
<TreatmentSettings store={store} />
<Text>footertext</Text>
</View>
)
}
}
the one gist file is here if you want to see it all: https://github.com/briancohn/learning-redux/blob/navigation_addn/App.js
I really appreciate the help—
Thanks in advance!
-Brian
I think the way you are updating the store is fine but there’s something wrong with how your components are listening to the changes.
It seems you meant to use connect from react-redux for the containers to connect to the store. Then you can use mapStateToProps to get the data from the store to pass into the components as props. Check https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options for example.