How to Programmatically Assign and Get Refs in React - javascript

I was wondering if it is possible to programmatically assign and get refs in React. Suppose I wanted to go through a loop creating elements, giving them refs that consist of a name + an index. I know I can assign them like that using strings. However, the only way I know how to access refs consists of using this.refs.refname which, as far as I know, precludes me from doing something like this.refs.{refname + index}. Is there any way I can do something like this? The source code below should hopefully give you an idea of what I'm asking.
render = () => (<div className='row signature-group'>
<div className='col-md-1 col-xs-2'>
<b>{this.props.signerDescription}</b>
</div>
<div className='col-md-4 col-xs-7'>
{this.props.signers.map((signer, index) => <div className='text-with-line' key={index} ref={"sig" + index}>{signer}</div>)}
</div>
<div className='col-md-2 col-xs-3'>
{this.props.signers.map((signer, index) => {
return (index > 0 && this/*.refs.sig+index.value == whateverValue*/) ?
(<div className='text-with-line-long-name' key={index}>Date</div>) :
(<div className='text-with-line' key={index}>Date</div>);
})}
</div>
</div>)
Also, I've heard that using strings to assign refs is considered legacy. Is there any way to programmatically assign refs in a more up-to-date fashion?

Yes, you can use a ref callback to achieve this. The function passed as the ref attribute value will be passed the DOM node of the component once, after it is rendered:
applyRef = (index, ref) => {
this[`sig${index}`] = ref;
};
render = () => (
<div className="row signature-group">
<div className="col-md-1 col-xs-2">
<b>{this.props.signerDescription}</b>
</div>
<div className="col-md-4 col-xs-7">
{this.props.signers.map((signer, index) => (
<div className="text-with-line" key={index} ref={this.applyRef.bind(this, index)}>
{signer}
</div>
))}
</div>
<div className="col-md-2 col-xs-3">
{this.props.signers.map((signer, index) => {
return index > 0 && this[`sig${index}`].clientHeight > 0 ? (
<div className="text-with-line-long-name" key={index}>
Date
</div>
) : (
<div className="text-with-line" key={index}>
Date
</div>
);
})}
</div>
</div>
);
You can use bracket notation to create a new property on your class component (this) and then you access it with the same name (this.sig1, this.sig2).
String refs are deprecated and should no longer be used. Your refs are now applied directly to the component instance (this).

Related

Update the render every time the props object is updated

I am making an expense tracker. I am receiving an object of data in the income component as props. I want to render it only when the object is updated / new data is passed to the object, I want the new data is rendered along with the previous data. But every time I pass the first data, the component returns 2 empty div. How can I stop getting 2 empty div's at first render.
const Income = ({incomeData}) => {
const [totalAmount, setTotalAmount] = useState(0)
const [displayData, setDisplayData] = useState([])
useEffect(()=>{
if(incomeData.amount) setTotalAmount(totalAmount + incomeData.amount)
setDisplayData((prevData)=> [...prevData, incomeData])
}, [incomeData])
return (
<div className={style.container}>
<div className={totalAmount > 0 ? style.main : style.mainBlock}>
<h1>Total Income: {totalAmount}</h1>
{displayData.map((data, idx)=>{
const {total, type, date, amount, income, id} = data
return (
<div key={id} className={style.incomeBox}>
<div className={style.incomeDetail}>
<p>{income}</p>
<div>
<span>{amount}</span>
<span>{date}</span>
</div>
</div>
<button id={id}>
<AiFillDelete />
</button>
</div>
)
})}
</div>
</div>
)
}
export default Income
I am expecting the 2 empty data not to be displayed in the income list.
It sounds like you’re passing in empty objects for previousData and incomeData. If that’s the case then what’s rendered initially will be empty values b/c there’s no initial data.
Your options are to either pass in data on the initial render, or hide elements if data is empty. As you have a number of divs, this is just a guess at what you want to hide.
return (
<div className={style.container}>
<div className={totalAmount > 0 ? style.main : style.mainBlock}>
<h1>Total Income: {totalAmount}</h1>
{displayData[0].amount && displayData[1].amount && displayData.map((data, idx)=>{
const {total, type, date, amount, income, id} = data
return (
<div key={id} className={style.incomeBox}>
<div className={style.incomeDetail}>
<p>{income}</p>
<div>
<span>{amount}</span>
<span>{date}</span>
</div>
</div>
<button id={id}>
<AiFillDelete />
</button>
</div>
)
})}
</div>
</div>
)
The idea being that with a conditional check before the mapping of data you can either hide or display based on whether data is initially provided.

ReactJS - Loop with map function not rendering view [duplicate]

This question already has answers here:
When should I use a return statement in ES6 arrow functions
(6 answers)
Why doesn't my arrow function return a value?
(1 answer)
Closed 1 year ago.
I know this is kind of a basic question... but I am just learning React and am not so familiar with Javascript as well.
App.js
return (
<div >
<h1>My Weather dashboard</h1>
<div className="container">
{weatherCards.map((obj, index) => {
<Card {...obj}/>
})}
</div>
</div>
)
Card.js
const Card = ( props ) => {
return (
<div className="card">
<img src={props.iconLink}/>
<div className="caption">{props.iconName}</div>
<h3>Day: {props.day}</h3>
<h3>time: {props.time}</h3>
<h3>temperature: {props.temperature}</h3>
</div>
)
}
export default Card
The map does not seem to be displaying anything.
Looks like you forgot the return in the map method
<div className="container">
{weatherCards.map((obj, index) => {
return <Card {...obj}/>
})}
</div>
You can directly return the component like below
return (
<div >
<h1>My Weather dashboard</h1>
<div className="container">
{weatherCards.map((obj, index) => <Card {...obj}/>)}
</div>
</div>
)
So in arrow function if you give anything after => that will be returned on every iteration. So if you give {}-Which is a function body it will return the function body. But if you give any variable or Componenet it will directly return that. We do this if we need to return any plain object.But sometime you have to do some logical execution, that time you can define a function body. But as you are defining a function body you must use the return key like this below
return (
<div >
<h1>My Weather dashboard</h1>
<div className="container">
{weatherCards.map((obj, index) => {
// your code here
return <Card {...obj}/>
})}
</div>
</div>
)

React JSX for loop shows only the last value

I have seen similar questions here, but these haven't been helpful so far.
I have a component that has an array state:
eventData: []
Some logic watches for events and pushes the objects to the state array:
eventData.unshift(result.args);
this.setState({ eventData });;
unshift() here is used to push the new elements to the top of the array.
What I want to achieve is rendering the content of the state array. I have written a conditional that checks for a certain state, and based on that state decides what to output.
let allRecords;
if (this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
} else if (!this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
if (this.state.account === this.state.eventData[i].paramOne) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
}
}
Problems that I have with this piece of code:
The code always renders the very last value of eventData state object.
I would like to limit the rendered elements to always show not more than 20 objects (the last 20 records of the state array).
paramTwo is a bool, and according to its value I expect to see either Win or Loose, but the field is empty (I get the bool value via the console.log, so I know the value is there)
Is this even the most effective way of achieving the needed? I was also thinking of mapping through the elements, but decided to stick with a for loop instead.
I would appreciate your help with this.
A few things :
First, as the comments above already pointed out, changing state without using setState goes against the way React works, the simplest solution to fix this would be to do the following :
this.setState(prevState => ({
eventData: [...prevState.eventData, result.args]
}));
The problem with your code here. Is that the arrow function was never called :
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}
}
This function can be reduced to the following (after applying the deconstructing seen in the below code) :
<span>{paramTwo ? 'Win' : 'Lose'}</span>
Next up, removing repetitions in your function by mapping it. By setting conditions at the right place and using ternaries, you can reduce your code to the following and directly include it the the JSX part of your render function :
render(){
return(
<div> //Could also be a fragment or anything
{this.state.allRecords || this.state.account === this.state.eventData[i].paramOne &&
this.state.eventData.map(({ paramOne, paramTwo, paramThree, paramFour, paramFive, paramSix }, i) =>
<div className="event-result-table-container" key={i}> //Don't forget the key like I just did before editing
<div className="result-cell">
{paramOne}
</div>
<div className="result-cell">
<span>{paramTwo ? 'Win' : 'Lose'}</span>
</div>
<div className="result-cell">
{paramThree.c[0]}
</div>
<div className="result-cell">
{paramFour.c[0]}
</div>
<div className="result-cell">
{paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{paramSix.c[0]}
</div>
</div>
)
}
</div>
)
}
Finally, to only get the 20 first elements of your array, use slice :
this.state.eventData.slice(0, 20).map(/* CODE ABOVE */)
EDIT :
Sorry, I made a mistake when understanding the condition you used in your rendering, here is the fixed version of the beginning of the code :
{this.state.allRecords &&
this.state.eventData.filter(data => this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
Here, we are using filter to only use your array elements respecting a given condition.
EDIT 2 :
I just made another mistake, hopefully the last one. This should ahve the correct logic :
this.state.eventData.filter(data => this.state.allRecords || this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
If this.state.allRecords is defined, it takes everything, and if not, it checks your condition.
I cleaned up and refactored your code a bit. I wrote a common function for the repetitive logic and passing the looped object to the common function to render it.
Use Map instead of forloops. You really need to check this this.state.account === this.state.eventObj.paramOne statement. This could be the reason why you see only one item on screen.
Please share some dummy data and the logic behind unshift part(never do it directly on state object), we'll fix it.
getRecord = (eventObj) => (
<React.Fragment>
<div className="result-cell">
{eventObj.paramOne}
</div>
<div className="result-cell">
{eventObj.paramTwo ? <span>Win</span> : <span>Loose</span>}
</div>
<div className="result-cell">
{eventObj.paramThree.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFour.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{eventObj.paramSix.c[0]}
</div>
</React.Fragment>
)
render() {
let allRecords;
if (this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => this.getRecord(eventObj)}</div>;
} else if (!this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => {
if (this.state.account === this.state.eventObj.paramOne) {
return this.getRecord(eventObj);
}
return null;
})}</div>;
}
return (<div className="event-result-table-container">{allRecords}</div>);
}

React | How to get length of divs by id/class?

I need to find the length of child element divs
<div className="intentContainer">
<div className="intent">
</div>
<div className="intent">
</div>
</div>
Here is my code. Need to find no of 'intent' elements
You can use callback ref.
ref={(ele) => this.myEle = ele
put callback ref on parent node which length or child count you want.
return (<div style={styles} ref={(ele) => this.myEle = ele}>
<div >Hello World</div>
<h2>Start editing to see some magic happen {'\u2728'}</h2>
</div>);
Use componentDidMount or componentDidUpdate life cycle to get the length.
componentDidMount(){
console.log(this.myEle.children.length); //2
}
Working React#codesandbox demo
You can use ReactDOM.findDOMNode, even-though the documentation encourages using ref.
DEMO
You need to put ref on parent node.
<div ref="intentContainer" className="intentContainer"></div>
Use the following code in our componentDidMount method.
componentDidMount(){
// get this intentContainer using ref (Its your parent)
var intentContainer = this.refs.intentContainer;
// this will return the count of all childrens
var childrenCount = this.refs.intentContainer.children.length;
// get the count by particular class name from parent dom
var countByClass = ReactDOM.findDOMNode(intentContainer).getElementsByClassName('intent').length;
}
And your render method like as follows,
render() {
return (
<div ref="intentContainer" className="intentContainer">
<div className="intent">
</div>
<div className="intent">
</div>
</div>
);
}
For more help please check to here and here.
Hoeps this will help you !!
import React, { Component } from "react";
export default class SampleComponent extends Component {
intentCount(){
console.log(document.querySelectorAll('.intent').length)
}
render() {
return (
<div className="intentContainer">
<div className="intent">
</div>
<div className="intent">
</div>
<div className="intent">
</div>
<button onClick={this.intentCount}>Intent Count</button>
</div>
);
}
}
This should work :) ..

Account for null values when mapping an array ES6

This is I'm mapping an array successfully..
const faculty = props.faculty;
{(faculty.science_department || []).map(science_dep => (
<div className="row">
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>
))}
But what if the array is empty? How can I account for null values/empty states? How can I check within this map function? Ie: show
<p>There are no faculty members within this category</p>
But what if the array is empty? How can I account for null
values/empty states? How can I check within this map function? Ie: show
<p>There are no faculty members within this category</p>
Given those requirements I would first filter the array, then render it with the map if it contains anything, otherwise render the placeholder message:
const {faculty} = this.props;
const science_department = (faculty.science_department || []).filter(dep => !!dep);
{
science_department.length ? science_department.map(science_dep => (
<div className="row" key={warning.id}>
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>)
:
<p>There are no faculty members within this category</p>
}
PS: What is warning.id? The key should be a field of the science_dep with a unique value.
Assuming you want to do this within JSX syntax, then you can use the ternary operator:
const faculty = props.faculty;
{
faculty.science_department.length !== 0 ?
faculty.science_department.map(science_dep => (
<div className="row" key={warning.id}>
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>
))
:
<p>There are no faculty members within this category</p>
}
{(faculty.science_department || []).length ? faculty.science_department.map(science_dep => (
<div className="row" key={warning.id}>
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>
)) : (science_dep => (
<div className="row" key={warning.id}>
<p>There are no faculty members within this category</p>
</div>)()}

Categories