React - render after single item modification in array - reactjs

My problem goes like this:
I'm rendering an array of items using "map".
Each rendered item also includes a toggle button component that i created. You can refer this as regular checkbox.
The checkbox value will decided if the rendered item will have a specific css class, with transitions.
Here come's the problem.
On state change, i update the whole list. Therefore, the whole list is being re-rendered. Therefore, i get the relevant item with the relevant boolean value. Despite that, this item isn't modified, it's re-rendered, so the css doesn't change from A to B, it's just getting a new css class instead the previous, so the TRANSITION DOESN'T WORK.
The rendered array:
{this.state.extensions.map((curr,index)=>{
return <div className="single-extension" key={'extension-' + Utils.guid()}>
<div className="toggle-area">
<ToggleButton onChange={(value)=>{
this.handleAssociateExtension(index,value);
}} active={curr.associated}/>
</div>
<div className={ClassNames({'details':true, 'on': curr.associated})}>
<div className="link-text">{curr.link_text}</div>
<div className="description">{curr.description}</div>
<div className="landing-page">{curr.landing_page}</div>
</div>
</div>
})}
The update function:
handleAssociateExtension(index, value) {
let extensions = Utils.cloneObject(this.state.extensions);
if (extensions[index]) {
extensions[index].associated = value;
}
this.setState({extensions: extensions});
}
Thank you in advance! :)

Related

How to toggle text without jquery in react?

I'm trying to make a menu in react, that turns into a dropdown menu on smaller devices. I want to avoid using jQuery, since as far as I know you should avoid using it in React.
I want the dropdown menu to, well, drop down only if you click a certain thing on the navbar, to reduce the place it would take on the website if it would be always opened on smaller devices. In my case now, the stuff I click is "dropdown. My dropdown menu works, but I want to make my "V" into a "O" when the menu expands, and back to "V" when I close the menu.
I tried to do it with If, but I couldn't figure it out.
JSX:
<div id="container">
<h1 id="logo">Logo</h1>
<h1 id="navitem1">Menu option1</h1>
<h1 id="navitem2">Menu option2</h1>
<h1 id="navitem3">Menu option3</h1>
<h1 id="dropdown" onClick={DropDown}>V</h1>
</div>
const DropDown = () => {
if (document.getElementById('dropdown').innerText = 'V'){
document.getElementById('dropdown').innerText = 'O'
}
}
The If method I'm using here changes the "dropdown" element to O, but I don't know how to change it back to V.
in react you don't manipulate dom directly.
you can handle it with useState hook. first define the state in the component.
const [dropDownText,setDropDownText] = useState("V")
so your DropDown function should be :
const DropDown = () => {
if (dropDownText=== 'V'){
setDropDownText("O")
}else{
setDropDownText("V")
}
}
and the the element should be
<div id="container">
<h1 id="logo">Logo</h1>
<h1 id="navitem1">Menu option1</h1>
<h1 id="navitem2">Menu option2</h1>
<h1 id="navitem3">Menu option3</h1>
<h1 id="dropdown" onClick={DropDown}>{dropDownText}</h1>
</div>

How to get the element with the testid using react testing library?

Hi i want to access the first div with class "icon" using getByTestId.
below is how the DOM looks,
<div class="parent">
<div data-testid="add">
<div class="info"></div>
<div class="action">
<div class="icon"/> //want to access this
</div>
</div>
</div>
<div class="parent">
<div data-testid="add">
<div class="info"></div>
<div class="action">
<div class="icon"/>
</div>
</div>
</div>
As you see from above there are multiple elements with testid "add" how can i get the first add div and access its child div with class icon.
i have tried
const element = queryAllByTestId('add')[0].querySelector(
'div'
)[1];
this gives element undefined.
could someone help me with this. thanks.
getAllBy
getAllBy* queries return an array of all matching nodes for a query,
and throw an error if no elements match.
queryAllBy
queryAllBy* queries return an array of all matching nodes for a
query, and return an empty array ([]) if no elements match.
Query Cheetsheet
Use the query variant so as not to throw in a test if no matches found, and use querySelectorAll since querySelector returns only the first element that matches, or null.
const element = queryAllByTestId("add");
if (element.length) {
let divs = element[0].querySelectorAll("div");
// ...get the div node you need
}
You can even specify divs with a specific class
const element = queryAllByTestId("add");
if (element.length) {
let divs = element[0].querySelectorAll("div.icon");
// ...get the div node you need
}

Radio buttons pre-populated with previous repsonse

I have a function that builds a matrix-grid style question, where you have multiple questions to the left with radio button responses for each question to the right.
My issue is that, when this function is called more than once (you have more than 1 matrix-grid question), the grid is already pre-populated with the responses from the previous matrix-grid question.
It's worth noting that, in my state the responses from Question1 are unchanged and despite being pre-populated, the state variables for the second question are actually blank until you start clicking
renderFieldlikert(params) {
var count = 0;
let matrixQuestion = params.SubFields.map(function(question, x) {
let questionText = (<li className='left-column'><h4 className='text-left'>{question.Label}</h4></li>);
let radioButtons = params.Choices.map(function(choice, i) {
let val = choice.Score
return(
<li className='animated fadeIn'>
<input onChange={(e) => this.props.handleRadioChange(o.ID, e)} type="radio" name={question.Label} id={++count} key={i} value={val} />
<label htmlFor={count}>{choice.Score}</label>
</li>
)
}.bind(this));
return(
<ul>
{questionText}
{radioButtons}
</ul>
)}.bind(this));
Image 1: User answer's questions
Image 2: Answers from question 1 show up in question 2
Try giving your returned <ul> a unique key so that React can identify that you're trying to render different, unique components. React internally optimizes how it handles the DOM and will re-use HTML elements if it thinks that they are supposed to be the same elements.
An easy way to do this is with Date.now() which will always be unique every time your function returns a new component.
return(
<ul key={Date.now()} >
{questionText}
{radioButtons}
</ul>
)}.bind(this));
However the above won't work if you're calling renderFieldlikert() on every render, causing React to generate a new component every time it renders instead of reusing the same one. key should be unique and generated once before using it in a render method. It would be even better if every param you pass in has some kind of unique identifier and you used that instead.
// Maybe?
return(
<ul key={params.uniqueKey} >
{questionText}
{radioButtons}
</ul>
)}.bind(this));

How to get value when React wraps content in spans?

I have a React component that renders some html like below, with one callback method (this.deleteItem) triggered upon click of an x. In the callback method, I try to get the content associated with each of the two refs like this
var date = this.refs.date.getDomNode.value;
var content = this.refs.content.getDomNode.value;
but the result is undefined in both cases. When I simply do this.refs.content.getDomNode (instead of looking for the value) it shows me a div with some span tags inside, and inside of one of those is the content I was seeking. Similarily with the date ref, it is a <small></small> element with spans inside.
Question: how to get the value/content from a div or element when react wraps content in spans?
<div ref="wrapperdiv">
<span className="delete" onClick={this.deleteItem}>x</span>
<small ref="date"> {date} </small>
<div ref="content"> {content } </div>
</div>
This is a known limitation of react, in that it wraps any floating text nodes in a span because it has to handle the data-reactid of the component. See this question too.
Perhaps if you tried to remove the white space around the content?
<div ref="wrapperdiv">
<span className="delete" onClick={this.deleteItem}>x</span>
<small ref="date">{date}</small>
<div ref="content">{content}</div>
</div>
Also try:
this.refs.content.getDomNode().children[0].textContent
to get the value of the span. (Not sure if there is a react specific function for this). This will have to be done as well as removal of the white space within:
<small ref="date">{date}</small>
<div ref="content">{content}</div>
This is important because react generates span tags to handle the data-reactid. Take a look at: Rid of repeated spans in React.js?.

AngularJS - Add DOM elements on counter++

I'm quite new to AngularJS and have read a little bit about how directives work. I'm still not quite sure about how to go about this problem though.
Here's the setup:
I have buttons on a view like so:
<div class="parent">
<button class="firstButton" type="button" ng-click="fetchData(param1, param2)"></button>
<button class="secondButton" type="button" ng-click="fetchData(param1, param2)"></button>
<button class="thirdButton" type="button" ng-click="fetchData(param1, param2)"></button>
<button class="fourthButton" type="button" ng-click="fetchData(param1, param2)"></button>
</div>
<div class="dataPanel">
<!-- This is where DIVs will be placed -->
</div>
Then in my controller I have:
// Init value of counter here
var counter = 0;
$scope.fetchData = function (param1, param2) {
// Do something with parameters here
counter++;
}
Ideal scenario would be when user clicks on firstButton, fetchData() initiates, getting data from a factory, then increments value of counter. A div would be created from counter value change. The number of divs created in the DOM would depend on counter value. Each created div is also populated with data gotten from each respective fetchData(). This goes on as user clicks on more buttons, although in my current project, I've limited the number of allowed dataSet duplicates.
End result of the HTML would then be:
<div class="dataPanel">
<div class="dataSet"> <!-- from firstButton -->
<p>{{dataFromFirstButton}}</p>
</div>
<div class="dataSet"> <!-- from secondButton -->
<p>{{dataFromSecondButton}}</p>
</div>
<div class="dataSet"> <!-- from thirdButton-->
<p>{{dataFromThirdButton}}</p>
</div>
</div>
What's the Angular way of doing this? I've read directives can pretty much do everything when it comes to DOM manipulation but have no idea on where to start when it comes to actually constructing the directive.
One way to accomplish this is to use ng-repeat directive to draw the divs based on an array, that is initially empty. You can set the html up so that a div is drawn for each object in the array, since the array is empty no div is initially drawn.
<div class="dataSet" ng-repeat="n in arrays[0]">
This div from button 1 contains: {{n.data}}
</div>
When you click on the buttons you can add an object, containing any needed data, to the array. Then a div will be drawn for the appropriate ng-repeat directive.
$scope.arrays = [ [], [], [], []];
$scope.fetchData = function (data, idx) {
$scope.arrays[idx].push({data: data});
}
See plunker here.

Resources