react-native update NavigatorIOS component's props - reactjs

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

Related

Can a React component render another component stored in it's state?

I tried to create a container component that might change its inner component over time.
class Container extends React.Component {
constructor(props) {
super(props)
this.state = { content: new FooComponent() }
}
render() {
return <div>
{ this.state.content }
</div>
}
}
I get errors like:
Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state}). If you meant to render a collection of children, use an array instead.
I tried { [this.state.component] }, but get same error.
Are you able to put components from this.state into the JSX that is returned from render()? If not, how do you implement a container? I'm thinking of creating a container something like iOS's UINavigationController, where I can push and pop components in a stack, with only the top one rendered to the screen.
You can use JSX: this.state = { content: <FooComponent/> }
Which is React.createElement() behind the scenes.
class Container extends React.Component {
constructor(props) {
super(props)
this.state = { content: <FooComponent/> }
}
render() {
return <div>{this.state.content}</div>;
}
}
Or refer to Creating React Elements.
For reference here are more readable forms:
class Container extends React.Component {
state = {
content: <FooComponent />
};
render() {
return <>{this.state.content}</>;
}
}
function Container() {
const [content, setContent] = useState(<FooComponent />);
return <>{content}</>;
}
Can a React component render another component stored in it's state?
Yes you can!
There is two ways of doing it
1
import FooComponent from '..'
...
this.state = {
content: <FooComponent/>
}
...
render(){
return (
<div>{this.state.content}</div>
)
}
or
2
import FooComponent from '..'
...
this.state = {
content: FooComponent
}
...
render(){
const {
content: BarComponent
} = this.state
return (
<div>
<BarComponent />
</div>
)
}
I get errors like:
Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state}). If you meant to render a collection of children, use an array instead.
This happens because when you do new FooComponent() it returns an object and you can't render an object.
When using JSX, will be transpiled to React.createElement(FooComponent) and this is renderable.
When using only FooComponent, you store a reference to it and only in the render method you create a JSX that will become React.createElement(BarComponent)
The second aproach is good when the component is comming from props and you depending on the situation, you want to give it some props.
Working Example
The main question probably already answered there.
Notice: Limited functionality - storing only components will be a simple stateless view/screen stack/buffer.
The problem can be much wider. This solution won't be equal to saving previous states, changes ... no props/params, no history, no context, state reversing ... but shared common state (redux store) can be an advantage - no old states, autoupdating the whole chain of stored views.
class Container extends React.Component {
constructor(props) {
super(props)
this.state = { content: this.FooComponent() }
}
FooComponent(){
return(<FooComponent/>)
}
render() {
return <div>
{ this.state.content }
</div>
}
}

How to pass props to 'screens'/components in react-navigation

I'm fairly new to programming in general and even newer to JS and React(Native) but I have worked on this for an entire day now and I still haven't figured it out so I have resorted to Stack Overflow in hopes that someone can help me.
Basically what I want to accomplish is to set other Components as children of the App component because I want them to be able to access information that I will set in the state of App. However, at the same time, I am also using react-navigation to create bottom navigation bars and thus I have no idea on how I can pass props of App to these other Components such as the ExplorePage component which is representative of the other children components.
App
import React from 'react';
import ExplorePage from './app/tabs/ExplorePage';
import {createBottomTabNavigator} from 'react-navigation';
...other imports
class App extends React.Component {
state = {
parentState: 'testing testing',
}
}
const MainScreenNavigator = createBottomTabNavigator(
{
Home: {screen: ExplorePage},
Search: {screen: SearchPage},
Favorites: {screen: FavoritesPage},
}
);
export default MainScreenNavigator;
ExplorePage, which is just like SearchPage and FavoritesPage
...imports
export default class ExplorePage extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
console.log(this.props.parentState ? this.props.parentState : "Parent state does not exist what do :(");
}
render(){
return(
<Text>Testing</Text>
)
}
And obviously every time the console prints that parentState does not exist. I thought that being in the same place would give the other components like ExplorePage props of App. Thanks for helping me!
for those who are looking for a React Navigation 5 solution, you can use initialParams like this:
<Stack.Navigator>
<Stack.Screen
name="screenName"
component={screenComponent}
initialParams={{key: value}}
/>
</Stack.Navigator>
You could pass a props using function. Try this
import React from 'react';
import ExplorePage from './app/tabs/ExplorePage';
import {createBottomTabNavigator} from 'react-navigation';
...other imports
class App extends React.Component {
state = {
parentState: 'testing testing',
}
render() {
// old
// const MainScreenNavigator = mainScreenNavigator(this.state.parentState);
const MainScreenNavigator = mainScreenNavigator(this.state);
return (
<MainScreenNavigator />
)
}
}
const mainScreenNavigator = value => createBottomTabNavigator(
{
// Home: { screen : props => <ExplorePage {...props} parentState={value} /> },
Home: { screen : props => <ExplorePage {...props} {...value} /> },
Search: {screen: SearchPage},
Favorites: {screen: FavoritesPage},
}
);
export default App;
Edit
First thing, I changed your MainScreenNavigator to be a function, as it is accepting state values dynamically.
Second thing, Instead of directly assigning { screen : Component }, I used function. This is the feature provided by reactnavigation. You can find about this in the documentation. ReactNavigation
If you want to pass multiple attributes then you can use es6 spread operator, as shown in the edit. {...value}, this will pass all the property of value to that component.
You should use Navigator Props "screenProps" as mentionned in API:
screenProps - Pass down extra options to child screens
On child screen, just take props via this.props.screenProps

Passing Props to Screens in React Native

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?

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>
);
}
}

Accessing this.context from onSubmitSuccess of redux-form

Here is the problem I am facing:
I am using react-router and redux-form in my application. In one of my forms, I want to use the router to go back to the main page after the submission succeeds. Here is the code I have:
function Router(target) {
target.contextTypes = target.contextTypes || {};
target.contextTypes.router = React.PropTypes.func.isRequired;
}
enter code here
#Router
#reduxForm({
form: 'editLocation',
fields: LocationFormFields,
validate: ValidateLocationForm,
onSubmitSuccess: () => {
var path = '/locations';
this.context.router.push(path);
},
onSubmitFail: () => {
console.log('failed');
}
}, MapStateToProps)
export class EditLocation extends React.Component .....
The problem is that 'this.context' is not defined inside the onSubmitSucess method.
I know that I can take care of this using browserHistory directly. However, I don't want to really go that route and I want to use this.cotext. Any ideas?
I think your best bet is to not make your form component the same as your route component, and pass in the onSubmitSuccess as a prop to your form component.
import { withRouter } from 'react-router'
#withRouter // injects router as prop
export class EditLocation extends React.Component {
handleSubmitSuccess() {
this.props.router.push('/locations')
}
render() {
return <EditLocationForm
onSubmitSuccess={this.handleSubmitSuccess}/>
}
}
#reduxForm({
form: 'editLocation',
fields: LocationFormFields,
validate: ValidateLocationForm,
onSubmitFail: () => {
console.log('failed');
}
}, MapStateToProps)
export class EditLocationForm extends React.Component .....
To use context inside a component you need to specify contextTypes
static contextTypes = {
router: PropTypes.object
};
So I would recommend either to call you function from inside the component and specify contextTypes there or add one higher order component to handle that.

Resources