React: Handling mapped states - javascript

I'm very new to coding and trying to figure out an issue I have come across.
I am using axios to pull a json file and store it in a state. (I am also using Redux to populate the form)
Then I am using .map() to dissect the array and show one value from within each object in the array.
example json:
unit :
[
{
designName : x,
quantity : 0,
},
{
designName : y,
quantity : 0,
},
{
designName : z,
quantity : 0,
}
]
I have then added an input to select the quantity of the value mapped and now I want to give that value back to the state, in order to send the entire modified json back to the API with Axios.
I feel like I'm close but I'm unsure what I need to do with the handleQuantity function.
Here's my code:
import React, { Component } from 'react';
import store from '../../redux_store'
import axios from 'axios';
import { Button, Card } from 'react-bootstrap'
import { Link } from 'react-router-dom'
store.subscribe(() => {
})
class developmentSummary extends Component {
constructor(props) {
super(props)
this.state = {
prjName: store.getState()[0].developmentName,
units: []
}
}
componentDidMount() {
axios.get('https://API')
.then(
res => {
console.log(res)
this.setState({
units: res.data.buildings
})
console.log(this.state.units.map(i => (
i.designName
)))
}
)
}
handleQuantity() {
}
render() {
return (
<>
<div className="Text2">
{this.state.prjName}
</div>
<div className="Text2small">
Please select the quantity of buildings from the list below
</div>
<ul>
{this.state.units.map((object, i) => (
<div className="Card-center">
<Card key={i} style={{ width: "50%", justifyContent: "center" }}>
<Card.Body>{object.designName}</Card.Body>
<Card.Footer>
<input
className="Number-picker"
type="number"
placeholder="0"
onChange={this.handleQuantity}
/>
</Card.Footer>
</Card>
</div>
))}
</ul>
Thanks in advance!

You have to pass the change event, unit object and the index to handleQuantity and then paste your changed unit as new object in between unchanged units.
Here is the code:
<input
className="Number-picker"
type="number"
placeholder="0"
onChange={(event) => this.handleQuantity(event, object, i)}
/>;
And the code for handleQuantity
handleQuantity = (event, unit, index) => {
const inputedNumber = +event.target.value; // get your value from the event (+ converts string to number)
const changedUnit = { ...unit, quantity: inputedNumber }; // create your changed unit
// place your changedUnit right in between other unchanged elements
this.setState((prevState) => ({
units: [
...prevState.units.slice(0, index),
changedUnit,
...prevState.units.slice(index + 1),
],
}));
}

Related

Inserting only unique ID in form value in React

I am new to React, there are two input fields in the application, one is for ID and another for Name, There are two components I've used, in the parent component I've maintained all the state and form in separate another component. My aim is to check the id which is a input from the user, id should be unique every time, if it's same, an alert should popup and the focus turns to ID input field, and it should do the same until the ID is different from all the objects(state object)
My app.js file is,
import React, { Component } from "react";
import Form from "./Form";
export default class App extends Component {
state = {
names: [
/*
{id: 1,name: "Aashiq"}
*/
],
};
renderTable() {
return this.state.names.map((eachName) => {
const { id, name } = eachName;
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>
<input
type="button"
value="Delete"
onClick={() => this.deleteName(eachName.id)}
/>
</td>
</tr>
);
});
}
deleteName = (id) => {
console.log("ID object", id);
this.state.names &&
this.setState({
names: this.state.names.filter((name) => name.id !== id),
});
};
addName = (newName) => {
this.setState({
names: [newName, ...this.state.names],
});
};
render() {
return (
<>
<Form onSubmit={this.addName} names={this.state.names} />
{/* Table */}
<br />
<table id="details">
<tbody>
<tr>
<th>ID</th>
<th>Names</th>
<th>Operation</th>
</tr>
{/* Render dynamic rows
*/}
{this.renderTable()}
</tbody>
</table>
</>
);
}
}
You can see I try to render the data as table and we can delete the row data also
The form.js file is,
import React, { useState } from "react";
// import { uniqueId } from "lodash";
export default function Form(props) {
const [name, setName] = useState("");
const [id, setId] = useState();
const handleSubmit = (e) => {
e.preventDefault();
handleChangeandValidate();
};
const handleChangeandValidate = () => {
const { onSubmit, names } = props;
console.log("Object keys length", Object.keys(names).length);
if (Object.keys(names).length !== 0) {
names.map((name) => {
if (name.id === id) {
alert("Enter unique id");
setId("");
document.getElementById("ID").focus();
} else {
//if different id
onSubmit({ id: id, name: name });
setName("");
setId("");
}
return null;
});
} else {
onSubmit({ id: id, name: name }); // first time
setName("");
setId("");
}
};
return (
<form onSubmit={handleSubmit} id="myform">
<label style={{ fontSize: "20px", fontWeight: "bold" }}>
Name: {""}
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</label>{" "}
<label style={{ fontSize: "20px", fontWeight: "bold" }}>
ID: {""}
<input
type="number"
onChange={(e) => setId(e.target.value)}
required
value={id}
id="ID"
/>
</label>
{""}
<input type="submit" value="Submit" />
</form>
);
}
You can see I've tried to get the state and onSubmit function from the parent component(app.js) and done some logic like comparing all the ID's, but this logic throws some error, please somebody come up with a good solution.
Thanks in advance!
I have modified your code a bit and here is a working example.
Here is what I did:
I used createRef() to create two references that refer to each input field named nameInputRef and idInputRef.
I added ref={nameInputRef} and ref={idInputRef} so that we can get their values on submit.
On submit, I get the values of the name + id using their refs.
to search for whether the ID exists or not, I used Array.find() which would return undefined if the same id doesn't exist in the list of names coming from the props.
in addName(), I used setState() but in the param I used a function to make sure I get the latest list of names as updating the state is asynchronous. Inside I also used ES6's destructuring feature to make a copy of the current list, push the new name to it and then update the state with the new list of names.

Map function in Reactjs is putting addition objects in the array

I am trying to have a set of input fields right after one another like a terminal.
The Page component looks like this
import React, { Component } from "react";
import NewLine from "./newLine";
export class Page extends Component {
state = {
numberOfLine: 0,
lines: [{ value: "", id: 0, displayInputs: true}]
};
render() {
return (
<div className="container">
<div className="terminal">
<p className="prompt">
Hey there! This is a pre rendered line.
</p>
{this.state.lines.map(l => (
<NewLine
key={this.state.numberOfLine}
handelWhatever={this.handelWhatever}
line={l}
></NewLine>
))}
</div>
</div>
);
}
handelWhatever = (string_value, Tid) => {
// console.log(string_value, Tid);
// console.log(this.state.lines.filter(l => l.id != Tid));
const num = this.state.numberOfLine + 1;
this.setState({
numberOfLine: this.state.lines.length + 1,
lines: [
...this.state.lines.filter(line => line.id !== Tid),
{ value: string_value, id: Tid, displayInput: false },
{ value: "", id: num, displayInput: true }
]
});
};
export default Page;
and my NewLine component looks like this
import React, { Component } from "react";
export class NewLine extends Component {
state = {
id: this.props.line.id,
value: this.props.line.value,
displayInput: this.props.line.displayInput
};
render() {
return (
<React.Fragment>
<p className=output>
{this.state.displayInput && (
<input
type="text"
className="here"
value={this.state.value}
onChange={this.handleChange}
onKeyDown={this.handelEnter}
/>
)}
{this.state.value}
</p>
</React.Fragment>
);
}
handleChange = event => {
this.setState({ value: event.target.value });
};
handelEnter = event => {
if (event.key === "Enter") {
this.props.handelWhatever(event.target.value, this.state.id);
}
};
}
export default NewLine;
When I enter the "something" in the input it should make a NewLine component and delete the input from the previous one so that user can type on the newly rendered line that is why I have bool in the New Line state.
The states updates perfectly but when i user input it takes all the previous ones and render them, i.e,
Initial Stage
> Hey there! This is a pre rendered line.
>
User Input : 'ls'
> Hey there! This is a pre rendered line.
> ls
>
User Input : 'cd'
> Hey there! This is a pre rendered line.
> ls
> ls
> cd
and so on
I don't know what is going on I tried printing the state of the Parent component and it has desired number of lines In the map if I do console.log just after one input I will get
{value: "ls", id: 1, displayInput: false}
{value: "ls", id: 1, displayInput: false}
{value: "", id: 2, displayInput: true}
console logging in map is like this
{this.state.lines.map(l => {
console.log(l);
return (
<NewLine
key={this.state.numberOfLine}
handelWhatever={this.handelWhatever}
line={l}
></NewLine>
);
})}
You need to use the id of the line for the key for each NewLine component.
Also, you need to use {this.props.line.value} instead of {this.state.value} in the NewLine component.
See below
<React.Fragment>
<p className=output>
{this.state.displayInput && (
<input
type="text"
className="here"
value={this.state.value}
onChange={this.handleChange}
onKeyDown={this.handelEnter}
/>
)}
{this.props.line.value} OR {this.state.value}
</p>
</React.Fragment>
See this codepen.
In the code pen I use a div instead of React.Fragment but that's just because the fragment was throwing an error in a codepen.
EDIT
It actually works with this.state.value so depending on use case, both this.props.line.value and this.state.value works.

Not rendering JSX from function in React

The function is getting the value of a button click as props. Data is mapped through to compare that button value to a key in the Data JSON called 'classes'. I am getting all the data correctly. All my console.logs are returning correct values. But for some reason, I cannot render anything.
I've tried to add two return statements. It is not even rendering the p tag with the word 'TEST'. Am I missing something? I have included a Code Sandbox: https://codesandbox.io/s/react-example-8xxih
When I click on the Math button, for example, I want to show the two teachers who teach Math as two bubbles below the buttons.
All the data is loading. Just having an issue with rendering it.
function ShowBubbles(props){
console.log('VALUE', props.target.value)
return (
<div id='bubbles-container'>
<p>TEST</p>
{Data.map((item,index) =>{
if(props.target.value == (Data[index].classes)){
return (
<Bubble key={index} nodeName={Data[index].name}>{Data[index].name}
</Bubble>
)
}
})}
</div>
)
}
Sandbox Link: https://codesandbox.io/embed/react-example-m1880
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
const circleStyle = {
width: 100,
height: 100,
borderRadius: 50,
fontSize: 30,
color: "blue"
};
const Data = [
{
classes: ["Math"],
name: "Mr.Rockow",
id: "135"
},
{
classes: ["English"],
name: "Mrs.Nicastro",
id: "358"
},
{
classes: ["Chemistry"],
name: "Mr.Bloomberg",
id: "405"
},
{
classes: ["Math"],
name: "Mr.Jennings",
id: "293"
}
];
const Bubble = item => {
let {name} = item.children.singleItem;
return (
<div style={circleStyle} onClick={()=>{console.log(name)}}>
<p>{item.children.singleItem.name}</p>
</div>
);
};
function ShowBubbles(props) {
var final = [];
Data.map((item, index) => {
if (props.target.value == Data[index].classes) {
final.push(Data[index])
}
})
return final;
}
function DisplayBubbles(singleItem) {
return <Bubble>{singleItem}</Bubble>
}
class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {
json: [],
classesArray: [],
displayBubble: true
};
this.showNode = this.showNode.bind(this);
}
componentDidMount() {
const newArray = [];
Data.map((item, index) => {
let classPlaceholder = Data[index].classes.toString();
if (newArray.indexOf(classPlaceholder) == -1) {
newArray.push(classPlaceholder);
}
// console.log('newArray', newArray)
});
this.setState({
json: Data,
classesArray: newArray
});
}
showNode(props) {
this.setState({
displayBubble: true
});
if (this.state.displayBubble === true) {
var output = ShowBubbles(props);
this.setState({output})
}
}
render() {
return (
<div>
{/* {this.state.displayBubble ? <ShowBubbles/> : ''} */}
<div id="sidebar-container">
<h1 className="sidebar-title">Classes At School</h1>
<h3>Classes To Search</h3>
{this.state.classesArray.map((item, index) => {
return (
<button
onClick={this.showNode}
className="btn-sidebar"
key={index}
value={this.state.classesArray[index]}
>
{this.state.classesArray[index]}
</button>
);
})}
</div>
{this.state.output && this.state.output.map(item=><DisplayBubbles singleItem={item}/>)}
</div>
);
}
}
ReactDOM.render(<Sidebar />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The issue here is ShowBubbles is not being rendered into the DOM, instead (according the sandbox), ShowBubbles (a React component) is being directly called in onClick button handlers. While you can technically do this, calling a component from a function will result in JSX, essentially, and you would need to manually insert this into the DOM.
Taking this approach is not very React-y, and there is usually a simpler way to approach this. One such approach would be to call the ShowBubbles directly from another React component, e.g. after your buttons using something like:
<ShowBubbles property1={prop1Value} <etc...> />
There are some other issues with the code (at least from the sandbox) that you will need to work out, but this will at least help get you moving in the right direction.

Push dynamically added html list item into last array

How can i push html into the last array. I was trying to add an item and supposed be add instantly into list array. The cod is working except I'm struggling to add new list into last array.
function addItem(id,name){
const array = JSON.parse(localStorage.getItem('categories'));
array.push({
name: name,
id:id,
});
//<li>{name}</li> push this into last array
localStorage.setItem('categories',JSON.stringify(array));
}
{categories.map(function(item, key){
return <div>
<ul>
<li>item.name</li>
</ul>
<button onClick={() => addItem(item.id,'value name')}>Add</button>
</div>
})}
Something looks wrong in your example. I have added a complete exampl. You can maintain localStorage and State both. I hope this example helps you.
You mistake is that while adding new item you are pushing it to localStoage due to which react dom does not get rerendered. You have to update the value of state for that.
class App extends React.Component {
constructor() {
super();
this.state = {
categories: [
{
name: "Hello",
id: 1
},
{
name: "World",
id: 2
}
]
};
this.addItem = this.addItem.bind(this);
this.SaveToLocalStorage = this.SaveToLocalStorage.bind(this);
}
SaveToLocalStorage() {
const categories = this.state.categories;
localStorage.setItem("categories", JSON.stringify(categories));
}
addItem(id, name) {
const categories = this.state.categories;
categories.push({
name: name,
id: id
});
this.setState({ categories });
//localStorage.setItem("categories", JSON.stringify(categories));
}
render() {
let categories = this.state.categories;
const test = categories.map(item => (
<div key={item.id}>
<li>{item.name}</li>
</div>
));
return (
<div>
{test}
<button onClick={() => this.addItem(Date.now(), "Item")}>
Click to Add More
</button>
<button onClick={() => this.SaveToLocalStorage()}>
Save To LocalStorage{" "}
</button>
</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById("root"));
<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>
I guess this is what you are asking for. You just need to set it to state and re-render it when ever you are trying to add an element to list/array. I don't know why you are setting it to local storage but you can do it from state directly if your intention is to just store the previous array for future additions.
import React, { Component } from "react";
class App extends Component {
state = {};
constructor(props){
super(props);
this.state={
arr = []
}
}
addItem(id, name) {
const array = JSON.parse(localStorage.getItem("categories"));
array.push({
name: name,
id: id
});
//<li>{name}</li> push this into last array
localStorage.setItem("categories", JSON.stringify(array));
this.setState({arr:array});
}
renderList = () => {
return this.state.array.map(function(item, key) {
return (
<div>
<ul>
<li>item.name</li>
</ul>
<button onClick={() => addItem(item.id, "value name")}>Add</button>
</div>
);
});
};
render() {
return <div>{this.renderList()}</div>;
}
}
export default App;

Custom component not getting rendered properly on this basic ReactJS app

I have a very basic ReactJS app which uses Redux which contains the following components:
PanelMaterialSize > Select
/src/controls/PanelMaterialSize/PanelMaterialSize.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import './PanelMaterialSize.scss';
import Select from '../Select/Select';
import { setThemeList } from '../../store/AppConfig/actions';
class PanelMaterialSize extends Component {
componentDidMount() {
this.n = 1;
setInterval(() => {
let themeList = [
{ value: this.n, text: 'Option ' + this.n },
{ value: this.n + 1, text: 'Option ' + (this.n + 1) },
{ value: this.n + 2, text: 'Option ' + (this.n + 2) },
];
this.props.setThemeList(themeList);
this.n += 3;
}, 1000);
}
render() {
return (
<div className="partial-designer-panel-material-size">
<div>
<div className="label-input">
<div className="label">MATERIAL</div>
<div className="input">
<Select data={this.props.themeList} style={{ width: '100%' }} />
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (appState) => {
return {
themeList: appState.appConfig.themeList,
}
}
const mapDispatchToProps = (dispatch) => {
return {
setThemeList: (themeList) => dispatch(setThemeList(themeList)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PanelMaterialSize);
In my opinion the Redux logic is fine because I have tested by doing couple of things.
My problem is that when the render(...) method of: PanelMaterialSize gets called, the component: Select doesn't get rendered with the new data (which changes every one second).
Here you have a Codesandbox.io you can play with (preferable use Chrome):
https://codesandbox.io/s/03mj405zzv
Any idea on how to get its content changed properly?
If possible, please, provide back a new Codesandbox.io with your solution, forked from the previous one.
Thanks!
the problem is here in your select component.
you are passing initially empty array and checking your component with this.state.data props, next time reducer change your this.state.data will not update the data. because you initialize in constructor. constructor only invoke once when component mount.
SOLVED DEMO LINK
The Problem is in your select render method:
render() {
let data = this.state[this.name];
return (
<div className="control-select" {...this.controlProps}>
<div className="custom-dropdown custom-dropdown--grey">
<select className="custom-dropdown__select custom-dropdown__select--grey">
//change data with this.props.data
{this.props.data.length > 0 &&
this.props.data.map((elem, index) => {
return (
<option value={elem.value} key={index}>
{elem.text}
</option>
);
})}
</select>
</div>
</div>
);
}

Categories

Resources