Is that good idea to locate Modalize component to height level node - react-native

I'm new to react-native. And I ran into one issue. Is it possible to use Modalize in the topmost node (before the navigator) as a context.
To call it like this.
useEffect(() => {
modal.open({
component: <Text>TEST modal</Text>,
})
}, [])
Main component
if (props.isOpened) {
return (
<Modalize
ref={ref}
modalHeight={390}
handleStyle={{ backgroundColor: "transparent" }}
>
{props.options.component}
</Modalize>
)
} else {
return <>{props.options.component}</>
}
Well, of course, this is possible, but is it right to do so?

Related

How to use props in React Native?

I am new to React Native. I am wondering how to use props to work my code? Here is my code.
const weatherConditions = {
Rain: {
color: '#005BEA',
},
Clear: {
color: '#f7b733',
}
};
const Weather = ({ weather }) => {
return (
<View
style={[
styles.weatherContainer,
{ backgroundColor: weatherConditions.weather.color }
]}/>
);
};
But it does not work. Only the code below works. How to fix this? Help me.
const Weather = ({ weather }) => {
return (
<View
style={[
styles.weatherContainer,
{ backgroundColor: weatherConditions.Rain.color } // or { backgroundColor: weatherConditions.Clear.color }
]}
>
);
};
Your problem is that you are telling your component to look for weatherConditions.weather.color but the key weather is being interpreted literally. The component is looking for the weather key inside weatherConditions and it won't find it.
What you need to do is to do is:
backgroundColor: weatherConditions[weather].color
The brackets ensure weather is interpreted as a variable and not the word weather.
You pass the weather conditions via the prop weather and not weatherConditions. Can you try it with weather.Rain.color? It should work with that.

How to restart app (react native and expo)

I use expo so I've no access to android folder.
I want to restart my app for first time. How can I do that?
I use react-native-restart, but not wroking and I have an error now:
null is not an object (evaluating 'x.default.restart;)
Codes:
componentDidMount() {
if (I18nManager.isRTL) {
I18nManager.forceRTL(false);
RNRestart.Restart();
}
}
How Can I restart my app?
I've had the same problem for over a month, nothing helped me, so I developed a library to accomplish this, simple install it using:
npm i fiction-expo-restart
and import it like:
import {Restart} from 'fiction-expo-restart';
and then when you want to perform a restart, use:
Restart();
Note in case this answer gets old, you can check the library here: https://www.npmjs.com/package/fiction-expo-restart
I have faced the same issue and found this solution somewhere.
You can try to use Updates from expo like this:
import { Updates } from 'expo';
Updates.reload();
import { StatusBar } from "expo-status-bar";
import React from "react";
import { Button, I18nManager, StyleSheet, Text, View } from "react-native";
import * as Updates from "expo-updates";
async function toggleRTL() {
await I18nManager.forceRTL(I18nManager.isRTL ? false : true);
await Updates.reloadAsync();
}
export default function App() {
return (
<View style={styles.container}>
<Text>{new Date().toString()}</Text>
<Text>{I18nManager.isRTL ? "RTL" : "LTR"}</Text>
<View style={{ marginVertical: 5 }} />
<Button title="Reload app" onPress={() => Updates.reloadAsync()} />
<View style={{ marginVertical: 5 }} />
<Button title="Toggle RTL" onPress={() => toggleRTL()} />
<StatusBar style="auto" />
</View>
);
}
https://github.com/brentvatne/updates-reload/blob/master/App.js
It's the only working way for me. When i try automatically reload app in useEffect - it crashes, so i make a separate screen where i ask user to press button to reload app
For Expo SDK 45+ please use
import * as Updates from "expo-updates"
Updates.reloadAsync()
The module fiction-expo-restart is not maintained anymore.
If you are using react-native-code-push library, you can restart with this;
import CodePush from 'react-native-code-push';
CodePush.restartApp();
What I did was to build a Restart component that is not a const but a var. And an applyReload() function that sets that var to an empty component <></> if the reload bool state is true, triggering the re-render.
The re-render will reinstate the Restart var back to its original structure, but a new instance is then created, effectively reloading everything that is inside the <Restart> tag:
My App.tsx:
export default function App() {
const [reload, setReload] = useState(false);
type Props = { children: ReactNode };
var Restart = ({ children }: Props) => {
return <>{children}</>;
};
const applyReload = () => {
if (reload) {
Restart = ({ children }: Props) => {
return <></>;
};
setReload(false);
}
};
useEffect(applyReload);
useEffect(() => {
// put some code here to modify your app..
// test reload after 6 seconds
setTimeout(() => {
setReload(true);
}, 6000);
}, []);
return (
<SafeAreaProvider>
<SafeAreaView style={{ flex: 1 }}>
<PaperProvider theme={appTheme}>
<NavigationContainer theme={appTheme} documentTitle={{ enabled: false }}>
<AppContext.Provider value={appContext}>
<Restart>
<MyMainAppComponent />
</Restart>
</AppContext.Provider>
</NavigationContainer>
</PaperProvider>
</SafeAreaView>
</SafeAreaProvider>
);
I also added the 'setReload' state function to my '<AppContext.Provider>' so anywhere down my App it is possible to trigger the App reload.

How to update the FlatList immediately after state changed?

I am working with react native.
I have component listing by using
And, when the state to give data to update the list change. It won't update immediately. It take few seconds to re-render.
so, how can I update the component immeidately
//Listcomponent
const ListGlossary = ({glossaries, onPressGlossary, navigation, searchField}) => {
return (
<FlatList
data={glossaries}
keyExtractor={(item) => item.key}
renderItem={({item}) =>
<TouchableHighlight
onPress = {() => navigation.navigate('DetailGlossaryScreen', { searchField: searchField, word: item.word, translate: item.translate})}>
<ListItem
key={`${item.key}`}
title={`${item.word}`}
/>
</TouchableHighlight>
}
/>
}
//And you can find here the home screen component
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
glossaries: [],
searchField: '',
}
}
componentDidMount () {
Promise.resolve().then(() => {this.setState({glossaries: JSONDataFromFile, isLoading: false})})
}
onSearchChange = (inputText) => {
this.setState({searchField: inputText});
}
render(){
return(
let filteredWords = []
if(this.state.searchField != null) {
let searchField = this.state.searchField.toLowerCase(),
glossaries = this.state.glossaries;
for(let i = 0, l = glossaries.length; i < l; ++i) {
if(glossaries[i].word.toLowerCase().indexOf(searchField) === 0){
filteredWords.push(glossaries[i]);
}
}
}
{this.state.isLoading ?
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
:
<ListGlossary
navigation = {this.props.navigation}
glossaries = {filteredWords}
onPressGlossary={this.onPressGlossary}
searchField = {this.state.searchField}
/>
}
)
}
Please show the whole component, and give the length of the list.
--- Edit
I suspect you're doing too much work in the render function. You're filtering every time it gets called, and since you're passing in the navigation prop (I assume you're using React-Navigation), it'll get called frequently. If you're using a stack navigator, all the other screens are also getting re-rendered every time you navigate to a new screen. Avoid passing navigation as much as possible, or use a HOC composition to ignore it.
You probably don't need to be filtering glossaries every time the user changes the search value. Use the shouldComponentUpdate lifecycle method.

Redux & NavigatorIOS does not update state in child views in react-native?

How can I make redux update master and detail views contained inside a NavigatorIOS?
Both the Master and Detail views do not update:
MainView > TabBar > TabBarItem > NavigatorIOS > MasterMenu > Detail
Other TabBarItem's work great using redux. For example:
MainView > TabBar TabBarItem > ViewProfile
<Icon.TabBarItem
style={{borderColor:'red', borderWidth:0}}
{...this.props}
iconName="timeline"
title="View"
selected={this.state.selectedTab === 'viewTab'}
onPress={() => {
this.setState({
selectedTab: 'viewTab',
presses: this.state.presses + 1
});
}}>
<ViewProfile {...this.props} />
</Icon.TabBarItem>
This MainView > TabBar > TabBarItem > NavigatorIOS > MasterMenu > Detail does NOT update:
<Icon.TabBarItem
style={{borderColor:'red', borderWidth:0}}
title={'Menu'}
iconName="more-vert"
selected={this.state.selectedTab === 'moreTab'}
onPress={() => {
this.setState({
selectedTab: 'moreTab',
});
}}>
<View style={ss.container}>
<NavigatorIOS
ref="nav"
style={ss.container}
{...this.props}
initialRoute={{
title: 'Master Menu',
component: MasterMenu,
passProps: {
...this.props,
},
}}
/>
</Icon.TabBarItem>
</TabBarIOS>
This is how I connect and pass props to MainView that contains the NaigatorIOS > MasterMenu > Detail
File: mainApp.js
class MainApp extends Component {
constructor(props) {
super(props);
}
render() {
console.log('****** MainApp::render ****');
const { state, actions } = this.props;
return (
<MainView
{...this.props} />
);
}
}
// mapStateToProps
function mapStoreStateToComponentProps(state) {
return {
state: state.appState,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Object.assign({}, appActions), dispatch),
}
}
export default connect(
mapStoreStateToComponentProps,
mapDispatchToProps
)(MainApp)
I can drill down into a NavigatorIOS containing a MasterMenu->DetailMenu view, call an action in the detail view, the state changes and all other views in TabBarIOS update. However, the redux state in both the master and detail views contained inside the <NavigatorIOS> retain their original values.
Is passProps not the way to do it?
Or is there other listen or subscribe methods that a master and child view can use to update when data store state changes?
Known issue with NavigatorIOS
NavigatorIOS does not rerender a scene, when passProps changed.
Bypass by connecting redux at a lower point in the hierarchy
You could bypass this issue by connecting your component MasterMenu with redux, instead of MainView.
The advice of #purii solved my problem.
I created a wrapper for the "Master View" component loaded into NavigatorIOS component.
I also created wrappers for all "Child View" components that were drilled-down into from the "Master View" component. This seems ugly and wrong but it works. Using NavigatorIOS passProps seemed to break redux updates.
var React = require('react-native');
var {
Component,
} = React;
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux';
import * as appActions from '../actions/appActions';
var MasterListView = require('../../MasterListView');
class MasterListWrapper extends Component {
constructor(props) {
super(props);
}
render() {
console.log('****** PlanList::render ****');
return (
<MasterListView {...this.props} />
);
}
}
// mapStoreStateToComponentProps would be a more descriptive name for mapStateToProps
function mapStateToProps(state) {
return {
state: state.appState,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Object.assign({}, appActions), dispatch),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(MasterListWrapper)
And then I pass MasterListWrapper as the NavigatorIOS component:
<Icon.TabBarItem
style={{borderColor:'red', borderWidth:0}}
title="Plan"
iconName="list"
selected={this.state.selectedTab === 'planTab'}
onPress={() => {
this.setState({
selectedTab: 'planTab',
presses: this.state.presses + 1
});
}}>
<View style={ss.container}>
<NavigatorIOS
ref="nav"
style={ss.container}
initialRoute={{
title: 'Field Planner',
component: MasterListWrapper,
passProps: {
},
}}
/>
</View>
</Icon.TabBarItem>
In the MasterListView I needed to add componentWillReceiveProps to get my ListView to re-render when redux state changed.
componentWillReceiveProps: function(nextProps) {
this.setState({
// Force all rows to render
dataSource: ds.cloneWithRows(nextProps.state.waypointItemList),
});
},
This is likely not the best approach. I will be looking for better approaches.
However at this point in my learning curve of javascript and react-native, I am happy it worked.
Redux is pretty cool. I have multiple tabs. I can go to menu and change a settings, for example, change units from feet to meters, and all my views are re-rendered.

In React Native, how can I access methods of one component from another component?

I'm trying to access a method of a React Native component from a different component. It is passed through props. Unfortunately, it seems like the components aren't providing their methods publicly. How can I get access to the method?
Have a look at the following, you'll see InsideView has this.props.myModal, which is a ShowMyModal component. However, it doesn't have access to the .openModal() method.
'use strict';
var React = require('react-native');
var {
AppRegistry,
ActionSheetIOS,
StyleSheet,
Text,
View,
} = React;
var InsideView = React.createClass({
makeItOpen: function() {
debugger;
this.props.myModal.openModal();
},
render: function() {
return (
<View>
<Text onPress={() => this.makeItOpen()}>Click me!</Text>
</View>
);
}
});
var ShowMyModal = React.createClass({
getInitialState: function() {
return {
isModalOpen: false,
}
},
openModal() {
this.setState({isModalOpen: true});
},
closeModal() {
this.setState({isModalOpen: false});
},
render: function() {
return (
<Text>isModalOpen = {String(this.state.isModalOpen)}</Text>
);
}
});
var AwesomeProject = React.createClass({
getInitialState: function() {
return {
myModal: <ShowMyModal />,
}
},
render: function() {
return (
<View style={{padding: 30}}>
<InsideView myModal={this.state.myModal}/>
{this.state.myModal}
</View>
);
},
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
Something like this should work:
'use strict';
var React = require('react-native');
var {
AppRegistry,
ActionSheetIOS,
StyleSheet,
Text,
TouchableOpacity,
View,
} = React;
var InsideView = React.createClass({
render: function() {
return (
<View>
<TouchableOpacity onPress={() => this.props.openModal()}><Text>Open modal!</Text></TouchableOpacity>
<TouchableOpacity onPress={() => this.props.closeModal()}><Text>Close modal!</Text></TouchableOpacity>
</View>
);
}
});
var ShowMyModal = React.createClass({
render: function() {
return (
<Text>isModalOpen = {String(this.props.isVisible)}</Text>
);
}
});
var SampleApp = React.createClass({
getInitialState: function() {
return {
isModalOpen: false
}
},
_openModal: function() {
this.setState({
isModalOpen: true
});
},
_closeModal() {
this.setState({
isModalOpen: false
});
},
render: function() {
return (
<View style={{padding: 30}}>
<InsideView openModal={this._openModal} closeModal={this._closeModal}/>
<ShowMyModal isVisible={this.state.isModalOpen}/>
</View>
);
},
});
AppRegistry.registerComponent('SampleApp', () => SampleApp);
I don't think it's a good idea to store the components in state. State should really be used for component's data rather than sub-components. Dave's solution above is good approach but it could be done a bit better as it moves the state of modal to the application (which is not very good to separate concerns). It's good if modal can keep it's own state and know if it's visible or not. Then openModal() and closeModal() can do some extra stuff as needed (as opposed to somehow reacting to change in visibility of ShowModal). You can also avoid those extra _openModal and _closeModal which are boilerplate.
I think it's best to use refs. Refs is standard way to refer to other components. See here for more details about refs https://facebook.github.io/react/docs/more-about-refs.html You can use refs as strings and refer to the component by that strings but it's kind of ugly as introduces global names which contradict the component approach of react. But you can also use callbacks as refs to set your internal components as fields. There is a nice and simple example of this is react's documentation: http://facebook.github.io/react-native/docs/direct-manipulation.html#forward-setnativeprops-to-a-child. I copy it here in case the documentation gets updated:
var MyButton = React.createClass({
setNativeProps(nativeProps) {
this._root.setNativeProps(nativeProps);
},
render() {
return (
<View ref={component => this._root = component} {...this.props}>
<Text>{this.props.label}</Text>
</View>
)
},
});
What happens here - the view in question has callback ref which sets this._root as the view's backing component. Then in any other place in the component you can use this._root to refer to it.
So in your case it could look like below (note that you need those anonymous arrow functions rather than passing the openModal / closeModal methods because at the time of rendering _modal is not yet set, you can only refer to it later using the anonymous methods).
// ...
// InsideView render (same as in Dave's solution)
<View>
<TouchableOpacity onPress={() => this.props.openModal()}><Text>Open modal!</Text></TouchableOpacity>
<TouchableOpacity onPress={() => this.props.closeModal()}><Text>Close modal!</Text></TouchableOpacity>
</View>
// ...
// Sample App render ...
<View style={{padding: 30}}>
<InsideView openModal={ () => this._modal.openModal() } closeModal={ () => this._modal.closeModal() } />
<ShowMyModal ref={component => this._modal = component} />
</View>
Then your initial ShowModal implementation can stay as it is - with it's own state and own openModal and showModal functions.