React: storing components in an object - reactjs

I have a few dozen fields in my users' profiles, and I'm trying to build an efficient means of displaying them in the appropriate input form components.
eg, a profile might look like:
profile1={
name: 'Cornelius Talmadge',
phone: '1'
}
And if I could stack components in something like this...
export const FieldCatalogue = {
name: <TextField defaultValue={this.state.userProfile.name} label={"Name"} />
}
...then I could do something like this:
for (let field in Object.keys(profile1)) {
return FieldCatalogue[field]
}
which would be super cool.
This question has a great answer for if my input components were all constructed via the normal syntax (eg, Component = React.createClass({..})), but:
a. that's a lot of boilerplate
b. i'd have to pass props galore to each one, which isn't optimal
The perfect situation for me would basically be passing input components almost as strings, such that they fell into the scope (state, props, etc.) of whatever parent component they were rendered.
Is this possible and/or advisable?

The perfect situation for me would basically be passing input
components almost as strings, such that they fell into the scope
(state, props, etc.) of whatever parent component they were rendered.
Is this possible and/or advisable?
Yes, it is actually possible! No, I don't think this is something I would actually use. But it works, and it looks kinda cool. In my example, the FieldCatalogue components obviously don't have their own separate this.state object, but by binding them to the parent component this they automagically inherit the correct context.
Note that the example will not work if the components are defined as arrow functions, because arrow functions never have own this objects.
Oh, and the key in <Tmp key={i} /> is just there because we need to supply React with some kind of identifier when we loop over an array.
I had to try this as an exercise, and this is quite neat:
https://jsfiddle.net/dannyjolie/e9s09xrm/
const FieldCatalogue = {
name: function() {
return <input defaultValue = {this.state.userProfile.name} label = {"Name"}/>;
},
age: function() {
return <input defaultValue = {this.state.userProfile.age}/>;
}
}
const App = React.createClass({
getInitialState: function() {
return {
userProfile: {
name: 'Name in parent state',
age: 'Age in parent state'
}
};
},
render: function() {
let content = Object.keys(FieldCatalogue).map((objkey, i) => {
let Tmp = FieldCatalogue[objkey].bind(this)
return <Tmp key = {i} />;
});
return <div>{content}</div>);
}
});
This way the thiscontext of the parent component is passed to the FieldCatalogue components, and it just works. Really not sure if it's a good thing to do though.

You could map over the user objects keys and match them to corresponding components in fieldCatalogue. If the key exists, render the component at that key and pass all of the parent components props in:
CodePen
const fieldCatalogue = { // stateless component functions rather than React element literals
name(props) {
return <div>Name: {props.value}</div>
},
age(props) {
return <div>Age: {props.value}</div>
}
};
class ProfileFields extends React.Component {
render() {
const {userProfile} = this.props;
return (
<div>
{Object.keys(userProfile).map((key) => {
if(fieldCatalogue.hasOwnProperty(key)) {
const FieldComponent = fieldCatalogue[key];
return (<FieldComponent {...this.props} value={userProfile[key]}/>);
} else {
return null;
}
})}
</div>
);
}
}
Alternatively, your field components could just handle undefined values by returning null. Then you could render all of them and only the ones that exist on the user object would be rendered.

Related

Is there a way to access a React component's sub-components?

So I know that you can access a component's children with this.props.children:
<MyComponent>
<span>Bob</span>
<span>Sally</span>
</MyComponent>
Which is great if I'm interested in Bob and Sally, but what if I want to interact with the components that make up MyComponent (i.e. Subcomp1 and Subcomp2 shown below)?
render: function() {
return (
<div className="my-comp">
<Subcomp1 />
<Subcomp2 />
</div>
);
},
Use Case
I'm trying to create a higher order component that manages the tab index (roving tab index: https://www.w3.org/TR/wai-aria-practices/#kbd_roving_tabindex) of the wrapped component's sub-components, so it would be great if I could get a ref to the wrapped component and filter it's subcomponents by type.
So far the only approach that seems possible is to have each component store a ref for each of it's subcomponents, but this is tedious and kind of defeats the purpose of an HOC. Is there a generic way to access these sub-components?
A rough example of what I'm trying to do:
var HOC = (ComposedComponent) => {
return React.createClass({
componentDidMount: function() {
const subComponents = this.composedComponent.subComponents; // Something like this would be nice
const menuItems = subComponents.filter(() => {
// figure out a way to identify components of a certain type
});
this.applyRovingTabIndex(menuItems);
},
render: function() {
return (
<ComposedComponent
ref={(c) => { this.composedComponent = c }}
{...this.props} />
);
}
});
};
The tabIndex manipulation need not be done in the HOC, rather it can be done in the Parent component that renders all the HOCs. Because all you need is to determine which sub component is clicked and adjust the selected state on the Parent component. This selected state can then be propagated back to the sub components who compare their index with selected index and assign tabIndex accordingly.
You can send the respective props to determine whether the current ComposedComponent is selected or not by passing an onClick event handler all the way. Then in your sub component you can access tabIndex using this.props.tabIndex and render your parent div as
<div tabIndex={this.props.tabIndex}> </div>
The code below is almost like pseudo code to give an idea. If you feel that this does not solve your requirement you can try out a Tab example worked out by an awesome developer at this link CODEPEN EXAMPLE
const HOC = (ComposedComponent) => {
return class extends React.Component {
render (
<ComposedComponent
tabIndex={this.props.selected === this.props.index ? "0" : "-1"}
{...this.props}
/>
)
}
}
class Parent extends React.Component {
state = {
selected: 0
}
// Set the current selection based on the currentSelection argument
// that is bound to the function as it is sent along to Props
adjustTabIndices = (currentSelection) => (event) => {
this.setState({selection: currentSelection})
}
render {
return (
<div>
{
// These are your various MenuItem components that
// you want to compose using HOC
[MenuItem1, MenuItem2, MenuItem3].map(index => {
const MenuItem = HOC(MenuItem1);
return (
<MenuItem
key={index}
onClick={this.adjustTabIndices(index)}
selection={this.state.selected}
index={index}
/>
)
})
}
</div>
)
}
}

Access parent context when using this.props.children in React

Given the following, is it possible to access the parent context rather than the containers from a child (non-react component) element?
The example logs container, ideally it would log parent. I would like for Parent to be self contained, not to have it's state managed by its container.
var Container = React.createClass({
getInitialState: function () {
return {
context: 'container'
}
},
render: function () {
return (
<Parent>
<a href="#" onClick={function () {console.log(this.state.context);}.bind(this)}>click me</a>
</Parent>
);
}
});
var Parent= React.createClass({
getInitialState: function () {
return {
context: 'parent'
}
},
render: function () {
return (
<div>
{this.props.children}
</div>
);
}
});
If there is another pattern for handling this, please share as well.
Note: To be clear, I understand how the this keyword works and why the above example works as it does. The example is simply meant to illustrate the problem.
You can import some React helpers for that:
var React = require('react')
...
var children = React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
context: this.state.context
})
})
render() {
return <div>{ children }</div>
}
...
Then your child component will have this.props.context which will be the string 'parent', but this must be a React component, as this needs to refer to the component using the parent prop
var YourComponent = React.createClass({
render() {
return (
<a href="#" onClick={() => console.log(this.props.context)}>
click me
</a>
)
}
})
------
var Parent = require('./Parent')
var YourComponent = require('./YourComponent')
...
render() {
return <Parent><YourComponent /></Parent>
}
I do not know about the first part of your question, but since you commented about dynamically creating components, here's how I do it:
You can set a state variable in the constructor of the class and its parent:
if (typeof this.state == 'undefined') {
this.state = {
componentsToRender: <div></div>
};
}
Then in the parent component, in the componentDidMount() function:
var componentsToRender = [];
if ([conditional]) {
// some logic so you know which component to render
componentsToRender.push(<customChildComponentToRender key={} />);
}
else {
componentsToRender.push(<otherComponentToRender key={} />);
}
this.setState({
componentsToRender: <div>{componentsToRender}</div>
});
Make sure to put a key (lines 4 and 7 of the second code block) or React will scream at you.
In response to your initial question, I would watch this video from the ReactJS Conference 2015 to get more of the heart behind a container. After hearing what the guys at Facebook say (who have radical views on containers!), you might want to rethink the design to make your container more of a data layer.
I would check out THIS article from the react website. I think it might give you some intuition on solving your problem.
As a general rule of thumb, I try and only use this.state to handle internal UI state of a specific component. Everything else is passed via props. If you're needing the full context of a component, I would either pass it as a prop or checkout something like flux or redux which will help you manage state between components.

How to get the state of a React app?

So, I've built up this React app with some state. I want to know what that state is, so I can save it to localStorage and let state carry from one session to another. Basically, the app is pretty complex and I don't want people to lose their "place" just because the closed it and opened it again later.
Reading through the React docs though, I don't see anything that references accessing a component's state from outside of React.
Is this possible?
You should never ever try to get a state from a component as a component should always be representing and not generate state on its own. Instead of asking for the component's state, ask for the state itself.
That being said, I definitely see where you're coming from. When talking about React, the term "state" seems to be pretty ambiguous indeed.
I don't see anything that references accessing a component's state
from outside of React.
Once and for all, here's the difference:
Local state, shouldn't persist: this.state, this.setState et al. Local state lives only within the component and will die once the component dies.
Global state, can be persisted: this.props.passedState. Global state is only passed to the component, it can not directly modify it. The view layer will adjust to whatever global state it got passed.
Or in simple:
this.state means local state, won't get persisted.
this.props.state means passed state, could be persisted, we just don't know and we don't care.
Example
The following example uses stuff of Babel's stage-1 Preset
React is all about giving a display representation of a data structure. Let's assume we have the following object to visibly represent:
let fixtures = {
people: [
{
name: "Lukas"
},
{
name: "fnsjdnfksjdb"
}
]
}
We have an App component in place, that renders Person components for every entry in the people array.
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired
}
render() {
return (
<li>
<input type="text" value={this.props.name} />
</li>
);
}
}
class App extends Component {
static propTypes = {
people: PropTypes.array.isRequired
}
render() {
let people = this.people.map(person => {
<Person name={person.name} />
});
return (
<ul>
{people}
</ul>
);
}
}
ReactDOM.render(
<App people={fixtures} />,
document.getElementById('yourid')
);
Now, we will implement focus functionality. There are 2 options:
We don't care about which person was focused the last time the user used the app, so we use local state.
We do care about which person was focused the last time the user used the app, so we use global state.
Option 1
The task is simple. Just adjust the People component so that it knows (and rerenders) once the focus changed. The original data structure won't be changed and the information whether a component is focused or not is
merely client side information that will be lost once the client is closed/reset/whatever.
State is local: We use component.setState to dispatch changes to local state
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired
}
constructor(...args) {
super(...args);
this.state = {
isFocused: false
}
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
static propTypes = {
name: PropTypes.string.isRequired
}
onFocus() {
this.setState({
isFocused: true;
});
}
onBlur() {
this.setState({
isFocused: false;
});
}
render() {
let borderColor = this.state.isFocused ? '#ff0' : '#000';
style = {
border: `1px solid ${borderColor}`
}
return (
<li>
<input
style={style}
type="text"
value={this.props.name}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</li>
);
}
}
Option 2
We actually want to persist the focused element to whatever store (eg. backend) we have, because we care about the last state. State is global: React components receive only props as "state", even granular information as whether an element is focused. Persist and feed global state to the app and it will behave accordingly.
function setFocus(index) {
fixtures.people[index].isFocused = true;
render();
}
function clearFocus(index) {
fixtures.people[index].isFocused = false;
render();
}
function render() {
ReactDOM.render(
<App people={fixtures} />,
document.getElementById('yourid')
);
}
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired,
isFocused: PropTypes.bool,
index: PropTypes.number.isRequired
}
static defaultProps = {
isFocused: false
}
constructor(...args) {
super(...args);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
static propTypes = {
name: PropTypes.string.isRequired
}
onFocus() {
setFocus(this.props.index);
}
onBlur() {
clearFocus(this.props.index);
}
render() {
let borderColor = this.props.isFocused ? '#ff0' : '#000';
style = {
border: `1px solid ${borderColor}`
}
return (
<li>
<input
style={style}
type="text"
value={this.props.name}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</li>
);
}
}
class App extends Component {
static propTypes = {
people: PropTypes.array.isRequired
}
render() {
let people = this.people.map((person, index) => {
<Person name={person.name} index={index} isFocused={person.isFocused} />
});
return (
<ul>
{people}
</ul>
);
}
}
render();
I think the solution to your problem really lies in how your application is modelled.
Ideally what you would need (depending on complexity) would be a single (flux/redux) store upon which you could subscribe to changes, if it diffs then save it to localStorage.
You would then need to determine a way to bootstrap this data into your single store.
Their is no API per se (that I know of) to do specifically what you want.
Don't try to get the state from outside of React -- pass the state from React to wherever it needs to be.
In your case, I would do this componentDidUpdate.
var SampleRootComponent = React.createClass({
componentDidUpdate: function() {
localStorage.setItem(JSON.stringify(this.state))
}
})
You're right in that there is no way to get the state from outside; you have to set it from React. But there is no reason to view this as a bad thing -- just call an external function from inside of React if you need to pass the info to something else, as shown. No functionality is lost.

how do I pass data upwards in reactjs?

Say I have an app with some path app -> taskbar -> button -> modal -> textfield. I want the textfield to be some setting a user inputs and is used elsewhere in the app, maybe app -> differentButton -> differentModal displays this user setting for example
I'm brand new to react, but it seems data can only go downwards through props, right? Is it expected that I store this state externally in a db? I don't mind doing that, but it seems like there should be an easy way to do this that I'm overlooking?
You can store the state in the parent component and pass not only the value, but also the function that modifies the value to the child. Eg:
const App = React.createClass({
getInitialState () {
return {
name: 'Dave'
};
},
render () {
return (
<div>
<MyComponent name={this.state.name} changeName={this.onChangeName} />
</div>
)
},
onChangeName (name) {
this.setState({ name });
}
});
const MyComponent = React.createClass({
propTypes: {
name: React.PropTypes.string,
changeName: React.PropTypes.func.isRequired
},
render () {
return (
<div>
<input value={this.props.name} onChange={this.props.changeName} />
</div>
);
}
});
The canonical way would be to pass a callback function from a component which is higher up in the view hierarchy through props.
That higher ordered component would encapsulate the state that you wish to modify, triggering a re-render of the sub-tree.
In your case, it looks like you would have to use App as the shared parent component for sharing state. So in App, you'd probably have a function such as:
handleTextInput: function(text) {
// handle the text input here (set some state, make an ajax call, etc)
},
And App's render function might look like this:
render: function() {
return (
<TaskBar onTextSubmit={this.handleTextInput} />
);
}
In your TaskBar component, you'd pass the callback down to Button, and so on.
Finally, in your modal component, you'd have a render function like:
render: function() {
return (
<form onSubmit={this.props.onTextSubmit}>
...
</form>
);
}
Of course, this is can quickly get quite clumsy if you have a deeply nested hierarchy, so a better approach would be to use a library for state management such as Redux.

Accessing methods on children

I Have a component that receives children and renders those children
component = React.createClass({
...some JSX
{this.props.children}
...some JSX
)}
At some point I need to loop around the children and call a method that they expose. However, children are not object instances, but rather React representations.
In essence I want to do something like this:
var children = this.props.children
React.Children.forEach(children, function(child) {
child.someAction()
})
What's the best way of achieving this?
There is one way that I came across on IRC and it involved cloning the children then accessing them by ref. Though it seems kind of convoluted.
// in JSX
{
newChildren = React.Children.map(children, function(child) {
return React.CloneElement(child, {ref: child.ref})
})
}
Then do
childRefs = this.props.children.map(function(child) {
return child.ref
})
var self = this;
childRefs.forEach(function(ref){
self.refs[ref].someAction()
})
Though, this feels like I'm working against React.
In generel you should always try to use to outside-in approach and think of data as immutable.
Providing all children with a shouldValidate flag as a prop and then make all children implement the componentWillReceiveProps function to do the validation, would be the preferred way as I see it.
Components should in my opinion never expose functions/properties to be called from outside as this goes against the idea of React.
An jsx example:
Parent
render: function() {
return this.children.map(Child => {
return <Child shouldValidate={this.state.submitting}/>
});
}
onSubmit: function() {
this.setState({
submitting: true
})
}
Child
componentWillReceiveProps: (nextProps) => {
if (this.props.shouldValidate) {
validate();
}
}

Resources