I'm new to React so please have mercy.
I've also read all of the threads on this, React / JSX Dynamic Component Name and React/JSX dynamic component names in particular. The solutions did not work.
I'm using a tab style interface where a user selects a tab and the appropriate content loads. A parent component stores the tab's content state, passes the corresponding props to the content child. This child then loads the correct content component (as its own child).
var TabbedContent = React.createClass({
loadMenu: function() {
var menus=this.props.carDivState.vehicleDetailState;
for (key in menus) {
if (menus.hasOwnProperty(key)) {
if (menus[key]) {
var Component='TabbedContent'+key;
return <Component />;
}
}
}
},
render: function() {
return (
<div className="TabbedContent">
<div className="contentWrapper">
{this.loadMenu()}
</div>
</div>
)
}
});
loadMenu loops through the props until it finds a true prop. It then returns that key (for instance "Overview") and creates a variable (e.g. Component='TabbledContentOverview').
However, my code returns an HTML tag <tabbedcontentoverview></tabbedcontentoverview>
Question
How do I get React to return the React component instead of an HTML tag? I appear to be using the correct capitalized naming conventions. I've read the Facebook docs. I just don't get it.
https://github.com/vasanthk/react-bits/blob/master/patterns/30.component-switch.md
import HomePage from './HomePage.jsx';
import AboutPage from './AboutPage.jsx';
import UserPage from './UserPage.jsx';
import FourOhFourPage from './FourOhFourPage.jsx';
const PAGES = {
home: HomePage,
about: AboutPage,
user: UserPage
};
const Page = (props) => {
const Handler = PAGES[props.page] || FourOhFourPage;
return <Handler {...props} />
};
// The keys of the PAGES object can be used in the prop types to catch dev-time errors.
Page.propTypes = {
page: PropTypes.oneOf(Object.keys(PAGES)).isRequired
};
First, if you are using Bootstrap for your app, I'd suggest that you use react-bootstrap`s tab. If you are not, I would suggest that you at least take a look at the implementation of their TabPane and TabbedArea.
Here's an example of how it looks like in your app:
const tabbedAreaInstance = (
<TabbedArea defaultActiveKey={2}>
<TabPane eventKey={1} tab='Tab 1'>TabPane 1 content</TabPane>
<TabPane eventKey={2} tab='Tab 2'>TabPane 2 content</TabPane>
<TabPane eventKey={3} tab='Tab 3' disabled>TabPane 3 content</TabPane>
</TabbedArea>
);
React.render(tabbedAreaInstance, mountNode);
Now, back to your question, if you want to create a component by name, just call React.createElement from inside your loadMenu:
loadMenu: function() {
var menus=this.props.carDivState.vehicleDetailState;
for (key in menus) {
if (menus.hasOwnProperty(key)) {
if (menus[key]) {
return React.createElement('TabbedContent'+key);
}
}
}
}
You need to have a reference to an actual class in order to create an element from it (in JS or JSX).
Hold a map of keys to React classes (i.e tabbedChildren), and just create this element using the JS API:
var childComponent = tabbedChildren[key]
return React.createElement(childComponent)
https://facebook.github.io/react/docs/top-level-api.html
Related
I'm trying to use the new React context to hold data about the logged-in user.
To do that, I create a context in a file called LoggedUserContext.js:
import React from 'react';
export const LoggedUserContext = React.createContext(
);
And sure enough, now I can get access to said context in other components using consumers, as I do here for example:
<LoggedUserContext.Consumer>
{user => (
(LoggedUserContext.name) ? LoggedUserContext.name : 'Choose a user or create one';
)}
</LoggedUserContext.Consumer>
But obviously, for this system to be useful I need to modify my context after login, so it can hold the user's data. I'm making a call to a REST API using axios, and I need to assign the retrieved data to my context:
axios.get(`${SERVER_URL}/users/${this.state.id}`).then(response => { /*What should I do here?*/});
I see no way to do that in React's documentation, but they even mention that holding info of a logged in user is one of the use cases they had in mind for contexts:
Context is designed to share data that can be considered “global” for
a tree of React components, such as the current authenticated user,
theme, or preferred language. For example, in the code below we
manually thread through a “theme” prop in order to style the Button
component:
So how can I do it?
In order to use Context, you need a Provider which takes a value, and that value could come from the state of the component and be updated
for instance
class App extends React.Component {
state = {
isAuth: false;
}
componentDidMount() {
APIcall().then((res) => { this.setState({isAuth: res}) // update isAuth })
}
render() {
<LoggedUserContext.Provider value={this.state.isAuth}>
<Child />
</LoggedUserContext.Provider>
}
}
The section about dynamic context explains it
Wrap your consuming component in a provider component:
import React from 'react';
const SERVER_URL = 'http://some_url.com';
const LoggedUserContext = React.createContext();
class App extends React.Component {
state = {
user: null,
id: 123
}
componentDidMount() {
axios.get(`${SERVER_URL}/users/${this.state.id}`).then(response => {
const user = response.data.user; // I can only guess here
this.setState({user});
});
}
render() {
return (
<LoggedUserContext.Provider value={this.state.user}>
<LoggedUserContext.Consumer>
{user => (
(user.name) ? user.name : 'Choose a user or create one';
)}
</LoggedUserContext.Consumer>
</LoggedUserContext.Provider>
);
}
}
I gave a complete example to make it even clearer (untested). See the docs for an example with better component composition.
I want to dynamically create JSX tags for imported components. So my idea is having something like this:
import DemoComponent from './DemoComponent';
class DynamicRendering extends Component {
assembleResult() {
const {
democomponent
} = this.props;
const result = [];
if (democomponent) {
const Tag = `DemoComponent`;
result.push(<Tag />);
}
return result;
}
render() {
const result = this.assembleResult();
return result;
}
}
The idea is that I can pass a couple of different props to the component and then the component dynamically crates JSX tags and assembles them together. The reason I want this because I have about 15 components I want to render dynamically. Instead of implicitly writing them all I would prefer to make a loop over them and create them dynamically if needed. That way I can keep this component DRY.
The problem with the code above is that if you create a Tag like this, it will take it as a HTML element. This causes an error because there are no such HTML elements like 'DemoComponent'. I managed to solve this problem by creating a mapping of the name of the props to the component which should get loaded. See example below:
import DemoComponent from './DemoComponent';
const PROP_MODULE_MAP = new Map([
['democomponent', DemoComponent]
]);
class DynamicRendering extends Component {
assembleResult() {
const {
democomponent
} = this.props;
const result = [];
if (democomponent) {
const Tag = PROP_MODULE_MAP.get('democomponent');
result.push(<Tag />);
}
return result;
}
render() {
const result = this.assembleResult();
return result;
}
}
But I was wondering if there was a simpler way then creating this Map. Is there another way how you can dynamically create JSX tags which represent a imported component?
You can just let the parent pass the desired component type:
Parent.js:
import SomeComponent from './someComponent';
import Child from './child';
// the parent renders Child and passes the type SomeComponent as a prop
const Parent = () => <Child Elem={SomeComponent} />
Child.js:
// the Child renders the component type passed
// note that the prop "Elem" is capitalized so that it will not be treated as a html node
const Child = ({Elem}) => <Elem />;
export default Child;
This way the Child component is capable of rendering any component type that it gets passed. This is much more flexible and does not require the Child to know all the components it should render at compile time.
Note that when rendering the passed component type in the child the variable to render has to be capitalized or it will be treated as a usual html node. See User-Defined Components Must Be Capitalized for details.
If you do not want the prop name to be capitalized you can reassign the value to a capitalized name in the child before rendering it:
const Child = ({elem: Elem}) => <Elem />;
I don't know what is your project layout, but I suggest you could do something like this:
create a file for dynamic component import in your components folder:
import Comp1 from './Comp1'
import Comp2 from './Comp2'
export default { Comp1, Comp2 }
create a helper for function for dynamic rendering:
import components from './components/dynamic'
const renderComponents = compNames => {
// take compNames as a list
const compsToRender = compNames.map(name => components[name])
// get all components by compNames provided and return them in array
return compsToRender.map(Component => <Component />)
}
Then call it where you want .
<App>
{renderComponents(['Comp1', 'Comp2'])}
</App>
If you want to pass props with component names, you could pass objects instead of strings and pass those in components inside the function, but I don't see why it will be better then just use plain components with props
I have a React app that has children of tabs and the display.
<App>
<Nav>
<Tab1></Tab1>
<Tab2></Tab2>
<Tab3></Tab3>
</Nav>
<Section>Display Stuff Here</Section>
<Footer></Footer>
</App>
When I made the tabs within the app, I have a for loop that appends the tabs in a Map variable (which is the child of the App React Component).
for(...) {
tab = React.createElement(Tab, {changeDisplay: () => {this.handleClick(this.changeDisplay(i)}});
myMap.set(i, tab);
}
What it should be doing is passing the i variable which increments from the for-loop. I even did a console.log to make sure it is pass 1, 2, 3, 4.
However, what is actually displaying is the last variable: 4.
Within the Tab class, all I have access to use calling the props:
class Tab extends React.Component {
constructor(props) {
super(props);
this.tab = React.Component('div', null);
}
render() {
return this.tab;
}
componentDidMount() {
this.forceUpdate();
}
componentWillUpdate(nextProps, nextState) {
this.tab = React.Component('div', {onClick: nextProps.changeDisplay});
}
}
The value for changeDisplay should be unique to each Tab, so Tab2 should only have a value of 2 and Tab3 should only have 3, etc. The for-loop should not be passing the next value to any previous Tabs already created since they are already made and appended to the map variable.
Any advice on how to make this work?
I could make custom functions:
changeDisplay1() { changeDisplay(1) };
changeDisplay2() { changeDisplay(2) };
changeDisplay3() { changeDisplay{3) };
changeDisplay4() { changeDisplay(4) };
However, I want to do it the right way instead of making hacks.
React is VERY powerful and you can find multiple ways of rendering a list of components based on parent state. Here's a quick version that I've pulled together.
You can view a working copy here: https://codesandbox.io/s/zzqLPrjO
Explanation:
We create a parent component that stores data such as the tabs available and active tab. We load the tabs available by creating an array from the keys on the Library object. (This is totally optionally, but makes it easier to add additional tabs)
Once the tabs are loaded into the parent component, I've also created a function that sets/changes the active tab. This will be passed down to our tabs in our map, which renders each tab that was picked up from Library object.
In our Tab component, we just render the name along with a click event that is hooked to our parent component which will change the active tab to the tab that has been clicked on. (Note: Notice how we avoid function binding in the render method so that we don't create a new function every time the component is rendered (this is considered bad practice, which is why I avoid it.)
Once the active tab has changed, we make a basic lookup to the Library and render the component attached to that index.
import React from 'react';
import { render } from 'react-dom';
// *---------------------
// | Tab Component
// *---------------------
const activeTab = {
background: 'green',
}
class Tab extends React.Component {
change = () => this.props.change(this.props.name)
render = () => {
const {
name: NAME,
active: ACTIVE,
} = this.props;
const isActive = ACTIVE === NAME ? activeTab : null;
return <button style={isActive} onClick={this.change}>{this.props.name}</button>
}
}
// *---------------------
// | Mock list of Components
// *---------------------
const Home = () => <div>Home</div>
const About = () => <div>About</div>
// *---------------------
// | Library of Components
// *---------------------
const Library = {
Home,
About
}
// *---------------------
// | Display Components
// *---------------------
const Display = ({screen}) => {
const Comp = Library[screen];
return screen === null ? <div>Please select a tab</div> : <Comp/>
}
// *---------------------
// | Main App
// *---------------------
class App extends React.Component {
state = {
tabs: [],
activeTab: null,
}
componentWillMount() {
const tabs = Object.keys(Library);
this.setState({
tabs: tabs
});
}
changeTab = (tab) => {
this.setState({ activeTab: tab });
}
render() {
return (
<div>
{ this.state.tabs.map((tab,index) => {
return <Tab key={index} name={tab} active={this.state.activeTab} change={this.changeTab} />
}) }
<Display screen={this.state.activeTab} />
</div>
)
}
}
render(<App />, document.getElementById('root'));
I've been getting started with react-redux and finding it a very interesting way to simplify the front end code for an application using many objects that it acquires from a back end service where the objects need to be updated on the front end in approximately real time.
Using a container class largely automates the watching (which updates the objects in the store when they change). Here's an example:
const MethodListContainer = React.createClass({
render(){
return <MethodList {...this.props} />},
componentDidMount(){
this.fetchAndWatch('/list/method')},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(oId){
this.props.fetchObject(oId).then((obj) => {
this._unwatch = this.props.watchObject(oId);
return obj})}});
In trying to supply the rest of the application with as simple and clear separation as possible, I tried to supply an alternative 'connect' which would automatically supply an appropriate container thus:
const connect = (mapStateToProps, watchObjectId) => (component) => {
const ContainerComponent = React.createClass({
render(){
return <component {...this.props} />
},
componentDidMount(){
this.fetchAndWatch()},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(){
this.props.fetchObject(watchObjectId).then((obj) => {
this._unwatch = this.props.watchObject(watchObjectId);
return obj})}
});
return reduxConnect(mapStateToProps, actions)(ContainerComponent)
};
This is then used thus:
module.exports = connect(mapStateToProps, '/list/method')(MethodList)
However, component does not get rendered. The container is rendered except that the component does not get instantiated or rendered. The component renders (and updates) as expected if I don't pass it as a parameter and reference it directly instead.
No errors or warnings are generated.
What am I doing wrong?
This is my workaround rather than an explanation for the error:
In connect_obj.js:
"use strict";
import React from 'react';
import {connect} from 'react-redux';
import {actions} from 'redux/main';
import {gets} from 'redux/main';
import {isFunction, omit} from 'lodash';
/*
A connected wrapper that expects an oId property for an object it can get in the store.
It fetches the object and places it on the 'obj' property for its children (this prop will start as null
because the fetch is async). It also ensures that the object is watched while the children are mounted.
*/
const mapStateToProps = (state, ownProps) => ({obj: gets.getObject(state, ownProps.oId)});
function connectObj(Wrapped){
const HOC = React.createClass({
render(){
return <Wrapped {...this.props} />
},
componentDidMount(){
this.fetchAndWatch()},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(){
const {fetchObject, watchObject, oId} = this.props;
fetchObject(oId).then((obj) => {
this._unwatch = watchObject(oId);
return obj})}});
return connect(mapStateToProps, actions)(HOC)}
export default connectObj;
Then I can use it anywhere thus:
"use strict";
import React from 'react';
import connectObj from 'redux/connect_obj';
const Method = connectObj(React.createClass({
render(){
const {obj, oId} = this.props;
return (obj) ? <p>{obj.id}: {obj.name}/{obj.function}</p> : <p>Fetching {oId}</p>}}));
So connectObj achieves my goal of creating a project wide replacement for setting up the connect explicitly along with a container component to watch/unwatch the objects. This saves quite a lot of boiler plate and gives us a single place to maintain the setup and connection of the store to the components whose job is just to present the objects that may change over time (through updates from the service).
I still don't understand why my first attempt does not work and this workaround does not support injecting other state props (as all the actions are available there is no need to worry about the dispatches).
Try using a different variable name for the component parameter.
const connect = (mapStateToProps, watchObjectId) => (MyComponent) => {
const ContainerComponent = React.createClass({
render() {
return <MyComponent {...this.props} obj={this.state.obj} />
}
...
fetchAndWatch() {
fetchObject(watchObjectId).then(obj => {
this._unwatch = watchObject(watchObjectId);
this.setState({obj});
})
}
});
...
}
I think the problem might be because the component is in lower case (<component {...this.props} />). JSX treats lowercase elements as DOM element and capitalized as React element.
Edit:
If you need to access the obj data, you'll have to pass it as props to the component. Updated the code snippet
In my scenario <MainComponent /> having so many subcomponents .But particular subcomponent i need to populate the new data with all other subcomponents.
var MainComponent = React.createClass({
renderScene:function(route, navigator) {
var type=this.props.type;
if(type =="text"){
<GetInputField/>
}
else if(type=="number"){
<GetNumberField/>
}
else if(type=="image"){
<GetImageField />
//using this <GetImageField> component i am showing two buttons.
//while click on the button i moving to another scene .
//in this scene i am showing local images using CameraRoll component.
// When select the image i need to populate that image in the <GetImageField/> component through the navigator.
// But i am unable to replace to the <GetImageField/> component using navigator
}
},
render:function() {
return(<Navigator
renderScene={this.renderScene.bind(this)}
navigator={this.props.navigator}
navigationBar={
<Navigator.NavigationBar style={styles.navBarStyle}
routeMapper={NavigationBarRouteMapperDisplayCustomSchema} />
} />)
}
})
In my scenario i need to populate the images in <GetImageField /> component with other components of <MainComponent/>
If the CameraRoll is a child of GetImageField you can just pass a callback property. For instance:
var GetImageField = React.createClass({
render: function() {
return <CameraContainer onPhotoSelected={this.updatePhotos} />
},
updatePhotos: function(images) {
this.setState({ images });
}
});
If it's not a children then you might need to use events to propagate the information over.
Might be worth reading this overview of how React components can communicate.