Within my component, how can I access the name of the parent component it is nested inside?
So if my render is thus:
ReactDOM.render(
<RadialsDisplay data={imagedata}/>,
document.getElementById('radials-1')
);
How can I retrieve the id name #radials-1 from within the component itself?
It probably makes the most sense to pass it as a property, but if you really need to get it programmatically, and from inside the component, you can wait for the component to mount, find its DOM node, and then look at its parent.
Here's an example:
class Application extends React.Component {
constructor() {
super();
this.state = { containerId: "" };
}
componentDidMount() {
this.setState({
containerId: ReactDOM.findDOMNode(this).parentNode.getAttribute("id")
});
}
render() {
return <div>My container's ID is: {this.state.containerId}</div>;
}
}
ReactDOM.render(<Application />, document.getElementById("react-app-container"));
Working demo: https://jsbin.com/yayepa/1/edit?html,js,output
If you do this a lot, or want to be really fancy, you could utilize a higher-order component:
class ContainerIdDetector extends React.Component {
constructor() {
super();
this.state = { containerId: "" };
}
componentDidMount() {
this.setState({
containerId: ReactDOM.findDOMNode(this).parentNode.getAttribute("id")
});
}
render() {
if (!this.state.containerId) {
return <span />;
} else {
return React.cloneElement(
React.Children.only(this.props.children),
{ [this.props.property]: this.state.containerId }
);
}
}
}
ContainerIdDetector.propTypes = {
property: React.PropTypes.string.isRequired
}
// Takes an optional property name `property` and returns a function. This
// returned function takes a component class and returns a new one
// that, when rendered, automatically receives the ID of its parent
// DOM node on the property identified by `property`.
function withContainerId(property = "containerId") {
return (Component) => (props) =>
<ContainerIdDetector property={property}>
<Component {...props} />
</ContainerIdDetector>
}
Here, withContainerId is a function that takes an argument called property and returns a new function. This function can take a component type as its only argument, and returns a higher-order component. When rendered, the new component will render the passed component, with all its original props, plus an additional prop specifying the parent container's ID on the property specified by the property argument.
You can use them with ES7 decorators (as currently implemented) if you wish, or via a regular function call:
#withContainerId()
class Application extends React.Component {
render() {
return <div>My containers ID is: {this.props.containerId}</div>;
}
}
// or, if you don't use decorators:
//
// Application = withContainerId()(Application);
ReactDOM.render(<Application />, document.getElementById("react-app-container"));
Working demo: https://jsbin.com/zozumi/edit?html,js,output
Related
I'm programatically validating an email and password inputs for simple login, here is the function that call other function that validate the email.
handleLogin(event) {
this.validateEmail();
this.validatePassword();
if (this.state.emailValid === 'error' || this.state.passwordValid === 'error') {
alert('invalid form');
return;
};
const email = ReactDOM.findDOMNode(this.refs.email).value;
const password = ReactDOM.findDOMNode(this.refs.password).value;
const creds = { email: email, password: password }
this.props.onLoginClick(creds)
}
Notice that first than all I'm calling the validateEmail() function which modifies the store that indicates if the input is correct, here's the validateEmail() source code:
validateEmail() {
const email = ReactDOM.findDOMNode(this.refs.email).value;
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
if (!validEmail) {
this.setState({
emailValid: 'error'
});
return;
}
this.setState({
emailValid: 'success'
});
}
But in the if statement the state.emailValid has not been yet updated, this is a delay in the state modifying, so the alert() is not displayed. How to get the updated state correctly?
Thanks
The thing to note here is that setState is asynchronous. It will not update the state until everything else that is synchronous in your handleLogin method has completed.
With React I like to use state as a single source of truth as often as I can. In the example above you have the html element as a source of truth and state. By changing your components to be controlled by the react state, you can validate your forms on each keystroke.
Forms and Controlled Components
Start by keeping the state of your input in state
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
emailValid: true,
};
// we bind the function in case we want to
// control text in child component
this.emailChange = this.handleEmailChange.bind(this);
}
emailChange(event) {
this.setState({email: event.target.value});
}
render() {
<textarea value={this.state.email} onChange={this.emailChange} />
}
}
Now whenever you type the state of your html input is handled in react. This will enable you to more easily check its validity. We can do this by adding another method to our class:
class LoginForm extends React.Component {
// ...all the stuff from above
validateEmail() {
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
if (!validEmail) {
// Object.assign just ensures immutability
this.setState(Object.assign({}, this.state, {
emailValid: false
}))
} else {
// If using babel, this is ensure immutable also
this.setState({
...state,
emailValid: true
})
}
}
// or....
validateEmail() {
let validEmail = /^.+([.%+-_]\w+)*#\w+([.-]\w+)*\.\w+([-.]\w+)*$/.test(email);
this.setState({...state, emailValid: validEmail})
}
// ...render method
}
The validation will now occur on each keystroke. When you need to submit your form all you need to do is check the state if the data is valid and dont need to reference the dom. You can send the data from state.
I'm new to React so I've tried to show as much code as possible here to hopefully figure this out! Basically I just want to fill form fields with properties from an object that I fetched from another API. The object is stored in the autoFill reducer. For example, I would like to fill an input with autoFill.volumeInfo.title, where the user can change the value before submitting if they want.
I used mapDispatchtoProps from the autoFill action creator, but this.props.autoFill is still appearing as undefined in the FillForm component. I'm also confused about how to then use props again to submit the form. Thanks!
My reducer:
import { AUTO_FILL } from '../actions/index';
export default function(state = null, action) {
switch(action.type) {
case AUTO_FILL:
return action.payload;
}
return state;
}
Action creator:
export const AUTO_FILL = 'AUTO_FILL';
export function autoFill(data) {
return {
type: AUTO_FILL,
payload: data
}
}
Calling the autoFill action creator:
class SelectBook extends Component {
render() {
return (
....
<button
className="btn btn-primary"
onClick={() => this.props.autoFill(this.props.result)}>
Next
</button>
);
}
}
....
function mapDispatchToProps(dispatch) {
return bindActionCreators({ autoFill }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(SelectBook);
And here is the actual Form where the issues lie:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { createBook } from '../actions/index;
class FillForm extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
onSubmit(props) {
this.props.createBook(props)
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { fields: { title }, handleSubmit } = this.props;
return (
<form {...initialValues} onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<input type="text" className="form-control" name="title" {...title} />
<button type="submit">Submit</button>
</form>
)
}
export default reduxForm({
form: 'AutoForm',
fields: ['title']
},
state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
}), {createBook})(FillForm)
I think you're mixing up connect and reduxForm decorators in the actual form component. Currently your code looks like this (annotations added by me):
export default reduxForm({
// redux form options
form: 'AutoForm',
fields: ['title']
},
// is this supposed to be mapStateToProps?
state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
}),
/// and is this mapDispatchToProps?
{createBook})(FillForm)
If this is the case, then the fix should be as simple as using the connect decorator as it should be (I also recommend separating this connect props to their own variables to minimize confusions like this):
const mapStateToProps = state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
})
const mapDispatchToProps = { createBook }
export default connect(mapStateToProps, mapDispatchToProps)(
reduxForm({ form: 'AutoForm', fields: ['title'] })(FillForm)
)
Hope this helps!
Looking around the next I could not find the answer: How do I dynamicly include a file, based on prop change per say: here some sudo code to intrastate what I'm trying to do!
class Test extends React.Component {
constructor(props){
super(props)
this.state = { componentIncluded: false }
includeFile() {
require(this.props.componetFileDir) // e.g. ./file/dir/comp.js
this.setState({ componentIncluded: true });
}
render() {
return(
<div className="card">
<button onClick={this.includeFile}> Load File </button>
{ this.state.componentIncluded &&
<this.state.customComponent />
}
</div>
)
}
}
so this.props.componetFileDir has access to the file dir, but I need to dynamically include it, and can't really do require() as its seems to running before the action onClick get called. Any help would be great.
Em, Your code looks a bit wrong to me. So I created a separate demo for dynamic inject components.
While in different situation you can use different React lifecycle functions to inject your component. Like componentWillReceiveProps or componentWillUpdate.
componentDidMount() {
// dynamically inject a Button component.
System.import('../../../components/Button')
.then((component) => {
// update the state to render the component.
this.setState({
component: component.default,
});
});
}
render() {
let Button = null;
if (this.state.component !== null) {
Button = this.state.component;
}
return (
<div>
{ this.state.component !== null ? <Button>OK</Button> : false }
</div>
);
}
After you edited your code, it should be something similar to below:
class Test extends React.Component {
constructor(props){
super(props)
this.state = { customComponent: null }
this.includeFile = this.includeFile.bind(this);
}
includeFile() {
System.import(this.props.componetFileDir)
.then((component) => {
this.setState({ customComponent: component.default });
});
}
render() {
return(
<div className="card">
<button onClick={this.includeFile}> Load File </button>
{
this.state.customComponent
}
</div>
)
}
}
I have a component with an input-element, i use defaultValue to set the initial value.
I want to focus that element and select the whole value initially, but it seems that the defaultvalue is not set when componentDidMount is called.
Do you have any tips?
i use window.setTimeout but i want to avoid that in my react-components:
public componentDidMount(): void {
if (this.props.focus) {
let tInput: HTMLInputElement = ReactDOM.findDOMNode(this).getElementsByTagName("input").item(0);
if (tInput) {
tInput.focus();
// FixMe: defaultValue is set too late by react so i cant set selection instantly
if (this.props.defaultValue) {
window.setTimeout(
() => {tInput.setSelectionRange(0, this.props.defaultValue.length); },
100
);
}
}
}
}
Works fine for me:
class MyComponent extends React.Component {
componentDidMount () {
const { myInput } = this.refs
myInput.focus()
myInput.select()
}
render () {
return (
<input type='text' defaultValue='Foobar' ref='myInput' />
)
}
}
I wouldn't use any reactDOM methods other than render/renderToString. These apis are "escape hatches" and usage is not recommended.
Given that there is not much examples about this, I am following the docs as best as I can, but the validation is not reactive.
I declare a schema :
import { Tracker } from 'meteor/tracker';
import SimpleSchema from 'simpl-schema';
export const modelSchema = new SimpleSchema({
foo: {
type: String,
custom() {
setTimeout(() => {
this.addValidationErrors([{ name: 'foo', type: 'notUnique' }]);
}, 100); // simulate async
return false;
}
}
}, {
tracker: Tracker
});
then I use this schema in my component :
export default class InventoryItemForm extends TrackerReact(Component) {
constructor(props) {
super(props);
this.validation = modelSchema.newContext();
this.state = {
isValid: this.validation.isValid()
};
}
...
render() {
...
const errors = this.validation._validationErrors;
return (
...
)
}
}
So, whenever I try to validate foo, the asynchronous' custom function is called, and the proper addValidationErrors function is called, but the component is never re-rendered when this.validation.isValid() is supposed to be false.
What am I missing?
There are actually two errors in your code. Firstly this.addValidationErrors cannot be used asynchronously inside custom validation, as it does not refer to the correct validation context. Secondly, TrackerReact only registers reactive data sources (such as .isValid) inside the render function, so it's not sufficient to only access _validationErrors in it. Thus to get it working you need to use a named validation context, and call isValid in the render function (or some other function called by it) like this:
in the validation
custom() {
setTimeout(() => {
modelSchema.namedContext().addValidationErrors([
{ name: 'foo', type: 'notUnique' }
]);
}, 100);
}
the component
export default class InventoryItemForm extends TrackerReact(Component) {
constructor(props) {
super(props);
this.validation = modelSchema.namedContext();
}
render() {
let errors = [];
if (!this.validation.isValid()) {
errors = this.validation._validationErrors;
}
return (
...
)
}
}
See more about asynchronous validation here.