how to properly address a state change in ReactJS - javascript

good people.
i have a small program that adds table row elements, when "Add" is clicked. There is also an color change option when table cell is clicked. The problem is - when multiple elements are created, clicking on one of them, changes the color for all of them, though i have a onClick sitting only on the TD tags. How could this be fixed?
https://jsfiddle.net/mattighof/5nmcyL7b/
<table>
{this.state.list.map((element, index) => {
return (
<tr>
<td
className={this.state.textColor ? "trRed" : "trBlack"}onClick={this.handleClick}>
{element}
<div
onClick={e => this.removeElement(e, index)}
className="div"
/>
</td>
</tr>
);
})}
</table>
would highly appreciate your advice.

Since you're generating <td> elements in an anonymous function, using this inside it will refer to the parent closure, which, in this case, is the Table component. Therefore, the textColor property is local to the component itself, and not to the individual <td> elements.
You're already iterating through a list that you keep in the component state, so you can slightly change the element structure to allow you to keep arbitrary state data individually.
To do this, instead of adding a raw string to your list, add an object with the text and isSelected properties set to the desired values, and when rendering the <td> elements or changing colors, use the said properties. You can, of course, name these properties to your liking, and even add more properties to individually manage the states of your elements.
One other thing to note is that the current implementation of the handleClick function is unaware of the context that you're calling it from, so you'll also need to pass the index of the element when calling it, and update your state with a new list where the element at the specified index has its state updated.
Here's the revised functions as per my naming:
addElement() {
this.setState({
list: this.state.list.concat({
text: "element",
isSelected: false
})
});
}
handleClick(e, index) {
if (!this.state.list[index]) {
return;
}
// to avoid any side effects, we're taking the immutable data approach
// and creating a new list with intended values, rather than updating the list directly
const oldElement = this.state.list[index];
const newElement = Object.assign({}, oldElement, {isSelected: !oldElement.isSelected});
const newList = [].concat(this.state.list);
newList.splice(index, 1, newElement);
this.setState({
list: newList
});
}
...
{this.state.list.map((element, index) => {
return (
<tr>
<td
className={element.isSelected ? "trRed" : "trBlack"}
onClick={e => this.handleClick(e, index)}
>
{element.text}
<div
onClick={e => this.removeElement(e, index)}
className="div"
/>
</td>
</tr>
);
})}
...
I've also forked your fiddle and updated it with the code blocks I mentioned above: https://jsfiddle.net/w76frtgx/

There are many ways to handle this. Instead of relying on state to change the class, simply toggle the class trRed on the clicked target element.
To achieve this, modify handleClick to this:
handleClick(e) {
e.target.classList.toggle("trRed")
}
Edit the style rule trRed to this:
.trRed {
color: red;
}
And finally remove the textColor: true from state since it will no longer be used.
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
list: []
};
this.handleClick = this.handleClick.bind(this);
this.addElement = this.addElement.bind(this);
this.removeElement = this.removeElement.bind(this);
}
handleClick(e) {
e.target.classList.toggle("trRed")
}
addElement() {
this.setState({ list: this.state.list.concat("element") });
}
removeElement(e, index) {
e.stopPropagation();
this.setState({ list: this.state.list.filter((_, i) => index !== i) });
}
render() {
return (
<div className="container">
<button onClick={this.addElement} type="button">
Add
</button>
<table>
{this.state.list.map((element, index) => {
return (
<tr>
<td
onClick={this.handleClick}
>
{element}
<div
onClick={e => this.removeElement(e, index)}
className="div"
/>
</td>
</tr>
);
})}
</table>
</div>
);
}
}
ReactDOM.render(<Table />, document.getElementById("app"));
body {
padding: 20px;
}
td {
border: 1px solid black;
height: 15px;
padding: 5px;
}
tr {
border: 1px solid black;
position: relative;
}
table {
margin-top: 10px;
text-align: center;
width: 70px;
border: 1px solid black;
background-color: beige;
border-collapse: collapse;
}
.trRed {
color: red;
}
.div {
float: right;
width: 6px;
height: 6px;
background-color: red;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Related

How to call a function with arguments onclick of a button in a react component

Here is my function with arguments that i added in index.html in publics folder in a script tag
function displayContent(event, contentNameID) {
let content = document.getElementsByClassName("contentClass");
let totalCount = content.length;
for (let count = 0; count < totalCount; count++) {
content[count].style.display = "none";
}
let links = document.getElementsByClassName("linkClass");
totalLinks = links.length;
for (let count = 0; count < totalLinks; count++) {
links[count].classList.remove("active");
}
document.getElementById(contentNameID).style.display = "block";
event.currentTarget.classList.add("active");
}
Trying to call this function from click of buttons on my react component that looks like below
<button class="linkClass" onclick="displayContent(event, 'project2')">Meet at Campus
</button>
Please guide me with the syntax
Here's the correct syntax
<button className="linkClass" onClick={(event)=>displayContent(event,'project2')}>Meet at Campus</button>
Edit: please note that React components return JSX
It looks like you're trying to make some sort accordion but you shouldn't really be mixing vanilla JS with React as React needs control of the DOM.
So here's a brief example of how you might approach this using 1) state, and 2) a Panel component which comprises a button, and some content.
const { useState } = React;
function Example() {
// Initialise state with an array of false values
const [ state, setState ] = useState([
false, false, false
]);
// When a button in a panel is clicked get
// its id from the dataset, create a new array using `map`
// and then set the new state (at which point the component
// will render again
function handleClick(e) {
const { id } = e.target.dataset;
const updated = state.map((el, i) => {
if (i === id - 1) return true;
return false;
});
setState(updated);
}
// Pass in some props to each Panel component
return (
<div>
<Panel
name="Panel 1"
active={state[0]}
id="1"
handleClick={handleClick}
>
<span className="text1">Content 1</span>
</Panel>
<Panel
name="Panel 2"
active={state[1]}
id="2"
handleClick={handleClick}
>
<span className="text2">Content 2</span>
</Panel>
<Panel
name="Panel 3"
active={state[2]}
id="3"
handleClick={handleClick}
>
<span className="text3">Content 3</span>
</Panel>
</div>
);
}
function Panel(props) {
// Destructure those props
const {
name,
id,
active,
handleClick,
children
} = props;
// Return a div with a button, and
// content found in the children prop
// When the button is clicked the handler is
// called from the parent component, the state
// is updated, a new render is done. If the active prop
// is true show the content otherwise hide it
return (
<div className="panel">
<button data-id={id} onClick={handleClick}>
{name}
</button>
<div className={active && 'show'}>
{children}
</div>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.panel button:hover { cursor: pointer; }
.panel { margin: 1em 0; }
.panel div { display: none; }
.panel div.show { display: block; margin: 1em 0; }
.add { margin-top: 1em; background-color: #44aa77; }
.text1 { color: darkblue; font-weight: 600; }
.text2 { color: darkgreen; font-weight: 700; }
.text3 { color: darkred; font-weight: 300; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Can't you use
document.getElementById("linkClass").onclick = () =>{
displayContent();
}
by giving the element an id with same of the class?

Prevent focus on Expand More button after content is inserted in React

I need to list out a long name list inside my page while showing all names at first is not desirable.
So I try to add an expand more button on it.
However, using a button will keep the browser focus on that button after it's pressed, left the button position unchanged on the screen while the name was inserted before that button.
On the other hand, using any, not focusable element (eg. div with onclick function) will do the desired behavior but lost the accessibility at all. Making the "button" only clickable but not focusable.
How do I make the button flushed to list bottom like the snippet div block does? Or is there a better choice to expand the existing list?
const myArray = [
'Alex',
'Bob',
'Charlie',
'Dennis',
'Evan',
'Floron',
'Gorgious',
'Harris',
'Ivan',
'Jennis',
'Kurber',
'Lowrance',
]
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const handleExpand = e => {
setIdx(idx + 1)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div>
</div>
}
ReactDOM.render(<ExpandList/>, document.getElementById('root'))
.demo>p {
display: block;
padding: 20px;
color: #666;
background: #3331;
}
.demo>div>div {
display: flex;
padding: 15px;
margin-left: auto;
color: #666;
background: #3331;
}
.pointer {
cursor: pointer;
}
.pointer:hover {
background-color: #6663;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id='root' class='demo'>hello</div>
Removing focus from the button in the click handler is probably the most elegant approach: e.target.blur(). It will work on any HTML element, whether it is focusable or not (as with the div in your case).
const myArray = [
'Alex',
'Bob',
'Charlie',
'Dennis',
'Evan',
'Floron',
'Gorgious',
'Harris',
'Ivan',
'Jennis',
'Kurber',
'Lowrance',
]
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const handleExpand = e => {
e.target.blur()
setIdx(idx + 1)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div>
</div>
}
ReactDOM.render(<ExpandList/>, document.getElementById('root'))
.demo>p {
display: block;
padding: 20px;
color: #666;
background: #3331;
}
.demo>div>div {
display: flex;
padding: 15px;
margin-left: auto;
color: #666;
background: #3331;
}
.pointer {
cursor: pointer;
}
.pointer:hover {
background-color: #6663;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id='root' class='demo'>hello</div>
Inspired by #MiKo, temporally unmount the button after click and set a timeout to add it back seems to do the work. Since browser lose the focus on original expand button, this will keep content flush down without focusing the original button:
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const [showBtn, setShowBtn] = React.useState(true)
const handleExpand = e => {
setShowBtn(false)
setIdx(idx + 1)
setTimeout(() => setShowBtn(true), 10)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
{showBtn?
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div> :
<div></div>
}
</div>
}
But I'm still looking a method that doesn't need to 'unmount' a thing which should be there all time.

How to animate label using JavaScript (React JS)

I am trying to create a floating label where the label will go up when you click the input. But I am running into a problem where I am unable to call the class(the element) without affecting the other one. For example, I have 2 classes with the same class name, when I try to click on one of the other ones would work at the same time. I am trying to add an array, but as I have just started to learn JavaScript I do not know how to do it properly.
const floatinput = document.getElementsByClassName('entry-form-input');
const floatlabel = document.getElementsByClassName("tryingtoanimatelabel");
function forgetmove() {
floatlabel[0].classList.add("myanimatelabel");
}
function removemove() {
if (floatinput[0].value === "") {
floatlabel[0].classList.remove("myanimatelabel");
} else {
floatlabel[0].classList.add("myanimatelabel");
}
}
<div className="tryingtoanimate-container" onClick={forgetmove} onBlur={removemove}>
<input type="text" className="entry-form-input forgetinput" name="TeamName"/>
<label className="tryingtoanimatelabel">Team Name</label>
</div>
<div className="tryingtoanimate-container" onClick={forgetmove} onBlur={removemove}>
<input type="text" className="entry-form-input forgetinput" name="TeamName" onChange={this.handelChange}/>
<label className="tryingtoanimatelabel">Team Name</label>
</div>
you can try to pass an "event" as argument to the functions above.
In this case your event (click or blur, etc.) will be bind to the element on which event happened, and you can refer to element as event.target
function forgetmove(event){
event.target.classList.add("myanimatelabel");
}
function removemove(event){
if (event.target.previousSibling.value === "") { //take the previous node of the element on which event happened (event.target)
event.target.classList.remove("myanimatelabel");
} else {
event.target.classList.add("myanimatelabel");
}
}
Few edits to answer...
I suggest you to use "onBlur" event on "input,
also you can use event.CurrentTarget, if you want to take exact element on which event happened excluding their child elements.
The final code will looks like this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleClick = this.handleClick.bind(this);
this.handleBlur = this.handleBlur.bind(this);
}
handleClick(event) { // using "currentTarget" to take element on which event happened, this will ignore cases where we would click on child elements
let _label = event.currentTarget.querySelector('label');
let _input = event.currentTarget.querySelector('input');
_label.classList.add('myanimatelabel');
_input.focus();
}
handleBlur(event) {
let _input = event.target; // using "target" to take element on which event happened
let _label = _input.nextElementSibling; // taking next element to input
if (_input.value === "") {
_label.classList.remove('myanimatelabel');
} else {
_label.classList.add('myanimatelabel');
}
}
render() {
return (
<div className="wrapper">
<div onClick={this.handleClick}>
<input type="text" onBlur={this.handleBlur} />
<label className="tryingtoanimatelabel">
Team Name
</label>
</div>
<div onClick={this.handleClick} >
<input type="text" onBlur={this.handleBlur}/>
<label className="tryingtoanimatelabel">Team Name</label>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
.wrapper > div {
position: relative;
margin-top: 30px;
margin-left: 30px;
display: inline-block;
}
.wrapper input {
padding-left: 10px;
padding-top:3px;
}
.wrapper label {
position:absolute;
left: 10px;
top: 3px;
transition-duration: 0.6s;
cursor: text;
}
.wrapper .myanimatelabel {
top: -25px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Add a numeric value at the end of the class name so it's unique or give each id="id1" and target that ID specifically that way

React Virtualized position of list item

I'm using React Virtualized in a similar setup like the snippet below. There are a couple of items that are rendered using a CellMeasurer. The last item indicates that more items will be loaded and is not rendered normally. This item has the wrong position/style and the wrong height associated with it. How can I fix this?
If you want to play with it, here is a Plunkr:
https://plnkr.co/edit/TrKdNu4FfNsXqERPVnYo?p=preview
var List = ReactVirtualized.List;
var CellMeasurer = ReactVirtualized.CellMeasurer;
var CellMeasurerCache = ReactVirtualized.CellMeasurerCache;
var cache = new CellMeasurerCache({
fixedWidth: true,
minHeight: 50
});
// List data as an array of strings
var namesList = [
'Brian Vaughn',
'Bob Smith',
'Someone Else',
'I hate making up names for the sake of names.'
// And so on...
];
var App = React.createClass({
getInitialState: function() {
return {
list: namesList
};
},
render: function() {
return <List
className='List'
width={300}
height={300}
rowCount={this.state.list.length + 1}
rowHeight={cache.rowHeight}
deferredMeasurementCache={cache}
list={this.state.list}
rowRenderer={
({ index, isScrolling, key, parent, style }) => {
var item = this.state.list[index];
let result;
if (!item) {
console.log(index);
result =
<div className='Row'
style={style}
key={key}
>Loading!!!
</div>; }
else
result = <CellMeasurer
cache={cache}
columnIndex={0}
key={key}
style={style}
rowIndex={index}
parent={parent}>
{
<div
className='Row'
key={key}
style={style}
>
{this.state.list[index]}
</div>
}
</CellMeasurer>;
return result;
}}/>;
}
});
// Render your list
ReactDOM.render(
<App/>,
document.getElementById('example')
);
.List {
border: 1px solid black;
box-sizing: border-box;
}
.Row {
width: 100%;
height: 100%;
border-bottom: 1px solid black;
display: flex;
align-items: center;
padding: 0 0.5rem;
box-sizing: border-box;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react/dist/react-with-addons.min.js"></script>
<script src="https://unpkg.com/react-dom/dist/react-dom.min.js"></script>
<script src="https://unpkg.com/react-virtualized/dist/umd/react-virtualized.js"></script>
<link rel="stylesheet" href="https://unpkg.com/react-virtualized/styles.css">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="example">
<!-- This element's contents will be replaced with your code... -->
</div>
<script src="script.js"></script>
</body>
</html>
CellMeasurer wasn't intended for use with only certain rows in this way. From an external POV I understand why it looks like it should just work though.
When Grid renders a cell- if it thinks CellMeasurer is being used- then it positions the cell at top:0, left:0 to give it space within the Grid/List to expand. (The size of its container constrains its size, even though it's absolutely positioned. I'm not sure why this is to be honest.)
So in your case, Grid sees the last row hasn't been measured, think it's supposed to be, and positions it at 0,0.
The simplest solution for now would be to just wrap that row in CellMeasurer too.

How to generate React table with different colored rows?

I’m trying to create a React table in which each row is either gray or white. I am doing this by passing rows the props ‘gray’ or ‘white’ upon their creation, but in my MyRow class I’m not sure how to take this prop and convert it to a style.
Relevant code in MyTable.js:
this.props.items.forEach(function(item) {
if (i % 2 === 0) {
rows.push(<MyRow item={item} key={item.name} color={'gray'} />);
} else {
rows.push(<MyRow item={item} key={item.name} color={'white'} />);
}
i++;
}.bind(this));
MyRow.js render function:
render() {
const color = this.props.color;
const rowColor = {styles.color}; //?? I’m not sure how to do this—how to get color to take on either gray or white?
return (
<tr className={styles.rowColor}>
<td className={styles.td}>{this.props.item.name}</td>
<td className={styles.editButton}> <Button
onClick={this.handleEditButtonClicked}
/>
</td>
</tr>
);
}
MyRow.css:
.td {
font-weight: 500;
padding: 0 20px;
}
.gray {
background-color: #d3d3d3;
}
.white {
background-color: #fff;
}
I believe you should be able to pass the prop directly to the row as follows, since it's already defined as a string in MyTable.js file:
render() {
return (
<tr className={this.props.color}>
<td className={styles.td}>{this.props.item.name}</td>
<td className={styles.editButton}> <Button
onClick={this.handleEditButtonClicked}
/>
</td>
</tr>
);
}
Also, from the code you have posted, it's unclear where the styles object is located where you're trying to access these properties. If you wanted to access styles.rowColor, you would need a styles object defined:
const styles = {
rowColor: color
};
Follow this to change individual row cell color using row.value
{
Header: () => <div className="text-center font-weight-bold">Status</div>,
accessor: "selectedstatus",
className: "font",
width: 140,
Cell: (row) => (
<div
className="text-center h-6"
style={{ background: row.value === "Selected" ? "green" : "red" }}
>
{row.value}
</div>
),
},

Categories