Use of array.map and onchange in React - javascript

The code below contains an array.map function what is the function of term and i and where was it gotten from, and what does the array.map and the onchange do
import React, { Component } from 'react';
class Apps extends Component {
componentDidMount() {
}
iLikeFunctions() {
console.log('yay functions');
}
render() {
var array = ['here','we','go'];
var no = 'yes';
const display = 'My Name';
return (
<div>
<p>{display}</p>
<hr />
<input type="text" onChange={this.iLikeFunctions} />
<table>
<tbody>
{array.map((term,i) => {
no = 'no';
return (
<tr key={i}>
<td>{term}</td>
<td>{no}</td>
</tr>
)
})}
</tbody>
</table>
</div>
);
}
}
export default Apps;

Map:
The map() method creates a new array with the results of calling a provided function on every element in the calling array. So in the following line:
array.map((term,i)
You are mapping the array called array and looping through the array, assigning the word term for each value in the array and return a tr element for each array element with their respective value, index and variable string printed on the <tr>.
Key:
i is the index of the respective value which acts as a key since you didn't specify unique key ids for the elements.
A "key" is a special string attribute you need to include when creating lists of elements. Keys help React identify which items have changed, are added, or are removed.
Do note that it is not recommended to use indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state.
Check out the keys section in the official React Docs for a more in-depth explanation of keys.
onchange:
onchange watches the input field for any change and when it detects a change, it runs the iLikeFunctions().
tldr: The above code loops through array ['here','we','go']; and returns a <tr> for each value. It also runs the iLikeFunctions() whenever the input field value is changed.

Related

Returning value from React child component

I am beginner in React so I apologize if this is a basic question. I am trying to build an online Sudoku grid using ReactJS but I am unable to render the digits in the field. I have made two components one being the Sudoku.js which renders the complete sudoku block and other being Node.js which consists of an input field wrapped inside a div. the functionality I want is to change the default value of the input field (which is "" in my case) whenever the user types a number. I have tried the below approach. It updates the value of my grid variable but does not show the updated value in the input field of Node. Kindly help.
Sudoku.js
const sudokugrid = (
<div>
{grid.map((row,rowIdx) => {
return (
<div key = {rowIdx} className="rowWrapper">
{row.map((col,colIdx) => {
var element = grid[rowIdx][colIdx].value;
const change = (event) => {
element = event.target.value;
grid[rowIdx][colIdx].value = element;
setGrid(grid);
return element;
}
return (
<Node
key = {colIdx}
onChangeValue = {change}
value = {element}
/>
)
})}
</div>
)
})}
</div>
);
here grid is a 2D array of 9x9 elements which have initial value of all the elements being "" , which are supposed to be updated when the user types in the value in the respective fields. the problem is that the value is updated when the user types the number but the number is not shown in the input field
The Node component is as follows:
function Node(props){
return (
<div className="box">
<input
className = "num"
type="number"
value = {props.value}
onChange = {props.onChangeValue}
/>
</div>
)
}
This is because react can't detect mutations. See this article. You have to store the sudoku grid in a state value and change the state with a callback function.
An example.
I suggest using an object map for storing the state because you can manage the values a lot easier.

React - sort array of child components with state

Currently I'm working on a react project, but I'm seeing some unexpected behavior when sorting an array of stateful child components.
If I have a parent component
export function Parent(){
const [children, setChildren] = useState([
{name:'Orange',value:2},
{name:'Apple',value:1},
{name:'Melon',value:3}
])
var count = 0
function handleSort() {
var newChildren=[...children]
newChildren.sort((a,b)=>{return a.value-b.value})
setChildren(newChildren)
}
return (
<div>
<button onClick={handleSort}>Sort</button>
{children.map((child) => {
count++
return(<ChildComp key={count} details={child}/>)
})}
</div>
)
}
And a child component
function ChildComp(props){
const[intCount,setIntCount] = useState(0)
function handleCount(){
setIntCount(intCount+1)
}
return (
<div>
<p>{props.details.name}</p>
<button onClick={handleCount}>{intCount}</button>
</div>
)
}
When the page first renders everything looks great, three divs render with a button showing the number of times it was clicked and the prop name as it was declared in the array. I've noticed that when I sort, it sorts the props being passed to the child components which then rerender, but the intCount state of the child component stays tied to the original location and is not sorted. is there any way to keep the state coupled with the array element through the sort while still maintaining state data at the child level, or is the only way to accomplish this to raise the state up to the parent component and pass a callback or dispatch to the child to update it?
The count is not is not sorted. It just got updated when you sorted.
Keys help React identify which items have changed, are added, or are
removed. Keys should be given to the elements inside the array to give
the elements a stable identity
Every time you sort, key stay the same, as you use count.
Try using value as key
export function Parent(){
// ....
return (
<div>
<button onClick={handleSort}>Sort</button>
{children.map(child => {
return <ChildComp key={child.value} details={child}/> // key is important
})}
</div>
)
}
More info: https://reactjs.org/docs/lists-and-keys.html#keys

Find First Iteration of Map in React/Javascript

I'm mapping an object array in React and I want to find the first iteration of chosenFields array.
People Array:
[ { id:1, name:"Jim", occupation:"Programmer".........}
{id:2, name:"Sally", occupation:"Pet Sitter".......}]
I have another object array that is in charge of what fields are suppose to be displayed from the PeopleArray:
chosenFields Array:
[{label:"id", show:true}, {label:"name", show:true}, {label:"occupation", show:false}]
I want to know if there's a way that the code can recognize when it first iterates through chosenFields Array for each row of the People Array
renderSingleData(rowListData, currData){
//find which one is the first iteration to add a radiobox for each row
}
renderUserData(currRow){
return(
<div>
{chosenFields.map(this.renderChosenData.bind(this, currRow)}
</div>
)
}
render() {
return (
<div >
{PeopleData.map(this.renderUserData.bind(this))}
</div>
);
}
}
This is a very simplified version of the code. I need to add an iteration to a table via <td>. I'm currently using a variable in React and setting the state with a counter. I'm wondering if there's a more straightforward way to find iterations via the map function.
Take a look at the docs for Array.prototype.map()
The callback function's second argument is the index. If this is 0, then that is the first iteration of the callback. In your case, you may need to propagate that condition up a few callbacks. Something like...
renderSingleData(isFirst, rowListData, currData){
//find which one is the first iteration to add a radiobox for each row
}
renderUserData(currRow, currIdx){
return(
<div>
{chosenFields.map(this.renderChosenData.bind(this, currIdx === 0, currRow)}
</div>
)
}
render() {
return (
<div >
{PeopleData.map(this.renderUserData.bind(this))}
</div>
);
}

Dynamically created custom form components in react

See this gist for the complete picture.
Basically I will have this form:
When you click the plus, another row should appear with a drop down for day and a time field.
I can create the code to add inputs to the form, however I'm having trouble with the individual components (selectTimeInput is a row) actually updating their values.
The onChange in the MultipleDayTimeInput is receiving the correct data, it is just the display that isn't updating. I extremely new to react so I don't know what is causing the display to not update....
I think it is because the SelectTimeInput render function isn't being called because the passed in props aren't being updated, but I'm not sure of the correct way to achieve that.
Thinking about it, does the setState need to be called in the onChange of the MultipleDayTimeInput and the input that changed needs to be removed from the this.state.inputs and readded in order to force the render to fire... this seems a little clunky to me...
When you update the display value of the inputs in state, you need to use this.setState to change the state data and cause a re-render with the new data. Using input.key = value is not the correct way.
Using State Correctly
There are three things you should know about
setState().
Do Not Modify State Directly
For example, this will not re-render a
component:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
The only place where you
can assign this.state is the constructor.
read more from Facebook directly here
I would actually suggest a little bit of a restructure of your code though. It's not really encouraged to have components as part of your state values. I would suggest having your different inputs as data objects in your this.state.inputs, and loop through the data and build each of the displays that way in your render method. Like this:
suppose you have one input in your this.state.inputs (and suppose your inputs is an object for key access):
inputs = {
1: {
selectedTime: 0:00,
selectedValue: 2
}
}
in your render, do something like this:
render() {
let inputs = Object.keys(this.state.inputs).map((key) => {
let input = this.state.inputs[key]
return (<SelectTimeInput
key={key}
name={'option_' + key}
placeholder={this.props.placeholder}
options={this.props.options}
onChange={this.onChange.bind(this, key)}
timeValue={input.selectedTime}
selectValue={input.selectedValue}
/>)
)}
return (
<div>
<button className="button" onClick={this.onAddClick}><i className="fa fa-plus" /></button>
{ inputs }
</div>
);
}
Notice how we're binding the key on the onChange, so that we know which input to update. now, in your onChange function, you just set the correct input's value with setState:
onChange(event, key) {
this.setState({
inputs: Immutable.fromJS(this.state.inputs).setIn([`${key}`, 'selectedTime'], event.target.value).toJS()
// or
inputs: Object.assign(this.state.inputs, Object.assign(this.state.inputs[key], { timeValue: event.target.value }))
})
}
this isn't tested, but basically this Immutable statement is going to make a copy of this.state.inputs and set the selectedTime value inside of the object that matches the key, to the event.target.value. State is updated now, a re-render is triggered, and when you loop through the inputs again in the render, you'll use the new time value as the timeValue to your component.
again, with the Object.assign edit, it isn't tested, but learn more [here]. 2 Basically this statement is merging a new timeValue value in with the this.state.inputs[key] object, and then merging that new object in with the entire this.state.inputs object.
does this make sense?
I modified the onChange in the MultipleDayTimeInput:
onChange(event) {
const comparisonKey = event.target.name.substring(event.target.name.length - 1);
const input = this.getInputState(comparisonKey);
input.selected = event.target.value;
input.display = this.renderTimeInput(input);
let spliceIndex = -1;
for (let i = 0; i < this.state.inputs.length; i++) {
const matches = inputFilter(comparisonKey)(this.state.inputs[i]);
if (matches) {
spliceIndex = i;
break;
}
}
if (spliceIndex < 0) {
throw 'error updating inputs';
}
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
}
The key points are:
// re render the input
input.display = this.renderTimeInput(input);
// set the state by copying the inputs and interchanging the old input with the new input....
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
Having thought about it though, input is an object reference to the input in the this.state.inputs so actually [...this.states.inputs] would have been enough??

Getting key prop warning in React, even though key is set

Problem
I'm getting this warning:
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of EventsTable. See fb.me/react-warning-keys for more information.
react-runtime-dev.js?8fefd85d334323f8baa58410bac59b2a7f426ea7:21998 Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of Event. See fb.me/react-warning-keys for more information.
Source
This is EventsTable:
EventsTable = React.createClass({
displayName: 'EventsTable',
render() {
console.dir(this.props.list);
return (
<table className="events-table">
<thead>
<tr>
{_.keys(this.props.list[0]).map(function (key) {
if (key !== 'attributes') {
return <th>{key}</th>;
}
})}
</tr>
</thead>
<tbody>
{this.props.list.map(function (row) {
return (
<Event key={row.WhatId} data={row} />
);
})}
</tbody>
</table>
)
}
});
This is Event:
Event = React.createClass({
displayName: 'Event',
render() {
return (
<tr>
{_.keys(this.props.data).map((x) => {
if (x !== 'attributes')
return <td>{this.props.data[x]}</td>;
})}
</tr>
)
}
});
Question
Clearly I've got the key prop on the <Event /> component. And I'm following the convention that you're supposed to include key on the component, not on the HTML itself (in other words, HTML tags within the Event component). Per the official React docs:
The key should always be supplied directly to the components in the array, not to the container HTML child of each component in the array:
I'm severely confused. Why am I getting warnings?
Have you tried adding a key to the <th> tag?
<tr>
{_.keys(this.props.list[0]).map(function (key) {
if (key !== 'attributes') {
return <th key={key}>{key}</th>;
}
})}
</tr>
I ended up solving it when I realized because I had a <React.Fragment> which also needs a unique key.
tl;dr
Every time you render a list (use map), add a unique key attribute to the list elements (the topmost or "root" element returned from map's callback):
render() {
return (
<div>
{this.props.data.map( element => {
// Place the key on to the returned "root" element.
// Also, in real life this should be a separate component...
return <div key={element.id}>
<span>Id (Name): </span>
<span>{element.id} </span>
<span>({element.name})</span>
</div>;
})}
</div>
)
}
Explanation
Understanding keys in React
The official Lists and Keys documentation shows how you should work with lists and the linked reconciliations doc tells the whys.
Basically when React rerenders a component it runs a diff algorithm that finds out what changed between the new and the previous version of the list. Comparison is not always trivial, but if there is a unique key in each element, it can be clearly identified what has changed. See the example in the doc:
<!-- previous -->
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
<!-- new -->
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
It is clear that a new element with the key 2014 was added, since we have all the other keys and those weren't changed. Without the keys this would be obscure.
Selecting a proper key
From now it is easy to see:
Why it is important that the key should be unique but only between the siblings in the list, because the comparison happens only within the given list's previous and new elements.
The key should remain the same for the same element between the previous and the new version, otherwise we would compare different elements and wouldn't be able to track change. That is why it is advised to use the id of the resource or (if it doesn't have one) some other data that is unique to the element, and why you shouldn't use things like Math.random().
Placing key attribute on components
The convention that you should place the key attribute to a component is more of a good practice, because when you iterate a list and want to render an element, that clearly indicates that you should organize that code to a separate component.
Setting the key attribute in the loop
The statement you quoted from the docs:
The key should always be supplied directly to the components in the array, not to the container HTML child of each component in the array:
Means that if you render components in a loop, then you should set the key attribute of the component in the loop, like you did it in your EventsTable component:
{this.props.list.map(function (row) {
return (
<Event key={row.WhatId} data={row} />
);
})}
The wrong way is to pass it down to the component where it would set the key on itself:
Event = React.createClass({
displayName: 'Event',
render() {
// Don't do this!
return (
<tr key={this.props.data.WhatId}>
{_.keys(this.props.data).map((x) => {
There is another good example for this in this article.
Check if variable that you pass to key is defined, because if it's undefined then error will be same, but it looks like code should work.
I had the problems too, and fixed it after follwing link.
like:
{_data.map(function(object, i){
return <div className={"row"} key={i}>
{[ object.name ,
<b className="fosfo" key={i}> {object.city} </b> , // remove the key
object.age
]}
</div>;
})}
The easiest fix for this is to create a separate component for the items you're mapping and add the key to that component.
Create a new component above your existing component (or link to it your call).
const TableDataComponent = ({ k }) => {
return (
<th>{k}</th>
)
}
Then in your code add that component with your key:
<tr>
{arr.map((k) => {
return <TableDataComponent key={k._id} k={k} />
})}
</tr>

Categories

Resources