I'm studying ReactNative.Navigator.renderScene props.
'use strict';
import React,{Component} from 'react';
import ReactNative from 'react-native';
const {
TouchableHighlight,
Navigator,
AppRegistry,
Text,
View,
} = ReactNative;
class TestClass extends Component{
render(){
return <Text>test</Text>
}
}
class MyTag extends Component{
render(){
return <Text>test</Text>
}
}
class Main extends Component{
render(){
const routes =[{component:TestClass,index:0},{component:MyTag,index:1}]
return(
<Navigator
initialRoute={routes[0]}
initialRouteStack={routes}
renderScene={(route, navigator) =>
<View><TouchableHighlight onPress={() => {
if (route.index === 0) {
navigator.push(routes[1]);
} else {
navigator.pop();
}
}}><View>{route.component}</View>
</TouchableHighlight></View>
}
/>
)
}
}
AppRegistry.registerComponent('ChoiceComponent', () => Main);
Can component in routes variable be called by using {route.component} in renderScene props in JSX?
TestClass is called correctly if {route.component} is changed into <Test Class />.
You're asking if you can use an object property (route.component) in place of a class name. Absolutely! Remember, these are just identifiers. You use it exactly the same way you used the class name.
So instead of
{route.component}
you want
<route.component />
(But keep reading, we may have to do more.)
Example:
class Example1 extends React.Component {
render() {
return <div style={{color: "blue"}}>{this.props.text}</div>;
}
}
class Example2 extends React.Component {
render() {
return <div style={{color: "green"}}>{this.props.text}</div>;
}
}
const routes = [
{component: Example1},
{component: Example2}
];
ReactDOM.render(
<div>{routes.map(route => <route.component text="Hi there" />)}</div>,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
The above works, but as far as I can tell from the React documentation, our component identifier name should start with a capital letter:
User-Defined Components Must Be Capitalized
When an element type starts with a lowercase letter, it refers to a built-in component like <div> or <span> and results in a string 'div' or 'span' passed to React.createElement. Types that start with a capital letter like <Foo /> compile to React.createElement(Foo) and correspond to a component defined or imported in your JavaScript file.
In our case, it's route.component, which is currently handled correctly (because of the .; it wouldn't if it were route_component, for instance), but that appears to be undocumented behavior. (Supporting the . is documented behavior, what appears undocumented is allowing you to start with a lower-case letter when it's not a simple identifier.)
So I think to be officially in line with the docs, we'd want to assign that to a capitalized identifier:
const RouteComponent = route.component;
return <RouteComponent text="Hi there" />;
Like so:
class Example1 extends React.Component {
render() {
return <div style={{color: "blue"}}>{this.props.text}</div>;
}
}
class Example2 extends React.Component {
render() {
return <div style={{color: "green"}}>{this.props.text}</div>;
}
}
const routes = [
{component: Example1},
{component: Example2}
];
ReactDOM.render(
<div>{routes.map(route => {
const RouteComponent = route.component;
return <RouteComponent text="Hi there" />;
})}</div>,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Related
New to React - I am trying to use multiple contexts within my App component, I tried following the official guide on multiple contexts.
Here is my current code:
App.js
import React from "react";
import { render } from "react-dom";
import Login from "./Login";
import AuthContext from "./AuthContext";
import LayoutContext from "./LayoutContext";
import LoadingScreen from "./LoadingScreen";
class App extends React.Component {
render() {
const { auth, layout } = this.props;
return (
<LayoutContext.Provider value={layout}>
<LoadingScreen />
<AuthContext.Provider value={auth}>
<AuthContext.Consumer>
{auth => (auth.logged_in ? console.log("logged in") : <Login />)}
</AuthContext.Consumer>
</AuthContext.Provider>
</LayoutContext.Provider>
);
}
}
render(<App />, document.getElementById("root"));
Login.js
import React from "react";
class Login extends React.Component {
render() {
return (
<div></div>
);
}
}
export default Login;
AuthContext.js
import React from "react";
const AuthContext = React.createContext({
logged_in: false
});
export default AuthContext;
LayoutContext.js
import React from "react";
const LayoutContext = React.createContext({
show_loading: false
});
export default LayoutContext;
LoadingScreen.js
import React from "react";
import LayoutContext from "./LayoutContext";
class LoadingScreen extends React.Component {
render() {
return (
<LayoutContext.Consumer>
{layout =>
layout.show_loading ? (
<div id="loading">
<div id="loading-center">
<div className="sk-chasing-dots">
<div className="sk-child sk-dot1"></div>
<div className="sk-child sk-dot2"></div>
</div>
</div>
</div>
) : null
}
</LayoutContext.Consumer>
);
}
}
export default LoadingScreen;
Following the example, I never really understood how this.props (in App.js) could hold my different contexts.
Both auth and layout show up as undefined, this.props is empty, which will in turn cause my app to throw errors such as Cannot read property 'show_loading' of undefined
I immediately liked the example provided in the React documentation, but I can't get this to work.
I've made a small snippet to show you how you could structure your context providers and consumers.
My App component in this case is the root of the app. It has all the providers, along with the value for each one of them. I am not changing this value, but I could if I wanted to.
This then has a single child component, MyOutsideComponent, containing all the chained consumers. There are better ways to do this, I just wanted to show you, one by one, how chaining consumers work. In practice you can neatly reduce this using a few techniques.
This MyOutsideComponent has the actual component, MyComponent, which takes all the context elements and just puts their value on the page. Nothing fancy, the point was to show how the values get passed.
let FirstContext = React.createContext('first');
let SecondContext = React.createContext('second');
let ThirdContext = React.createContext('third');
let FourthContext = React.createContext('fourth');
let MyComponent = (props) => {
return (<span >{Object.values(props).join(" ")}</span>);
};
let App = (props) => {
return (
<FirstContext.Provider value="this is">
<SecondContext.Provider value="how you">
<ThirdContext.Provider value="pass context">
<FourthContext.Provider value="around">
<MyOutsideComponent />
</FourthContext.Provider>
</ThirdContext.Provider>
</SecondContext.Provider>
</FirstContext.Provider>
);
};
let MyOutsideComponent = () => {
return ( < FirstContext.Consumer >
{first =>
(< SecondContext.Consumer >
{second =>
(< ThirdContext.Consumer >
{third =>
(<FourthContext.Consumer >
{fourth =>
(<MyComponent first={first} second={second} third={third} fourth={fourth} />)
}
</FourthContext.Consumer>)
}
</ThirdContext.Consumer>)
}
</SecondContext.Consumer>)
}
</FirstContext.Consumer>);
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Now, for the actual explanation. createContext gives you two actual components: a Provider and Consumer. This Provider, as you found out, has the value. The Consumer takes as child a single function taking one argument, which is your context's value.
This is where the docs are a bit unclear, and a bit which I hope I can help a bit. This does not get passed automatically in props unless the Provider is the direct parent of the component. You have to do it yourself. So, in the example above, I chained four consumers and then lined them all up in the props of my component.
You've asked about class-based components, this is how it ends up looking like:
let FirstContext = React.createContext('first');
let SecondContext = React.createContext('second');
let ThirdContext = React.createContext('third');
let FourthContext = React.createContext('fourth');
class MyComponent extends React.Component {
render() {
return ( < span > {Object.values(this.props).join(" ")} < /span>);
}
}
class App extends React.Component {
render() {
return (
<FirstContext.Provider value = "this is" >
<SecondContext.Provider value = "how you" >
<ThirdContext.Provider value = "pass context" >
<FourthContext.Provider value = "around" >
<MyOutsideComponent / >
</FourthContext.Provider>
</ThirdContext.Provider >
</SecondContext.Provider>
</FirstContext.Provider >
);
}
}
class MyOutsideComponent extends React.Component {
render() {
return (
<FirstContext.Consumer >
{ first =>
(< SecondContext.Consumer >
{ second =>
( < ThirdContext.Consumer >
{ third =>
( < FourthContext.Consumer >
{ fourth =>
( < MyComponent first = {first} second={second} third={third} fourth={fourth} />)
}
</FourthContext.Consumer>)
}
</ThirdContext.Consumer>)
}
</SecondContext.Consumer>)
}
</FirstContext.Consumer>
);
}
}
ReactDOM.render( < App / > , document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app" />
I have a global variable called global.language. In my CustomHeader component, I have a Button that toggles the language global variable. What I want is to update all my screen components to reflect the language change.
I don't know if the best way to go is to get a reference to the Screens or to use an event library or if there are React friendly ways of doing this.
My CustomHeader.js looks like this:
export default class CustomHeader extends React.Component {
constructor(props) {
super(props);
this.toggleLanguage = this.toggleLanguage.bind(this);
}
render() {
return (
<Button onPress={ this.toggleLanguage } title="Language" accessibilityLabel="Toggle language" />
);
}
toggleLanguage() {
if (global.language == "PT") global.language = "EN";
else if (global.language == "EN") global.language = "PT";
}
}
My Screen.js renders numerous components called Event. This is what my Event.js looks like:
export default class Event extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Card>
<Text>{Event.getTitle(this.props.data)}</Text>
</Card>
);
}
static getTitle(data) {
if (global.language === "PT") return data.title;
else if (global.language === "EN") return data.title_english;
}
}
Live sandbox
In details.
React.createContext we can just export to reuse. But this would be just "generic" context. Better encapsulate data and methods we need into custom container element and HOC:
import React from "react";
const context = React.createContext();
export class I18N extends React.Component {
state = {
language: "en"
};
setLanguage = language => {
this.setState({ language });
};
render() {
return (
<context.Provider
value={{ language: this.state.language, setLanguage: this.setLanguage }}
>
{this.props.children}
</context.Provider>
);
}
}
export function withI18n(Component) {
return props => (
<context.Consumer>
{i18nContext => <Component {...props} i18n={i18nContext} />}
</context.Consumer>
);
}
<I18N> is provider that will typically go just once on the topmost level.
And with HOC withI18n we are going to wrap every element that need access to our language value or ability to update this value.
import React from "react";
import ReactDOM from "react-dom";
import { I18N, withI18n } from "./i18n";
const Header = withI18n(function({i18n}) {
const setLang = ({ target: { value } }) => i18n.setLanguage(value);
return (
<div>
<input type="radio" value="en" checked={i18n.language === "en"} onChange={setLang} /> English
<input type="radio" value="fr" checked={i18n.language === "fr"} onChange={setLang} /> French
<input type="radio" value="es" checked={i18n.language === "es"} onChange={setLang} /> Spanish
</div>
);
});
const Body = withI18n(function(props) {
return <div>Current language is {props.i18n.language}</div>;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<I18N>
<Header />
<Body />
</I18N>, rootElement);
And finally good article with some additional details: https://itnext.io/combining-hocs-with-the-new-reacts-context-api-9d3617dccf0b
I have a function that is used to change the state of a react component but I'm trying to pass the function in another file. I get the error that the function I'm trying to pass (changeView) is not defined.
This is the App.js
export default class App extends Component {
constructor() {
super();
this.state = {
language: "english",
render: ''
}
}
changeView(view, e){
console.log(view);
this.setState({render: view});
}
_renderSubComp(){
switch(this.state.render){
case 'overview': return <Overview />
case 'reviews': return <Reviews />
}
}
render() {
const {render} = this.state
return <Fragment>
<Header language={this.state.language} />
<Hero />
<Navigation render={render}/>
{this._renderSubComp()}
</Fragment>;
}
}
I'm trying to pass the changeView method to the Navigation.JS component, so I can change the active link as well as render the components listed in the _renderSubComp method above.
import React from "react";
import "./navigation.css";
import { changeView } from "../app";
export default function Navigation() {
return <div className="navigation">
<a onClick={this.changeView.bind(this,
'overview')}>Overview</a>
<a>Reviews</a>
</div>;
}
How should I pass the function to another file so it's able to change the state of my component and render the component I need.
You can't import a method like that. You will pass your function like any other prop to your component and you use there.
I've changed a few things. Firstly, I define changeView function as an arrow one, so we don't need to bind it. Secondly, I pass this function to the component as a prop. Thirdly, I used this function there like:
onClick={() => props.changeView('overview')}
As you can see it is props.changeView not state.changeView
Just go through the official documentation a little bit more. You are confused about states, props and how to pass them to your components.
class App extends React.Component {
constructor() {
super();
this.state = {
language: "english",
render: ''
}
}
changeView = (view, e) => {
console.log(view);
this.setState({ render: view });
}
render() {
const { render } = this.state
return <div>
<Navigation render={render} changeView={this.changeView} />
</div>;
}
}
const Navigation = (props) => {
return <div className="navigation">
<a onClick={() => props.changeView('overview')}>Overview</a>
<a>Reviews</a>
</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
say my HOC is:
import React, { Component } from "react";
let validateURL = WrappedComponent =>
class extends Component{
render() {
if( wrappedcomponentnameis === 'xyz')
return ...
elseif(wrappedcomponentnameis === 'abc')
return ...
and so on....
}
};
export default validateURL;
how do I get the name of wrapped component inside this HOC?
You can access it via WrappedComponent.name:
const HOC = WrappedComponent => class Wrapper extends React.Component{
render() {
if (WrappedComponent.name === 'Hello') {
return <WrappedComponent name='World' />
}
return <WrappedComponent/>
}
}
class Hello extends React.Component {
render() {
return <div>Hello {this.props.name}</div>
}
}
const App = HOC(Hello)
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
However, I will prefer to pass optional props to the HOC, in order to control its behavior, because it's much safer, rather than relying on WrappedComponent.name.
For example: there are many libraries (as redux, react-router, and etc) which provide some functionality to your components through HOC mechanism. When this libraries wraps your component, then WrappedComponent.name will point to the library HOC name and will break your logic silently.
Here's how you can pass custom props:
const HOC = (WrappedComponent, props) => class Wrapper extends React.Component{
render() {
const { shouldPassName } = props
if (shouldPassName) {
return <WrappedComponent name='World' />
}
return <WrappedComponent/>
}
}
const App = HOC(Hello, { shouldPassName: true })
I'm a real beginner in javascript / React...but I'm trying to set-up a tag based on a string value. Why does widget1 fail to get instantiated? (I get an uncaught ReferenceError: FooA is not defined error) What difference does importing the react component make, versus defining it in the same file?
import React, {Component} from "react";
import ReactDOM from "react-dom";
// Assume FooA and FooB have identical definitions
import FooA from './fooa.jsx'
class FooB extends Component {
render() {
return(<p>Hello A</p>);
}
};
class Splash extends Component {
render() {
var widget1 = eval('new ' + "FooA")
var widget2 = eval('new ' + "FooB")
return (
<div>
{(widget1.render())}
{(widget2.render())}
</div>
)
};
}
ReactDOM.render(<Splash/>, container);
I am passing this through webpack to get a single .js file.
Is there a better way to achieve this?
You dont have to instantiate Components, React does that for you.
Your Splash component should look like this:
class Splash extends Component {
render() {
return (
<div>
<FooA />
<FooB />
</div>
)
};
}
Now lets supouse you want to have some logic to determine which component must be rendered:
class Splash extends Component {
let comp = (<FooA />);
if(some condition)
comp = (<FooB />);
render() {
return (
<div>
{comp}
</div>
)
};
}
Now let supouse you want just to parametrize the text:
class FooA extends Component {
render() {
return(<p>this.props.textToShow</p>);
}
};
class Splash extends Component {
let text = 'whatever text you want to show';
render() {
return (
<div>
<FooA textToShow={text}/>
</div>
)
};
}
You can pass as a prop other components as well:
class FooA extends Component {
render() {
return(
<p>Some text</p>
{this.props.child}
);
}
};
class FooAChild extends Component {
render() {
return(
<p>I am a child</p>
);
}
};
class Splash extends Component {
let child = (<FooAChild />);
render() {
return (
<div>
<FooA child={child}/>
</div>
)
};
}
You're coming at this problem from the wrong angle. If you want to make a component able to render other components in a generic, reusable way, there's three approaches you can take:
Pass the component class as a prop
class Splash extends Component {
render() {
let heading = this.props.heading;
// These have to start with a capital letter, otherwise
// JSX assumes 'widget1' is a normal HTML element.
let Widget1 = this.props.widget1;
let Widget2 = this.props.widget2;
return (
<div>
<h1>{heading}</h1>
<Widget1 />
<Widget2 />
</div>
)
};
}
// This can then be used like so:
<Splash heading="My Generic Splash" widget1={FooA} widget2={FooB} />
Pass the component instance as a prop:
class Splash extends Component {
render() {
let heading = this.props.heading;
let widget1 = this.props.widget1;
let widget2 = this.props.widget2;
return (
<div>
<h1>{heading}</h1>
{widget1}
{widget2}
</div>
)
};
}
// This can then be used like so:
let fooA = <FooA />;
<Splash heading="My Generic Splash" widget1={fooA} widget2={<FooB />} />
Pass the components as children:
class Splash extends Component {
render() {
let heading = this.props.heading;
return (
<div>
<h1>{heading}</h1>
{this.props.children}
</div>
)
};
}
// This can then be used like so:
<Splash heading="My Generic Splash">
<FooA />
<FooB />
</Splash>