Accessing refs in a stateful component doesn't work in React? - reactjs

I'm currently trying to refactor the simple-todos tutorial for meteor using presentational and container components, but ran into a problem trying to access the refs of an input in a functional stateless component. I found out that to access refs, you have to wrap the component in a stateful component, which I did with the input.
// import React ...
import Input from './Input.jsx';
const AppWrapper = (props) => {
// ... more lines of code
<form className="new-task" onSubmit={props.handleSubmit}>
<Input />
</form>
}
import React, { Component } from 'react';
This Input should be stateful because it uses class syntax, at least I think.
export default class Input extends Component {
render() {
return (
<input
type="text"
ref="textInput"
placeholder="Type here to add more todos"
/>
)
}
}
I use refs to search for the input's value in the encompassing AppContainer.
import AppWrapper from '../ui/AppWrapper.js';
handleSubmit = (event) => {
event.preventDefault();
// find the text field via the React ref
console.log(ReactDOM.findDOMNode(this.refs.textInput));
const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
...
}
The result of the console.log is null, so is my Input component not stateful? Do I need to set a constructor that sets a value for this.state to make this component stateful, or should I just give up on using functional stateless components when I need to use refs?

or should I just give up on using functional stateless components when I need to use refs?
Yes. If components need to keep references to the elements they render, they are stateful.
Refs can be set with a "callback" function like so:
export default class Input extends Component {
render() {
// the ref is now accessable as this.textInput
alert(this.textInput.value)
return (
<input
type="text"
ref={node => this.textInput = node}
placeholder="Type here to add more todos"
/>
)
}
}

You have to use stateful components when using refs. In your handleSubmit event, you're calling 'this.refs' when the field is in a separate component.
To use refs, you add a ref to where you render AppWrapper, and AppWrapper itself must be stateful.
Here's an example:
AppWrapper - This is your form
class AppWrapper extends React.Component {
render() {
return (
<form
ref={f => this._form = f}
onSubmit={this.props.handleSubmit}>
<Input
name="textInput"
placeholder="Type here to add more todos" />
</form>
);
}
};
Input - This is a reusable textbox component
const Input = (props) => (
<input
type="text"
name={props.name}
className="textbox"
placeholder={props.placeholder}
/>
);
App - This is the container component
class App extends React.Component {
constructor() {
super();
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
const text = this._wrapperComponent._form.textInput.value;
console.log(text);
}
render() {
return (
<AppWrapper
handleSubmit={this.handleSubmit}
ref={r => this._wrapperComponent = r}
/>
);
}
}
http://codepen.io/jzmmm/pen/BzAqbk?editors=0011
As you can see, the Input component is stateless, and AppWrapper is stateful. You can now avoid using ReactDOM.findDOMNode, and directly access textInput. The input must have a name attribute to be referenced.
You could simplify this by moving the <form> tag into the App component. This will eliminate one ref.

Related

Passing state-based data between pages in React

I am trying to create a multiple page form in React, and I have the basic wireframe set up. I am trying to export a user's Name from one page to the next, but the user will change depending on who has logged in. I've been in google purgatory for a while trying figure out how to grab a specific state-based value out of a component to be available on another page. In my code below, I'm exporting the whole component to render on the App.js page. However, I'd also like to grab just the {userName} to render within another component.
import React, { Component } from 'react'
class Intro extends Component {
state = { userName: ''}
handleChange = (event) => this.setState({ userName: event.target.value })
render() {
const { userName } = this.state
return (
<div id='intro'>
<form>
<FieldGroup
id='nameArea'
value={this.state.value}
onChange={this.handleChange}
/>
<input id='submit' type='submit' value='Submit' /> .
</form>
</div>
)
}
}
export default Intro
To put it simply, you can't. This is where tools like redux come into play. Here's an example using React's new context API:
const UserContext = React.createContext('');
class Intro extends Component {
handleChange = (event) => {
this.props.updateUserName(event.target.value);
}
render() {
const { userName } = this.props
return (
<div id='intro'>
<form>
<input
id='nameArea'
value={userName}
onChange={this.handleChange}
/>
<input id='submit' type='submit' value='Submit' /> .
</form>
</div>
)
}
}
// only doing this to shield end-users from the
// implementation detail of "context"
const UserConsumer = UserContext.Consumer
class App extends React.Component {
state = { userName: '' }
render() {
return (
<UserContext.Provider value={this.state.userName}>
<React.Fragment>
<Intro userName={this.state.userName} updateUserName={(userName) => this.setState({userName})} />
<UserConsumer>
{user => <div>Username: {JSON.stringify(user)}</div>}
</UserConsumer>
</React.Fragment>
</UserContext.Provider>
)
}
}
See my updated codesandbox here.
Most of the time, when you need data on another component, the solution is store the date higher in your component.
As the others already said, the most easy way is trying to pass your state via props from your higher order component to the childs.
Another approach would be to use Redux for your state management. This gives you one global state store accessible from any component.
Third you can try to use the react context api.

Where in redux-react app would I include my stateful presentational component? In components or containers folder?

Search Component:
import React from "react";
import SearchResults from "../SearchResults";
import PropTypes from "prop-types";
class Search extends React.Component {
state = {
value: ""
};
handleChange = event => {
let value = event.target.value;
this.setState({ value });
this.props.performSearch(value);
};
handleSubmit = event => {
event.preventDefault();
};
render() {
return (
<div>
<h1>The Guardian Search App</h1>
<form onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
</form>
<div>
<SearchResults articles={this.props.articles} />
</div>
</div>
);
}
}
Search.propTypes = {
performSearch: PropTypes.func,
articles: PropTypes.array
};
export default Search;
Search Container:
import React from "react";
import Search from "../../components/Search";
import { API_KEY } from "../../../config";
import fetchArticles from "../../api";
class SearchContainer extends React.Component {
state = {
articles: []
};
performSearch = event => {
return fetchArticles(event).then(data =>
this.setState({ articles: data.response.results })
);
};
render() {
return (
<Search
performSearch={this.performSearch}
articles={this.state.articles}
/>
);
}
}
export default SearchContainer;
I am currently trying to get my head around redux so transitioning this into react-redux version. I've got a Search Container whereby I am doing mapStateToProps and will soon write mapDispatchToProps as well. But if my Search component also includes state, do I then do another Search Container to then map its state to props?
The state required in your Search component is directly linked and required by the input element that you have rendered as a child. Therefore, the value state in the Search component should stay within the component and not be associated with Redux.
There is no "correct" way of doing this, mainly preference and design pattern. Since you have state in the Search component that you don't want to be associated with Redux, I would hook the SearchContainer component into your Redux store for providing the array of article objects which can then be passed to the base Search component as a prop and leave that component entirely unaware that Redux even exists.

Add a class to child component when parent component state changes

This is my first time attempting to build with React. I typically write UI interaction with jQuery or plain old JS. I simply want a text field which when there is text entered has a class added to it so that I can style it differently to the default state. Note I only want this class adding when there is at least one character entered, not when the field is focused.
I already have an onChange function in the child component which is used to change the state of 'textEntered' but I can't figure out how to make use of this state in the child component to add a class.
Here is my parent component
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import TextInput from './components/TextInput/TextInput';
export default class Form extends Component {
constructor(props) {
super(props);
this.state = {
textEntered: '',
completed: false,
};
}
render() {
return (
<div>
<TextInput
placeholderText={'Title'}
updateText={textEntered => this.setState({ textEntered })}
completed={this.state.completed}
/>
</div>
);
}
}
ReactDOM.render(<Form />, document.getElementById('react-create-form'));
And here is the child component
import React, { PropTypes } from 'react';
const TextInput = props => (
<div>
<input
type={props.type}
placeholder={props.placeholderText}
onChange={e => props.updateText(e.target.value)}
data-completed={props.completed}
/>
</div>
);
TextInput.propTypes = {
type: PropTypes.string,
placeholderText: PropTypes.string,
updateText: PropTypes.func,
completed: PropTypes.bool,
};
TextInput.defaultProps = {
type: 'text',
};
export default TextInput;
Pass the class name from parent component, and also put the check on that. If text field has atleast one character then pass the actual class name otherwise blank string.
Since you are storing the value of text field inside state of parent component so put the condition like this:
customClass = {this.state.textEntered.length ? 'actualClassName': ''}
Code:
<div>
<TextInput
customClass={this.state.textEntered.length ? 'actualClassName': ''}
placeholderText={'Title'}
updateText={textEntered => this.setState({ textEntered })}
completed={this.state.completed}
/>
</div>
Inside child component apply this customClass.
const TextInput = props => (
<div>
<input
type={props.type}
className={props.customClass}
placeholder={props.placeholderText}
onChange={e => props.updateText(e.target.value)}
data-completed={props.completed}
/>
</div>
);
Note: Another way is, pass the value in props instead of passing the class name and put the condition inside child component directly.

Get another component input value React js

I have two components one is app component and other one is sidebar component i have been using input field in side bar and i want to get the value of that input field in my app component on click how this could be possible ?
You can try lifting the state up.
Create a new component that will contain your two components. In that new component, create a function that you will pass as props inside the sidebar component.
class ContainerComponent extends React.Component {
constructor() {
super();
this.state = {
valueThatNeedsToBeShared: ''
}
}
handleChange(e) {
this.setState({valueThatNeedsToBeShared: e.target.value})
}
render() {
return (
<div>
<AppComponent value={this.state.valueThatNeedsToBeShared} />
<SidebarComponent handleChange={this.handleClick.bind(this)} value={this.state.valueThatNeedsToBeShared} />
</div>
)
}
}
const SidebarComponent = ({handleChange, value}) => <aside>
<input value={value} onChange={handleChange} />
</aside>
const AppComponent = ({value}) => <div>
value from sidebar: {value}
</div>
In pure react it is possible by adding callback from one component and use it into parent component to change their state and then send input value from parent's state to props of your second component

React way to "setState" in a pure ES2015 function

React newb here. I have a pure function that returns a form (presentation component). In this form I need to handle onChange events for those text fields that are controlled. FWIU, I need to this.setState(...) in my onChange event handlers. However due to this being a pure function, I don't have access to this.setState(). Is there a nice way to set the state on these onChange events in a ES2015 function? I'm also using redux if this helps. Example code:
import React, {PropTypes} from 'react'
const ApplicationForm = ({submitHandler, person}) => (
<form onSubmit={e => submitHandler(e)}>
<div>
<label htmlFor="firstName">First Name:</label>
<input type="text" name="firstName" onChange={e => setState(e.target.value)} value={person.firstName || ''}/>
</div>
...
</form>
)
That is a Stateless Function, there is no state to set
If you're using redux, you probably want to trigger a redux action in the onChange, passing the new value as an argument, and have the action update the value of firstName in the redux store for person.firstName
I would recommend taking a look at redux-form to reduce a bunch of boilerplate
You can actually use setState in something that looks like a functional component, but it's pretty hacky. I imagine this method is something only people who really can't stand using the this keyword and class syntax would ever use. Still, I think it's kind of fun.
Here's how you might write an input that changes another element in a normal way using this and class syntax:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {text: "Hello, world"};
}
handleChange = (event) => {
this.setState({text: event.target.value});
}
render() {
return (
<div>
<input value={this.state.text} onChange={this.handleChange} />
<h1>{this.state.text}</h1>
</div>
)
}
}
And here's how you could create the same effect without this and class syntax:
function App() {
"use strict";
const obj = {
state: {text: "Hello, world"},
__proto__: React.Component.prototype
};
obj.handleChange = function (event) {
obj.setState({text: event.target.value});
};
obj.render = function () {
return (
<div>
<input value={obj.state.text} onChange={obj.handleChange} />
<h1>{obj.state.text}</h1>
</div>
);
};
return obj;
}
The trick is to make the App function inherit from React.Component by setting the dunder proto property of the object App returns to React.Component's prototype so that it can use setState.
Here's a codepen if you want to play around with the code.
Stateless functional components can't have state... because they're stateless. If you want to have event handlers to call and state to set, you will need to create a component class, either via React.createClass or by using ES6 classes.
You can use react hooks to achieve what you want.
If you write a function component and you want to add some sate to your function, previously you had to change your function into a class. But now you can use react hooks to create your state in your functional component.
EX:- We write class components with state as below
class Foo extends Component {
constructor(props) {
super(props);
this.state = {
age: 20
};
}
now we can achieve above code in function component as followed
import React, { useState } from 'react';
function Foo() {
const [age, setAge] = useState(20);
Refer this document for more details - https://reactjs.org/docs/hooks-state.html
With React Hooks, we now have state usability extended to functional components as well.
To use this, we can import {useState} from React and pass default value into its arguments.
import React, {PropTypes, useState} from 'react'
const ApplicationForm = ({submitHandler, person}) => (
const [name, updateName]= useState(person.firstName);
<form onSubmit={e => submitHandler(e)}>
<div>`enter code here`
<label htmlFor="firstName">First Name:</label>
<input type="text" name="firstName" onChange={e => updateName(e.target.value)} value={name || ''}/>
</div>
...
</form>
)
More details about this can be found in the documentation for useState.

Resources