ReactJS selecting an element uniquely from a map - javascript

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 !!

Related

What is the correct way to select one of many child elements in React?

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>

How to pass class props to function in reactjs

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?

rendering multiple elements after onClick event in React

I'm having problems trying to render two react elements inside a react component after a onClick event. Wondering if that's even possible? I'm sure I'm messing up the ternary operator, but I cannot think on another way to do what I'm trying to do ?
TL;DR: "When I click a button I see elementA and elementB"
Here is a snippet of the code:
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = { showElement: true };
this.onHandleClick = this.onHandleClick.bind(this);
}
onHandleClick() {
console.log(`current state: ${this.state.showElement} and prevState: ${this.prevState}`);
this.setState(prevState => ({ showElement: !this.state.showElement }) );
};
elementA() {
<div>
<h1>
some data
</h1>
</div>
}
elementB() {
<div>
<h1>
some data
</h1>
</div>
}
render() {
return (
<section>
<button onClick={ this.onHandleClick } showElement={this.state.showElement === true}>
</button>
{ this.state.showElement
?
null
:
this.elementA() && this.elementB()
}
</section>
)
}
}
export default MyComponent;
You just inattentive.
elementA() {
return ( // You forget
<div>
<h1>
some data
</h1>
</div>
)
}
And the same in element B.
And if You want to see both components you should change Your ternary to
{ this.state.showElement
?
<div> {this.elementA()} {this.elementB()}</div>
:
null
}
Another "and", for toggling showElement in state just enough
this.setState({showElement: !this.state.showElement });
Try this instead, (I will add comments into the code trying to explain what's going on):
function SomeComponentName() { // use props if you want to pass some data to this component. Meaning that if you can keep it stateless do so.
return (
<div>
<h1>
some data
</h1>
</div>
);
}
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = { showElement: false }; // you say that initially you don't want to show it, right? So let's set it to false :)
this.onHandleClick = this.onHandleClick.bind(this);
}
onHandleClick() {
this.setState(prevState => ({ showElement: !prevState.showElement }) );
// As I pointed out in the comment: when using the "reducer" version of `setState` you should use the parameter that's provided to you with the previous state, try never using the word `this` inside a "reducer" `setState` function
};
render() {
return (
<section>
<button onClick={ this.onHandleClick } showElement={this.state.showElement === false}>
</button>
{ this.state.showElement
? [<SomeComponentName key="firstOne" />, <SomeComponentName key="secondOne" />]
: null
}
</section>
)
}
}
export default MyComponent;

React.js - add div's on the fly

So I am getting my hands dirty on React and I can't seem to figure out this simple problem (probably because of lack of sleep)
I want to add elements (or divs) inside the render on the fly when I click "Add Row".
How would I go on about it? Do I need to keep it in an array and within the render function, I will have to map it?
class SimpleExample extends React.Component {
constructor(props) {
super(props)
this.handleAddingDivs = this.handleAddingDivs.bind(this)
}
handleAddingDivs() {
const uniqueID = Date.now()
return (
<div>
This is added div! uniqueID: {uniqueID}
</div>
)
}
render() {
return (
<div>
<h1>These are added divs </h1>
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
</div>
)
}
}
Let's say you want to add multiple divs, so maintain a state variable for that, count or any other data (you can use any array also and store the unique value of all the divs), then use map or any other loop to create the divs for that.
Check this working snippet:
class SimpleExample extends React.Component {
constructor(props) {
super(props);
this.state = {count : 0}
this.handleAddingDivs = this.handleAddingDivs.bind(this)
}
handleAddingDivs() {
this.setState({count: this.state.count + 1})
}
renderDivs(){
let count = this.state.count, uiItems = [];
while(count--)
uiItems.push(
<div>
This is added div! uniqueID: {count}
</div>
)
return uiItems;
}
render() {
return (
<div>
<h1>These are added divs </h1>
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
{this.renderDivs()}
</div>
)
}
}
ReactDOM.render(<SimpleExample/>, document.getElementById('app'))
<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>
<div id ='app'/>
Try the code below. Whenever you click the button, a unique id is generated and stored in state.uids. In render(), added divs are rendered according to state.uids.
class SimpleExample extends React.Component {
constructor(props) {
super(props)
this.handleAddingDivs = this.handleAddingDivs.bind(this)
this.state = {uids:[]}
}
handleAddingDivs() {
let curr = this.state.uids;
const uniqueID = Date.now()
this.setState({uids:[...curr, uniqueID]});
}
render() {
return (
<div>
<h1>These are added divs </h1>
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
{ this.state.uids.map((uid, idx)=>
<div key={uid}>This is added div! uniqueID: {uid}</div>
)}
</div>
)
}
}
ReactDOM.render(<SimpleExample/>, document.getElementById('app'))
<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>
<div id ='app'/>
yes you will need to store data about new divs somewhere...
this is what flux/redux is sometimes used for: you store all data you need to render in Store and then you know what to render.
But if you whant this using only React, then use state !
Your state should be like this in your case:
{
addedDivs: [
{uniqueId: 123},
{uniqueId: 754},
]
}
then in render you will map it (don't forget to add key)
this.state.addedDivs.map((item) = > {return (<div key={item.uniqueId}></div>) })
and onClick you should just add some, using setState:
this.setState((prevState, props) => {
var addedDivs = prevState.addedDivs;
var uniqueId = Date.now();
addedDivs.push({uniqueId: uniqueId});
return {addedDivs: addedDivs}
});
You are thinking of terms of DOM manipulation/jQuery. When you use React, you need think in terms of data, and how it relates to your DOM.
In your case, you need to:
Update your component state with a new row, everytime there is a click to the 'Add Row" button, in the handleAddingDivs() method
Render rows based on the state, in the render() method
class SimpleExample extends React.Component {
constructor(props) {
super(props)
this.handleAddingDivs = this.handleAddingDivs.bind(this)
}
//Modify state of component when 'Add Row' button is clicked
handleAddingDivs() {
const uniqueID = Date.now();
this.setState({rows: this.state.rows.concat(uniqueId)});
}
//The `render()` function will be executed everytime state changes
render() {
return (
<div>
<h1>These are added divs </h1>
//The rows are rendered based on the state
{this.state.rows.map(function(uniqueId) {
return (
<div key={item.uniqueId}>This is added div! uniqueID: {uniqueID}</div>
)
}}
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
</div>
)
}
}

ReactJS clearing an input from parent component

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>
)
}

Categories