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.
Related
I created front-end for json and now I want to create CMS to add new things to database.
My problem start with update state because something not work proper.
const [object, setObject] = useState([{ name: "",
Description: "",
price: {
A: "",
B: "",
C: "", }}])
const changeIloscSztuk = (event, index) => {
const { name, value } = e.target;
const tempProducts = object.map((el, i) => {
if(i === index) {
return {
...el,
[name]: value
};
}
return el;
});
setObject(tempProducts)
}
const handleChange = (e, index) => {
const { name, value } = e.target;
const tempProducts = object.map((el, i) => {
if(i === index) {
return {
...el,
[name]: value
};
}
return el;
});
setObject(tempProducts);
};
return (
<>
<Container style={{backgroundColor: "black", color: "white"}}>
{object.map((name,i)=>{
return(<>
{name.name}
</>)
})}
</Container>
<form onSubmit={setDodatkowe} ref={formData}>
Dodaj nowy moduł
<input type="submit" value="dodaj" />
</form>
{object.length>0 ?
<form ref={formData}>
<Table responsive striped bordered hover >
<thead><tr><td>Nazwa usługi</td><td>usun</td></tr></thead>
<tbody>
{object.map((props, index) => (
<React.Fragment key={index}>
<tr>
<tr>
<td>Nazwa <input type="text" className="Nazwa" name="name" onChange={handleChange} /> </td>
<td> Opis<input type="text" className="Description" name="Description" onChange={handleChange}/> </td>
<td>Wielkość <input type="text" className="Wielkosc" name="Wielkosc" onChange={handleChange}/> </td></tr>
</React.Fragment>
))}
</tbody>
</Table>
</form> : ""}
</Container>
after click "dodaj" I see lots of inputs so it is correct. But when I write in field "name" something the state is not updating. Without this step building database is not possible.
The reason for that is that state variables in React are immutable. You cannot simply do object.description = "something". If you insist on having one big nested object, you need to make a copy of the entire object whenever a single property changes.
A common way to do so is
const newObject = JSON.parse(JSON.stringify(object);
// make modifications, for example:
newObject.Description = "something";
setObject(newObject);
React UseStates are immutable. Bit of a pain, but you can use Immer to get rid of this.
npm i immer
Example of immer:
import produce from "immer"
const nextState = produce(baseState, draft => {
draft[1].done = true
draft.push({title: "Tweet about it"})
//draft is muttable
})
I have a React component that renders an array with an input field and a radio button. The input field is working fine but when clicking the radio button even though the state gets updated, the value is not reflected in the UI.
Following is the implementation of the React component
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [subTemplate, setSubTemplate] = useState([
{ name: 'main', outputFormat: 'html' },
{ name: 'something else', outputFormat: 'text' }
]);
const handleInputChange = (value, row, key) => {
const updatedSubTemplate = subTemplate.map(item => {
if (item.name === row.name) {
return {
...item,
[key]: value
};
}
return item;
});
console.log('updatedSubTemplate', updatedSubTemplate); // updatedSubTemplate consists of the modified value
setSubTemplate(updatedSubTemplate);
};
const renderSubTemplates = () => {
return subTemplate.map((item, index) => {
return (
<div key={index}>
<input
type="text"
value={item.name}
onChange={e => {
handleInputChange(e.target.value, item, 'name');
}}
/>
<div>
<input
type="radio"
name="html"
checked={item.outputFormat === 'html'}
onChange={e => {
handleInputChange(e.target.name, item, 'outputFormat');
}}
/>
HTML
</div>
<div>
<input
type="radio"
name="text"
checked={item.outputFormat === 'text'}
onChange={e => {
handleInputChange(e.target.name, item, 'outputFormat');
}}
/>
TEXT
</div>
</div>
);
});
};
return <div>{renderSubTemplates()}</div>;
}
Following is a stackblitz link for the above implementation : https://stackblitz.com/edit/react-ugat24
Note: The radio button works as expected if there's only 1 element in the array.
It is because you have different value in name attribute.
Radio buttons are used to select one option from a list of options. so they need to have the same name to group them .
<input
type="radio"
name={`group-${index}`}
checked={item.outputFormat === 'html'}
onChange={e => {
handleInputChange('html', item, 'outputFormat');
}}
/>
Working Sample
https://stackblitz.com/edit/react-4xxpev
There are 2 issues in your code.
You are passing the same name attribute for all the radio inputs.
You are passing the wrong value for the radio elements.
Use template literals to append the index value to each set of radio group elements in the array.
Here's working code
References
Radio Input
Template literals
Don't get this confused with checking each radio button I have on the page. I want to implement a check all button that sets the value of a nested object state equal to a certain value. I am storing each question in a nested state. Ex.
formQuestions({
kitchen: [question,question2,question3],
living: [question,question2,question3]
})
Four radio buttons are being made for each question. Now one radio button can only be selected at once. Each radio button has its' own value. Ex. `"Good", "Fair", "Poor", "N/A".
When a radio button is selected a state is generated dynamically for that section and question. Ex.
formAnswers({
kitchen: {
question: "Good"
question2: "Poor"
}
})
The goal here is the button that I want to create that checks only one value for each question Ex. clicks button question: "Good", question2: "Good" etc..
For me to set the state of a dynamic value I would need the "Section name" lets call it Name and the "Question" we'll call it question. That would give me access to the value like so formAnswers[Name][question]: value
I am trying to set that state from a component called SectionHeader. These contain the buttons.
SectionHeader.js
import { FormAnswersContext, FormQuestionsContext } from "../../Store";
function SectionHeader({ title, name }) {
const [formAnswers, setFormAnswers] = useContext(FormAnswersContext);
const [formQuestions, setFormQuestions] = useContext(FormQuestionsContext);
return (
<div>
<h1 className={styles["Header"]}>{title}</h1>
<div className={styles["MarkAllWrapper"]}>
<button className={styles["MarkAll"]}>
Mark all items as "Good" in this section
</button>
<br />
<button className={styles["MarkAll"]}>
Mark all items as "N/A" in this section
</button>
</div>
</div>
);
}
The parent of Section Header and the rest of the form code excluding the child radio buttons which I have explained, are in another component LivingRoom.js
LivingRoom.js
import { FormQuestionsContext, FormAnswersContext } from "../../Store";
function LivingRoomForm({ Name }) {
const [expanded, setExpanded] = useState(false);
const [formQuestions, setFormQuestions] = useContext(FormQuestionsContext);
const [formAnswers, setFormAnswers] = useContext(FormAnswersContext);
const array = formQuestions.living;
const onChange = (e, name) => {
const { value } = e.target;
setFormAnswers((state) => ({
...state,
[Name]: { ...state[Name], [name]: value },
}));
};
const handleOpen = () => {
setExpanded(!expanded);
};
return (
<div>
<Button
className={styles["CollapseBtn"]}
onClick={handleOpen}
style={{ marginBottom: "1rem", width: "100%" }}
>
<p>LIVING ROOM INSPECTION</p>
<FontAwesome
className="super-crazy-colors"
name="angle-up"
rotate={expanded ? null : 180}
size="lg"
style={{
marginTop: "5px",
textShadow: "0 1px 0 rgba(0, 0, 0, 0.1)",
}}
/>
</Button>
<Collapse className={styles["Collapse"]} isOpen={expanded}>
<Card>
<CardBody>
{array ? (
<div>
<SectionHeader title="Living Room Inspection" name={Name} />
<div
className={styles["LivingRoomFormWrapper"]}
id="living-room-form"
>
{array.map((question, index) => {
const selected =
formAnswers[Name] && formAnswers[Name][question]
? formAnswers[Name][question]
: "";
return (
<div className={styles["CheckboxWrapper"]} key={index}>
<h5>{question}</h5>
<Ratings
section={Name}
question={question}
onChange={onChange}
selected={selected}
/>
</div>
);
})}
</div>
<br />
<ImageUploader name="living" title={"Living Room"} />
</div>
) : (
<div></div>
)}
</CardBody>
</Card>
</Collapse>
</div>
);
}
If there is anything I am missing please let me know, I would be happy to share it. Cheers
Edit: for anyone that needs the radio buttons component.
Ratings.js
import React from "react";
import { FormGroup, CustomInput } from "reactstrap";
function Ratings({ selected, section, question, onChange }) {
return (
<div>
<FormGroup>
<div>
<CustomInput
checked={selected === "Good"}
onChange={(e) => onChange(e, question)}
type="radio"
id={`${section}_${question}_Good`}
value="Good"
label="Good"
/>
<CustomInput
checked={selected === "Fair"}
onChange={(e) => onChange(e, question)}
type="radio"
id={`${section}_${question}_Fair`}
value="Fair"
label="Fair"
/>
<CustomInput
checked={selected === "Poor"}
onChange={(e) => onChange(e, question)}
type="radio"
id={`${section}_${question}_Poor`}
value="Poor"
label="Poor"
/>
<CustomInput
checked={selected === "N/A"}
onChange={(e) => onChange(e, question)}
type="radio"
id={`${section}_${question}_NA`}
value="N/A"
label="N/A"
/>
</div>
</FormGroup>
</div>
);
}
I do not completely understand your question, I am sorry but I think this will help you.
Here is an implementation of radio buttons using react -
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
handleChange = e => {
const { name, value } = e.target;
this.setState({
[name]: value
});
};
render() {
return (
<div className="radio-buttons">
Windows
<input
id="windows"
value="windows"
name="platform"
type="radio"
onChange={this.handleChange}
/>
Mac
<input
id="mac"
value="mac"
name="platform"
type="radio"
onChange={this.handleChange}
/>
Linux
<input
id="linux"
value="linux"
name="platform"
type="radio"
onChange={this.handleChange}
/>
</div>
);
}
}
After a few attempts, I was able to figure out the solution to this issue.
The key here was to figure out a way to get gather each question so that it may be used as a key when setting the state. As my questions were stored in a ContextAPI, I was able to pull them out like so...
this may not be the best solution however it worked for me.
const setStateGood = () => {
formQuestions[name].map((question) => {
setFormAnswers((state) => ({
...state,
[name]: { ...state[name], [question]: "Good" },
}));
});
};
const setStateNA = () => {
formQuestions[name].map((question) => {
setFormAnswers((state) => ({
...state,
[name]: { ...state[name], [question]: "N/A" },
}));
});
};
I was able to map through each question since the name is being passed through props is a key inside the actual object, formQuestions[name]. Because i'm mapping through each one I can set that question as a key and return the new state for each question to whatever I would like.
However, if I was to create an onClick={setState('Good')}, React didn't like that and it created an infinite loop. I will look for more solutions and update this post if I find one.
A working example of my problem can be found at:
https://codepen.io/RyanCRickert/pen/vYYQeaW
I am prop drilling a function two levels and passing that function along with an index to a rendered component. When a name is submitted it renders a new component which shows the name and div which has an onClick (X). I am trying to receive the index of where the name is located in the array which it lives so that I may splice it out when the button is clicked.
If I enter the name "Bob" for example, then click the div with the listener I can console log the event.target. Using the above example I get "<div class='person-item__X' value='0'>X</div>" for event.target and undefined for event.target.value. The value is being assigned as <div onClick={props.removeName} class="person-item__X" value={props.value}>X</div>.
Am I just unable to grab the value of a div in such a manor? Or is there something that I am missing? Thank you
Change these to your code
const PersonListItem = props => (
<div class="person-item">
<div class="person-item__name">{props.name}</div>
<div onClick={() => props.removeName(props.value)} class="person-item__X" value={props.value}>X</div>
</div>
);
Inside PeopleList replace this line
<PersonListItem key={index} name={person} value={index} removeName={(id) => props.removeName(id)} />
Inside TeamGenerator replace this line
<PeopleList people={this.state.names} removeName={(id) => this.handleRemoveName(id)} />
now in handleRemoveName you will recieve a id of the item on which X was clicked
handleRemoveName = id => {
const currentArr = this.state.names;
console.log(id);
}
In your case, to grab the value inside this div, you should use ref API.
Your code should look like this:
TeamGenerator.js
import React, { Component } from "react";
import CustomModal from "./Modal";
import PeopleList from "./PeopleList";
import "./index.css";
export default class App extends Component {
constructor(props) {
super(props);
// Create a ref
this.divTextRef = React.createRef();
this.state = {
names: [],
selectedName: ""
};
}
handleCloseModal = () => {
this.setState({
selectedName: ""
});
};
handleChange = e => {
this.setState({ name: e.target.value });
};
handleRemoveName = index => {
// Get your name and index this way
console.log("Your text: ", this.divTextRef.current.innerHTML);
console.log("Your index: ", index);
};
handleSubmit = e => {
e.preventDefault();
const currentNames = this.state.names;
if (this.state.name)
currentNames.push(
this.state.name[0].toUpperCase() + this.state.name.slice(1)
);
this.setState({
name: "",
names: currentNames
});
};
render() {
return (
<div className="container">
<CustomModal
selectedName={this.state.selectedName}
closeModal={this.handleCloseModal}
/>
<form onSubmit={this.handleSubmit}>
<label>
Add name:
<input
type="text"
value={this.state.name}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
<div className="people-list-container">
<PeopleList
people={this.state.names}
removeName={this.handleRemoveName}
upperRef={this.divTextRef} // Pass the ref down from your Component tree
/>
</div>
</div>
);
}
}
PeopleList.js
import React from "react";
import PersonListItem from "./PersonListItem";
export default class PeopleList extends React.Component {
render() {
return (
<div className="people-container">
<div className="people-title">List of people</div>
<div className="people-list">
{this.props.people.length === 0 ? (
<div className="people-item">
<span>No people added</span>
</div>
) : (
this.props.people.map((person, index) => (
<PersonListItem
key={index}
name={person}
value={index}
removeName={() => this.props.removeName(index)} // Passing index to the removeName function of Parent
upperRef={this.props.upperRef} // Continue passing it down to PersonListItem
/>
))
)}
</div>
</div>
);
}
}
PersonListItem.js
import React from "react";
const PersonListItem = props => (
<div className="person-item">
<div ref={props.upperRef} className="person-item__name"> // Use the passed ref
{props.name}
</div>
<div
onClick={props.removeName}
className="person-item__X"
value={props.value}
>
X
</div>
</div>
);
export default PersonListItem;
The div node does not have the value like input, so you can not grab it by your old way.
I have data from local json file that I use to create a table.
In Table class component I have the table contains top 10 movies. The data is being displayed from filteredData state variable and are well displayed after loading the table. Above table I have 2 radio buttons, to choose whether I want to search data based on column title or column genre saved in state variable radioSearch by using function searchHandler. Then I have an input field, when I enter a string in it the result is being saved in searchFieldInput state variable, by using updatedSearch function.
Finally, I have submitHandler function in this component to filter the table based on selected radio button(title/genre of the film), and after that based on entered string in input field. The filtered data I am putting into filteredData variable in order to update the state by using setState. Unfortunately no filtering is being done after hitting submit. In Table component is nested TableRow component which should display the data based on applied filtering. I don't know whether the concept of submitHandler function is wrong, and why is not filtering the data? Can somebody help.
Here is my Table component:
import React, {Component} from 'react';
import TableRow from './TableRow/TableRow';
class Table extends Component {
constructor(props) {
super(props)
this.state = {
filteredData: this.props.data,
searchFieldInput: '',
radioSearch: this.props.radioSearch,
transformed: false
}
}
updatedSearch = (event) => {
this.setState({
searchFieldInput: event.target.value
})
}
searchHandler = (e) => {
this.setState({
radioSearch: e.target.value
})
};
submitHandler = (event) => {
event.preventDefault();
if(this.state.radioSearch === "title") {
let filteredData = this.props.data.filter(column => {
return column.title.toLowerCase().indexOf(this.state.searchFieldInput.toLowerCase()) !== -1;
});
this.setState({
filteredData: filteredData
});
return this.state.filteredData;
} else if(this.state.radioSearch === "genre"){
let filteredData = this.props.data.filter(column => {
return column.genre.toLowerCase().indexOf(this.state.searchFieldInput.toLowerCase()) !== -1;
});
this.setState({
filteredData: filteredData
});
return this.state.filteredData;
}
console.log(this.state.radioSearch);
}
render() {
let filteredData = this.props.data.filter(column => {
return column.title.toLowerCase().indexOf(this.state.searchFieldInput.toLowerCase()) !== -1;
});
return(
<React.Fragment>
<div className="container-fluid">
<div className="container">
<form>
{/*Search field*/}
<input
className={"Search" + (this.state.transformed === true ?
' transformed' : '')}
type="text"
placeholder={(this.state.transformed === true ?
'' : 'Type here')}
maxLength="20"
value={this.state.searchFieldInput} required
onChange={this.updatedSearch.bind(this)}
/>
<button type="submit">
Search
</button>
{/*Radio buttons*/}
<label htmlFor="title">
<input type="radio" name="title" id="title" value="title" checked={this.state.radioSearch === "title"}
onChange={this.searchHandler}/>
title
</label>
<label htmlFor="genre">
<input type="radio" name="genre" id="genre" value="genre" checked={this.state.radioSearch === "genre"}
onChange={this.searchHandler}/>
genre
</label>
</form>
</div>
<div className="container">
<table>
<thead>
<tr>
<th>No.</th>
<th>Picture</th>
<th>Release date</th>
<th>Genre</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{this.state.filteredData.map((row, index) => {
return (
<TableRow
numeration={index + 1}
key={row.id}
row={row}
/>
)
})
}
</tbody>
</table>
</div>
</div>
</React.Fragment>
)
}
}
export default Table;
I think its because you forgot to add the function to the submit button:
<button type="submit" onSubmit={this.submitHandler.bind(this)}>
Search
</button>