I'm teaching myself react with a super simple app that asks the user to type a word presented in the UI. If user enters it correctly, the app shows another word, and so on.
I've got it almost working, except for one thing: after a word is entered correctly, I need to clear the input element. I've seen several answers here about how an input element can clear itself, but I need to clear it from the component that contains it, because that's where the input is checked...
// the app
class AppComponent extends React.Component {
constructor() {
super();
this.state = {
words: ['alpha', 'bravo', 'charlie'],
index: 0
};
}
renderWordsource() {
const word = this.state.words[this.state.index];
return <WordsourceComponent value={ word } />;
}
renderWordinput() {
return <WordinputComponent id={1} onChange={ this.onChange.bind(this) }/>;
}
onChange(id, value) {
const word = this.state.words[this.state.index];
if (word == value) {
alert('yes');
var nextIndex = (this.state.index == this.state.words.count-1)? 0 : this.state.index+1;
this.setState({ words:this.state.words, index:nextIndex });
}
}
render() {
return (
<div className="index">
<div>{this.renderWordsource()}</div>
<div>{this.renderWordinput()}</div>
</div>
);
}
}
// the input component
class WordinputComponent extends React.Component {
constructor(props) {
this.state = { text:''}
}
handleChange(event) {
var text = event.target.value;
this.props.onChange(this.props.id, text);
}
render() {
return (
<div className="wordinput-component">
<input type="text" onChange={this.handleChange.bind(this)} />
</div>
);
}
}
See where it says alert('yes')? That's where I think I should clear the value, but that doesn't make any sense because it's a parameter, not really the state of the component. Should I have the component pass itself to the change function? Maybe then I could alter it's state, but that sounds like a bad idea design-wise.
The 2 common ways of doing this is controlling the value through state in the parent or using a ref to clear the value. Added examples of both
The first one is using a ref and putting a function in the child component to clear
The second one is using state of the parent component and a controlled input field to clear it
class ParentComponent1 extends React.Component {
state = {
input2Value: ''
}
clearInput1() {
this.input1.clear();
}
clearInput2() {
this.setState({
input2Value: ''
});
}
handleInput2Change(evt) {
this.setState({
input2Value: evt.target.value
});
}
render() {
return (
<div>
<ChildComponent1 ref={input1 => this.input1 = input1}/>
<button onClick={this.clearInput1.bind(this)}>Clear</button>
<ChildComponent2 value={this.state.input2Value} onChange={this.handleInput2Change.bind(this)}/>
<button onClick={this.clearInput2.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent1 extends React.Component {
clear() {
this.input.value = '';
}
render() {
return (
<input ref={input => this.input = input} />
);
}
}
class ChildComponent2 extends React.Component {
render() {
return (
<input value={this.props.value} onChange={this.props.onChange} />
);
}
}
ReactDOM.render(<ParentComponent1 />, document.body);
<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>
I had a similar issue: I wanted to clear a form which contained multiple fields.
While the two solutions by #noveyak are working fine, I want to share a different idea, which gives me the ability to partition the responsibility between parent and child: parent knows when to clear the form, and the items know how to react to that, without using refs.
The idea is to use a revision counter which gets incremented each time Clear is pressed and to react to changes of this counter in children.
In the example below there are three quite simple children reacting to the Clear button.
class ParentComponent extends React.Component {
state = {revision: 0}
clearInput = () => {
this.setState((prev) => ({revision: prev.revision+1}))
}
render() {
return (
<div>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<button onClick={this.clearInput.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent extends React.Component {
state = {value: ''}
componentWillReceiveProps(nextProps){
if(this.props.revision != nextProps.revision){
this.setState({value : ''});
}
}
saveValue = (event) => {
this.setState({value: event.target.value})
}
render() {
return (
<input value={this.state.value} onChange={this.saveValue} />
);
}
}
ReactDOM.render(<ParentComponent />, document.body);
<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>
EDIT:
I've just stumbled upon this beautifully simple solution with key which is somewhat similar in spirit (you can pass parents's revision as child's key)
Very very very simple solution to clear form is add unique key in div under which you want to render form from your child component key={new Date().getTime()}:
render(){
return(
<div className="form_first_step fields_black" key={new Date().getTime()}>
<Form
className="first_step">
// form fields coming from child component
<AddressInfo />
</div>
</Form>
</div>
)
}
Related
I have a small part of my new React app which contains a block of text, AllLines, split into line-by-line components called Line. I want to make it work so that when one line is clicked, it will be selected and editable and all other lines will appear as <p> elements. How can I best manage the state here such that only one of the lines is selected at any given time? The part I am struggling with is determining which Line element has been clicked in a way that the parent can change its state.
I know ways that I can make this work, but I'm relatively new to React and trying to get my head into 'thinking in React' by doing things properly so I'm keen to find out what is the best practice in this situation.
class AllLines extends Component {
state = {
selectedLine: 0,
lines: []
};
handleClick = (e) => {
console.log("click");
};
render() {
return (
<Container>
{
this.state.lines.map((subtitle, index) => {
if (index === this.state.selectedLine) {
return (
<div id={"text-line-" + index}>
<TranscriptionLine
lineContent={subtitle.text}
selected={true}
/>
</div>
)
}
return (
<div id={"text-line-" + index}>
<Line
lineContent={subtitle.text}
handleClick={this.handleClick}
/>
</div>
)
})
}
</Container>
);
}
}
class Line extends Component {
render() {
if (this.props.selected === true) {
return (
<input type="text" value={this.props.lineContent} />
)
}
return (
<p id={} onClick={this.props.handleClick}>{this.props.lineContent}</p>
);
}
}
In your case, there is no really simpler way. State of current selected Line is "above" line collection (parent), which is correct (for case where siblings need to know).
However, you could simplify your code a lot:
<Container>
{this.state.lines.map((subtitle, index) => (
<div id={"text-line-" + index}>
<Line
handleClick={this.handleClick}
lineContent={subtitle.text}
selected={index === this.state.selectedLine}
/>
</div>
))}
</Container>
and for Line component, it is good practice to use functional component, since it is stateless and even doesn't use any lifecycle method.
Edit: Added missing close bracket
'Thinking in React' you would want to give up your habit to grab DOM elements by their unique id ;)
From what I see, there're few parts missing from your codebase:
smart click handler that will keep only one line selected at a time
edit line handler that will stick to the callback that will modify line contents within parent state
preferably two separate components for the line capable of editing and line being actually edited as those behave in a different way and appear as different DOM elements
To wrap up the above, I'd slightly rephrase your code into the following:
const { Component } = React,
{ render } = ReactDOM
const linesData = Array.from(
{length:10},
(_,i) => `There goes the line number ${i}`
)
class Line extends Component {
render(){
return (
<p onClick={this.props.onSelect}>{this.props.lineContent}</p>
)
}
}
class TranscriptionLine extends Component {
constructor(props){
super(props)
this.state = {
content: this.props.lineContent
}
this.onEdit = this.onEdit.bind(this)
}
onEdit(value){
this.setState({content:value})
this.props.pushEditUp(value, this.props.lineIndex)
}
render(){
return (
<input
style={{width:200}}
value={this.state.content}
onChange={({target:{value}}) => this.onEdit(value)}
/>
)
}
}
class AllLines extends Component {
constructor (props) {
super(props)
this.state = {
selectedLine: null,
lines: this.props.lines
}
this.handleSelect = this.handleSelect.bind(this)
this.handleEdit = this.handleEdit.bind(this)
}
handleSelect(idx){
this.setState({selectedLine:idx})
}
handleEdit(newLineValue, lineIdx){
const linesShallowCopy = [...this.state.lines]
linesShallowCopy.splice(lineIdx,1,newLineValue)
this.setState({
lines: linesShallowCopy
})
}
render() {
return (
<div>
{
this.state.lines.map((text, index) => {
if(index === this.state.selectedLine) {
return (
<TranscriptionLine
lineContent={text}
lineIndex={index}
pushEditUp={this.handleEdit}
/>
)
}
else
return (
<Line
lineContent={text}
lineIndex={index}
onSelect={() => this.handleSelect(index)}
/>
)
})
}
</div>
)
}
}
render (
<AllLines lines={linesData} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
I am attempting to pull the value of Number from the props set in the Button class. And then render this value in the discover function. The class is correctly displaying the value of Number. However, the function is not displaying any value for Number.
I have been messing around with this for a while to get it to work. But I cannot find any solutions to my problem.
class Button extends React.Component {
constructor(props) {
super(props);
this.state = {
Number: "55"
};
}
render() {
return (
<div>
<p>Number: {this.state.Number}</p> //The value of Number is displayed on the page
</div>
);
}
};
const discover = (props) => {
return (
<div>
<Button />
<p>Number: {props.Number}</p> //The value of Number is not displayed
</div>
);
};
export default discover;
There are no error messages.
Expected result shown:
https://i.imgur.com/fr61SE0.png
Actual result shown:
https://i.imgur.com/MRE0Lsj.png
You want to keep discover and button in sync with eachother, but currently there isn't anything doing that. button is a child of discover with a local state. Instead of this make the parent have the state and it can then pass that down to the button component.
class Discover extends Component {
state = { number: 55 }
render() {
const { number } = this.state
return (
<div>
<Button number={number} />
<p>Number: {number}</p>
</div>
);
}
};
const Button = ({number) => {
return (
<div>
<p>Number: {number}</p>
</div>
);
}
};
export default Discover;
Here's a live example for you to play with
Your discover is a functional component and you are not passing anything to your component and in your button component, you are setting state that is the reason behind your output. try this.
class Button extends React.Component {
constructor(props) {
super(props);
this.state = {
Number: "55"
};
}
render() {
return (
<div>
<p>Number: {this.state.Number}</p> //The value of Number is displayed on the page
<discover {...this.state} />
</div>
);
}
};
const discover = (props) => {
return (
<div>
<p>Number: {props.Number}</p> //The value of Number is not displayed
</div>
);
};
export default Button;
now you will get your desire output
I'm not sure where you are calling the Discover component, but you would need to pass the number down as a prop to the Discover component in order to get it to render.
class Button extends React.Component {
constructor(props) {
super(props);
this.state = {
Number: "55"
};
}
render() {
return (
<div>
<p>Number: {this.state.Number}</p> //The value of Number is displayed on the page
<Discover Number={this.state.Number}/> // we are passing Number as a prop to the Discover component
</div>
);
}
};
const Discover = (props) => {
return (
<div>
<Button />
<p>Number: {props.Number}</p> //The value of Number is not displayed
</div>
);
};
export default Discover;
I'd also capitalize your custom react components like Discover.
Why do components in react need to be capitalized?
I'm currently following this and I did get it to work. But I would like to know if there is a way to stop the Query Render from reloading the data when calling this.setState(). Basically what I want is when I type into the textbox, I don't want to reload the data just yet but due to rendering issues, I need to set the state. I want the data to be reloaded ONLY when a button is clicked but the data will be based on the textbox value.
What I tried is separating the textbox value state from the actual variable passed to graphql, but it seems that regardless of variable change the Query will reload.
Here is the code FYR.
const query = graphql`
query TestComponentQuery($accountId: Int) {
viewer {
userWithAccount(accountId: $accountId) {
name
}
}
}
`;
class TestComponent extends React.Component{
constructor(props){
super(props);
this.state = {
accountId:14,
textboxValue: 14
}
}
onChange (event){
this.setState({textboxValue:event.target.value})
}
render () {
return (
<div>
<input type="text" onChange={this.onChange.bind(this)}/>
<QueryRenderer
environment={environment}
query={query}
variables={{
accountId: this.state.accountId,
}}
render={({ error, props }) => {
if (error) {
return (
<center>Error</center>
);
} else if (props) {
const { userWithAccount } = props.viewer;
console.log(userWithAccount)
return (
<ul>
{
userWithAccount.map(({name}) => (<li>{name}</li>))
}
</ul>
);
}
return (
<div>Loading</div>
);
}}
/>
</div>
);
}
}
Okay so my last answer didn't work as intended, so I thought I would create an entirely new example to demonstrate what I am talking about. Simply, the goal here is to have a child component within a parent component that only re-renders when it receives NEW props. Note, I have made use of the component lifecycle method shouldComponentUpdate() to prevent the Child component from re-rendering unless there is a change to the prop. Hope this helps with your problem.
class Child extends React.Component {
shouldComponentUpdate(nextProps) {
if (nextProps.id === this.props.id) {
return false
} else {
return true
}
}
componentDidUpdate() {
console.log("Child component updated")
}
render() {
return (
<div>
{`Current child ID prop: ${this.props.id}`}
</div>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
id: 14,
text: 15
}
}
onChange = (event) => {
this.setState({ text: event.target.value })
}
onClick = () => {
this.setState({ id: this.state.text })
}
render() {
return (
<div>
<input type='text' onChange={this.onChange} />
<button onClick={this.onClick}>Change ID</button>
<Child id={this.state.id} />
</div>
)
}
}
function App() {
return (
<div className="App">
<Parent />
</div>
);
}
I am doing a todo app to practice React. I hit a blocker and now I'm trying to figure out how to uniquely edit a card.
Currently when I click on edit, all my cards are set to isEditing == true. I've tried adding a key and index, but doesn't seem to uniquely identify the selected card.
As seen in my gif:
Obviously the expected outcome is that it should only set isEditing == true to the selected card.
See Code below.
For more context: there is stateful component that passes the props to this component, I'm using react-bootstrap (hence Panel, Button), and I removed some code for brevity (construct and whatnot).
edit() {
this.setState({
isEditing: true
})
}
renderEditDoneButtons() {
return (
<div>
<Button onClick={this.edit}>edit</Button>
</div>
)
}
renderNote(note) {
return (
<p> {note} </p>
)
}
renderCard(note, i) {
return (
<Panel key={i}
index={i}>
{
this.state.isEditing ?
this.renderForm() :
this.renderNote(note.note)
}
</Panel>
)
}
render() {
return (
<div>
{this.props.notes.map(this.renderCard)}
</div>
)
}
All three are changing based on your single isEditing state, which is why you're seeing all three being shown when you click any of the "Edit" buttons. Instead of a single isEditing key in state, use an array to maintain all three states like so:
constructor(props) {
super(props);
// Sets a true/false editing state for all three panels
this.state = {
editingPanels: Array(3).fill(false)
}
}
edit(i) {
// Switches editing state to false/true for given i
const editingPanels = this.state.editingPanels.slice();
editingPanels[i] = !editingPanels[i];
this.setState({
editingPanels: editingPanels
})
}
renderEditDoneButtons(i) {
return (
<div>
<Button onClick={()=>this.state.edit(i)}>edit</Button>
</div>
)
}
renderNote(note) {
return (
<p> {note} </p>
)
}
renderCard(note, i) {
return (
<Panel key={i}
index={i}>
{
this.state.editingPanels[i] ?
this.renderForm() :
this.renderNote(note.note)
}
</Panel>
)
}
render() {
return (
<div>
{this.props.notes.map(this.renderCard)}
</div>
)
}
You can use a separate component for each todo list item and use it inside the map method.The following example gives an idea on how to implement this.I am using another example as you have not provided the full code.
class EditText extends React.Component {
constructor(props) {
super(props)
this.state = {value:props.data,newValue:'hi'}
this.editValue = this.editValue.bind(this)
}
editValue() {
this.setState({value:this.state.newValue})
}
render() {
return(
<div>
{this.state.value}
<button onClick={this.editValue}>Change text to Hi</button>
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {tempDate : ['hello','how']}
}
render() {
return (
<div className="App">
{this.state.tempDate.map(data=>(<EditText data={data}/>))}
</div>
);
}
}
You need to have state variable isEditing for each particular card.
If there are 3 cards, you need to have 3 variables.
Edit 1 :-
Example is already shared by Kody R.
One Thing i noticed is instead of hard-coding array size to 3,we could assign array size by number of notes recieved in props.
this.state = {
editingPanels: Array(3).fill(false)
}
To
this.state = {
editingPanels: Array(this.props.notes.length).fill(false)
}
Hope this helps,
Cheers !!
I'm trying to use the react-bootstrap checkbox (https://react-bootstrap.github.io/components.html#forms-controls) and I need to fire an event when it changes state. It would also be great to be able to programatically un/check it and/or tell if it is checked. Unfortunately when the code is transpiled and rendered it wraps the input in a div.
How can I find this in the dom and manipulate it?
My code looks similar to this:
import React, { PropTypes } from 'react';
import { Checkbox } from 'react-bootstrap';
const EditItem = (props) => {
return (
<div>
<Checkbox style={{ marginLeft: '15px' }} >{props.itemLable}</Checkbox>
</div>
);
};
export default EditItem;
And the browser renders this:
...
<div class="checkbox" style="margin-left: 15px;">
<label>
<input type="checkbox">
</label>
</div>
...
I see the inputRef prop in the documentation but I can't find any examples of this or get it to work myself.
There are two ways: The React way and the not-so-React way.
The React way is to set the child component's state by passing it props and respond to changes in its state by attaching event handlers. In the case of Checkbox, that means setting the checked and onChange props.
Note in the below example how the parent component (App) keeps track of the Checkbox's state and can both set it with this.setState and query it with this.state.checkboxChecked.
const { Checkbox, Button } = ReactBootstrap;
class App extends React.Component {
constructor() {
super();
this.state = { checkboxChecked: false };
this.handleChange = this.handleChange.bind(this);
this.handleIsItChecked = this.handleIsItChecked.bind(this);
this.handleToggle = this.handleToggle.bind(this);
}
render() {
return (
<div>
<Checkbox
checked={this.state.checkboxChecked}
onChange={this.handleChange} />
<Button type="button" onClick={this.handleToggle}>Toggle</Button>
<Button type="button" onClick={this.handleIsItChecked}>Is it checked?</Button>
</div>
);
}
handleChange(evt) {
this.setState({ checkboxChecked: evt.target.checked });
}
handleIsItChecked() {
console.log(this.state.checkboxChecked ? 'Yes' : 'No');
}
handleToggle() {
this.setState({ checkboxChecked: !this.state.checkboxChecked });
}
}
ReactDOM.render(<App/>, document.querySelector('div'));
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-bootstrap/0.30.8/react-bootstrap.min.js"></script>
<div></div>
The not-so-React way is to get a reference to the rendered DOM element and access its checked property directly. I don't recommend this, because it necessarily pollutes your lovely functional React code with icky imperative code. Nevertheless, with React-Bootstrap you can do it by setting the inputRef prop, as in the below example:
const { Checkbox, Button } = ReactBootstrap;
class App extends React.Component {
constructor() {
super();
this.handleIsItChecked = this.handleIsItChecked.bind(this);
this.handleToggle = this.handleToggle.bind(this);
}
render() {
return (
<div>
<Checkbox
onChange={this.handleChange}
inputRef={ref => this.myCheckbox = ref} />
<Button type="button" onClick={this.handleToggle}>Toggle</Button>
<Button type="button" onClick={this.handleIsItChecked}>Is it checked?</Button>
</div>
);
}
handleIsItChecked() {
console.log(this.myCheckbox.checked ? 'Yes' : 'No');
}
handleToggle() {
this.myCheckbox.checked = !this.myCheckbox.checked;
}
}
ReactDOM.render(<App/>, document.querySelector('div'));
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-bootstrap/0.30.8/react-bootstrap.min.js"></script>
<div></div>
Thanks for the above answers. I generalized the above slightly for use when you have more than one checkbox in a given component:
constructor() {
super();
this.state = { YourInputName: false };
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
}
render() {
return (
<div>
<Checkbox
name="YourInputName"
onChange={this.handleCheckboxChange} />
</div>
);
}
handleCheckboxChange(event) {
const target = event.target
const checked = target.checked
const name = target.name
this.setState({
[name]: checked,
});
}
Have you tried setting an onChange property to your checkbox?
handleChange(event) {
this.setState(*set checkbox state here*);
}
<Checkbox onChange={this.handleChange}></Checkbox>