Passing Props to Screens in React Native - reactjs

I have started to learn React Native and as always begin by creating reusable components. I learnt how you can pass and access props while creating custom components.
I want to create a base screen in React Native, which has common properties and all screens in my app can set, like a title for example.
Below I'm creating a new Screen for the home page of my app
class APCComponent extends Component {
constructor(props) {
super(props);
}
render() { //dummy function, will always get overridden in the child
return (
<View />
);
}
}
export default class Home extends APCComponent {
//I somehow want to pass a string to the parent APCComponent
//and want APCComponent use it to set the Header of the navigation
constructor() {
super({ title: 'Home' });
}
//Right now the following works, but in case I use a different type of Navigation,
//I'll have to change all components. By just setting a string, I'm allowing my base
//component to display the header
static navigationOptions = { title: "Home" }; //from react-navigtion's StackNavigator
render() {
return <Button title="Sample Button" />;
}
}
Thanks

class BaseComponent extends Component {
render() {
return (){
<Header title={this.props.title} />
}
}
}
class HomeScreen extends Component {
render() {
return (){
<BaseComponent title='this is your home screen' />
}
}
}
where Header component is also a separate reusable component.
you need to pass props(in our case 'title') from the upper level component to base level components like the above example.

In the constructor of HomeScreen, props should be able to passed to BaseComponent through
constructor(props) {
super(props);
...
does it work for you?

Related

Can a child component render its parent content only?

I'm extending a parent component that is part of an SDK (AWS Amplify - SignIn), which I have no control over. I only need to make a small change where the input field data will be modified to be lowercase before it's passed to the authentication function.
import React from "react";
import { SignIn } from "aws-amplify-react";
export class CustomSignIn extends SignIn {
constructor(props) {
super(props);
this._validAuthStates = ["signIn", "signedOut", "signedUp"];
}
showComponent(theme) {
return (
<div>My child component that I don't need to render</div>
);
}
}
export default CustomSignIn;
This is more of a general question not specifically related to AWS Amplify, but I'd like to use the existing UI / rendering code from the parent — is there a way to simply display the parent's rendering content and not have any child content?
You can extend a component and use that components render function.
class ComponentFromThirdParty extends React.Component {
render() {
return <div>This is ComponentFromThirdParty</div>;
}
}
class MyComponent extends ComponentFromThirdParty {
componentDidMount() {
console.log(
"MyComponent was mounted and I render the same as ComponentFromThirdParty"
);
}
render() {
return super.render();
}
}
MyComponent will render this when mounted.
This is ComponentFromThirdParty

React - Getting refs to wrapped class components

I have a map component that contains a child sidebar component. I am trying to do a relatively simple task of scrolling to the place in the list of places in the sidebar when it's map marker is clicked on. But, because the sidebar needs to be wrapped in withRouter and connect, I'm unable to set a ref (ref) => this.sidebar = ref in the map component.
export class Map extends React.Component {
...
handleClick() {
this.sidebar.scrollToPlace(place.id);
}
render () {
return (
<MapSidebar
// unable to set ref
/>
)
}
}
and
class MapSidebar extends React.Component {
...
scrollToPlace(id) {
this.refs[id].scrollIntoView({block: 'end', behavior: 'smooth'});
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MapSidebar));
I know that using wrappedComponentRef could get me the contents of withRouter, but then I still have connect to deal with.
I also tried creating a custom ref on the MapSidebar instance:
<MapSidebar
getReference={(ref) => {
this.sidebar = ref;
}} />
and then in the MapSidebar class constructor, calling:
if(this.props.getReference) {
this.props.getReference(this);
}
but that resulted in an infinite loop of that component updating (although I'm not sure I understand why).
Is there a better way to get around these issues?
I suggest you avoid refs and simply pass the scroll value down:
export class Map extends React.Component {
...
handleClick() {
this.setState({scrollToPlaceId: place.id});
}
render () {
return (
<MapSidebar
// Add a new property
scrollToPlace={this.state.scrollToPlaceId}
/>
)
}
}
Then in your sidebar component, just listen to scroll changes in componentWillReceiveProps for example
class MapSidebar extends React.Component {
...
componentWillReceiveProps(nextProps) {
if (nextProps.scrollToPlace !== this.props.scrollToPlace) {
this.refs[nextProps.scrollToPlace].scrollIntoView({block: 'end', behavior: 'smooth'});
}
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MapSidebar));
Store a reference in both classes:
// MapSidebar render - add this to the element you want.
<div ref={r => (this.ref = r)}>
Then in Map render:
<MapSidebar ref={r => (this.sidebar = r)}>
Now after Map has mounted you have access to the ref:
this.sidebar.ref

react.js props and state inside multilevel components

I have one app having component structure like below.
Component App Main parent which loads header in all components using {this.props.children}
Component Header
Component Home
Component Dashboard
Component Data
Component DataLoad
App contains Header in render passing some state variables.
Home contains Dashboard which has actions to update the state variables of App to update the Header.
Data contains DataLoad also from here i need to update the state variable of App to update the Header.
For example my App is like
import React from 'react';
import Header from './Header.jsx';
class App extends React.Component {
constructor(props) {
super();
this.state = {
show : '1',
}
this.showHide = this.showHide.bind(this);
}
showHideSearch() {
this.setState(prevState => ({
show: prevState.show == '1' ? '0' : '1',
}));
}
render() {
return (
<div>
<Header show={this.state.show}/>
{this.props.children}
</div>
)
}
}
export default App;
import React from 'react';
import Dashboard from './Dashboard.jsx'
class Home extends React.Component {
constructor(props) {
super();
this.showHide = this.showHide.bind(this);
}
showHide() {
tried this but not working
//this.props.showHideSearch();
}
render() {
return (
<div>// this props show not going to dashboard component
<Dashboard show={this.props.show} showHide= {this.showHide.bind(this)}/>
</div>
)
}
}
export default Home;
If I am understanding your question right, you are wanting to pass your this.showHideSearch function from your App component to its the component in this.props.children - your Home component.
This is easily accomplishable using:
React.cloneElement(this.props.children, {showHideSearch: this.showHideSearch});
Put that in place of where you have this.props.children.
Source - https://stackoverflow.com/a/35102287/6621973
Edit: To update a parent's state, simply pass down a function setting the state from parent to child. There are several examples if you Google "react change parent state", for example: How to update parent's state in React?

Get propery of other React component in a library

I'm writing a library full of ReactJS components, so Flux should not be used, since it's a library.
I have a component, a ThemeProvider.
import React from 'react';
class OfficeUIThemeProvider extends React.Component {
constructor(props) {
super(props);
}
render() {
return null;
}
}
OfficeUIThemeProvider.propTypes = {
theme: React.PropTypes.oneOf(['Office2016']).isRequired,
color: React.PropTypes.oneOf(['Light-Blue', 'Blue', 'Green', 'Orange', 'Purple', 'Red']).isRequired
};
export default OfficeUIThemeProvider;
I return null in the render() method since this component should not render anything.
Then I do have a simple component, a button.
import React from 'react';
class OfficeUIButton extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div className={"officeui-button"}>
<span className="{officeui-button-label}">{this.props.label}</span>
</div>
}
}
OfficeUIButton.propTypes = {
label: React.PropTypes.string.isRequired,
};
export default OfficeUIButton;
Now, I want the button to have specific classes, based on the values provided in the ThemeProvider.
A simple solution would be to render the OfficeUIButton component directly in my ThemeProvider render() method but this is not a valid solution since I'm developing a library and don't want to couple things.
An application using this library should work as:
ReactDOM.render(
<OfficeUIThemeProvider theme='Office2016' color='Light-Blue'>
<OfficeUIButton label="To..." />
</OfficeUIThemeProvider>,
document.getElementById('app')
);
But, this renders nothing since my ThemeProvider return null○ in it'srender` method.
How can this be accomplished?
Kind regards,
OfficeUIButton is child component of OfficeUIThemeProvider, so I suppose you should try:
class OfficeUIThemeProvider extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{this.props.children}
</div>
);
}
}

react-native update NavigatorIOS component's props

I have the following code in which HomeTab contains a NavigatorIOS. I'm passing HomeScreen as the component for the navigator.
I have some events happening on a parent component that contains HomeTab and would like to pass them to HomeScreen. I am able to get the to HomeTab. How can I set the props or state for the component and preferably, I'd like to be able to call a function on it. Any clue?
class HomeTab extends React.Component {
constructor() {
super()
this.state = {}
}
render() {
return (
<NavigatorIOS style={styles.container}
initialRoute={{
title: 'Home',
component: HomeScreen,
passProps: {}
}}
/>
)
}
}
You can simply pass them through that passProps object there.
I think you might be missing the other piece in your HomeScreen component.
var HomeScreen = React.createClass({
propTypes: {
text: React.PropTypes.string.isRequired,
},
Then in your root view you would do
class HomeTab extends React.Component {
constructor() {
super()
this.state = {}
}
render() {
return (
<NavigatorIOS style={styles.container}
initialRoute={{
title: 'Home',
component: HomeScreen,
passProps: {text: 'Woot!'}
}}
/>
)
}
}
N.B. that if you provide the isRequired that your code will slam to a halt if you don't provide it when you display/push HomeScreen.
Yeah, the navigator model breaks the React-y dataflow (as I've outlined in this answer, too: What is the right way to save data from a list view?)
IMHO best way to achieve what you're trying to do is to have this Geolocation service provide a global subscription functionality that your HomeScreen component subscribes to. E.g. using the Geolocation module's API that ships with React Native:
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {locationInfo: {...}};
this._watchID = Geolocation.watchPosition(
(locationInfo) => {this.setState({locationInfo});},
(error) => {...},
...
);
}
componentWillUnmount() {
Geolocation.clearWatch(this._watchID);
}
render() {
...
}
}
If you're using some sort of custom location service in between the device APIs and your app, then I would model a similar API for that service. You could for instance use the EventEmitter library that ships with React Native.
Now, if you absolutely must get an instance of HomeScreen from HomeTab, then that's possible too, using the ref prop, as I've outlined in this answer: Function to call onRightButtonPress in NavigatorIOS - React Native

Resources