I am pretty new on using React, what i'm trying to build is a dynamic form in which user can add/remove fields, the problems come when rendering after a row (field) is added
Here is my Row Component, which I use as a template to fill by props
class Row extends React.Component {
constructor(props){
super(props);
this.addLine = this.addLine.bind(this)
this.handleTitleChange = this.handleTitleChange.bind(this)
this.handleInquisitionChange = this.handleInquisitionChange.bind(this)
}
state = {
pos: this.props.pos,
title: this.props.title,
inquisition: this.props.inquisition
}
addLine() {
this.props.addLine(this.state.pos);
}
handleTitleChange = async (event) => {
await this.setState({title: event.target.value});
this.props.updateRowState(this.state.pos, this.state.title, "title")
}
handleInquisitionChange = async (event) => {
await this.setState({inquisition: event.target.value});
this.props.updateRowState(this.state.pos, this.state.inquisition, "inquisition")
}
render(){
return(
<div className="w3-row odg-line">
<div className="w3-col m2" style={{paddingRight: "8px"}}>
<input type="text" name="titolo[]" placeholder="Argomento" style={{width:"100%"}} onChange={this.handleTitleChange} required/>
</div>
<div className="w3-col m4" style={{paddingRight: "8px"}}>
<textarea form="convocazione" name="istruttoria[]" placeholder="Istruttoria" style={{width:"100%"}} onChange={this.handleInquisitionChange} required></textarea>
</div>
<div className="w3-col m1">
<button type="button" style={{padding:0, height: "24px", width: "24px"}} className="w3-red w3-button w3-hover-white" onClick={() => this.addLine()}>+</button>
</div>
</div>
)
}
}
And this is its parent Convoca, as you can see by its addLine method whenever a "plus" button is pressed it pushes a row after that and updates component state, as far as I know this should cause the component to render again but when it comes it just adds the new one after the already rendered ones
class Convoca extends React.Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this);
this.addLine = this.addLine.bind(this);
}
state = {
rows: [
{pos:0, title: "", inquisition: ""},
{pos:1, title: "", inquisition: ""}
]
}
async addLine(position){
let buffer = this.state.rows;
buffer.splice(position+1, 0, {pos: position+1, title: "", inquisition: ""})
for(let i = position+2; i<buffer.length; i++){
buffer[i].pos++;
}
await this.setState({rows: buffer})
}
handleChangeState = async (pos, val, field) => {
let buffer = this.state.rows;
if(field === "title") buffer[pos].title = (field === "title" ? val : null);
if(field === "inquisition") buffer[pos].inquisition = (field === "inquisition" ? val : null);
await this.setState({rows: buffer})
}
handleSubmit(){
console.log("submitted")
}
render() {
return(
<div className="w3-main" style={{marginLeft:"340px", marginRight:"40px"}}>
<form action="/convocazione" id="convocazione">
{ this.state.rows.map((row) => (
<Row updateRowState={(pos, val, field) => this.handleChangeState(pos, val, field)} addLine={(pos)=>this.addLine(pos)} pos={row.pos} title={row.title} inquisition={row.inquisition}></Row>)
) }
<input className="w3-red w3-button w3-hover-white" type="submit" value="Convoca"/>
</form>
</div>
);
}
}
I would implement addLine function another way
Take a look at the snippet.
const createElement = React.createElement;
class Row extends React.Component {
render() {
const {
position
} = this.props;
return createElement('div', null, [
createElement('input', {
type: "text",
value: this.props.title
}),
createElement('button', {
type: "button",
onClick: () => this.props.onAdd(position)
}, '+')
]);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: [{
id: 0,
position: 0,
title: 'id 0 position 0'
},
{
id: 1,
position: 1,
title: 'id 1 position 1'
},
]
};
this.addLine = this.addLine.bind(this);
}
addLine(position) {
const {
rows
} = this.state
const newId = rows.reduce((acc, row) => acc > row.id ? acc : row.id, 0) + 1
position = position + 1;
const newRows = [
...rows.filter(row => row.position < position),
{
id: newId,
position,
title: `id ${newId} position ${position}`
},
...rows.filter(row => row.position >= position).map(row => ({ ...row,
position: row.position + 1,
title: `id ${row.id} position ${row.position + 1}`
}))
]
newRows.sort((prev, next) => prev.position - next.position)
this.setState({
rows: newRows
})
}
render() {
const items = this.state.rows.map(item =>
createElement(Row, {
key: item.id,
title: item.title,
position: item.position,
onAdd: this.addLine
})
)
return createElement('form', null, items);
}
}
var rootElement = createElement(App, {}, )
ReactDOM.render(rootElement, 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>
Make sure every Row has key prop with unique id
Related
I'm new React developer(mainly with hooks but did not find good example with hooks), here i have antd table with search functionality, my question is when user writes something in search then user gets different result, how to cancel that search by clicking 'Reset' button ?
my code:
https://codesandbox.io/s/antd-table-filter-search-forked-mqhcn?file=/src/EventsSection/EventsSection.js
You can add an id to your input into TitleSearch.js:
<Search
id='IDYOUWANT'
placeholder="Enter Title"
onSearch={onSearch}
onChange={onChange}
style={{ width: 200 }}
/>
And add event into EventsSection.js
ResetInput = () => {
const input = document.getElementById('IDYOUWANT');
input.value = '';
this.handleSearch('');
}
....
<button
onClick={this.ResetInput}
>Reset</button>
Change IDYOUWANT with your id
run this code
Created a new function for reset value and trigger it from reset button.
function:
resetValue = () =>{
this.setState({
eventsData: eventsData
});
}
And trigger from button
<button onClick={this.resetValue}>Reset</button>
all code::
import React, { Component } from "react";
import styles from "./style.module.css";
import { EventsTable } from "../EventsTable";
import { StatusFilter } from "../StatusFilter";
import { TitleSearch } from "../TitleSearch";
const eventsData = [
{
key: 1,
title: "Bulletproof EP1",
fileType: "Atmos",
process: "match media",
performedBy: "Denise Etridge",
operationNote: "-",
updatedAt: "26/09/2018 17:21",
status: "complete"
},
{
key: 2,
title: "Dexter EP2",
fileType: "Video",
process: "Compliance",
performedBy: "Dane Gill",
operationNote: "passed",
updatedAt: "21/09/2018 12:21",
status: "inProgress"
}
];
class EventsSection extends Component {
constructor(props) {
super(props);
this.state = {
eventsData
};
}
handleFilter = (key) => {
const selected = parseInt(key);
if (selected === 3) {
return this.setState({
eventsData
});
}
const statusMap = {
1: "complete",
2: "inProgress"
};
const selectedStatus = statusMap[selected];
const filteredEvents = eventsData.filter(
({ status }) => status === selectedStatus
);
this.setState({
eventsData: filteredEvents
});
};
handleSearch = (searchText) => {
const filteredEvents = eventsData.filter(({ title }) => {
title = title.toLowerCase();
return title.includes(searchText);
});
this.setState({
eventsData: filteredEvents
});
};
handleChange = (e) => {
const searchText = e.target.value;
const filteredEvents = eventsData.filter(({ title }) => {
title = title.toLowerCase();
return title.includes(searchText);
});
this.setState({
eventsData: filteredEvents
});
};
resetValue = () =>{
this.setState({
eventsData: eventsData
});
}
render() {
return (
<section className={styles.container}>
<header className={styles.header}>
<h1 className={styles.title}>Events</h1>
<button onClick={this.resetValue}>Reset</button>
<TitleSearch
onSearch={this.handleSearch}
onChange={this.handleChange}
className={styles.action}
/>
</header>
<EventsTable eventsData={this.state.eventsData} />
</section>
);
}
}
export { EventsSection };
Here is what i did in order to solve it:
i added onClick on the button
<button onClick={this.resetSearch}>Reset</button>
Then in the function i put handleSearch to '', by doing this it reset the table:
resetSearch = () =>{
this.handleSearch('')
}
the array is stored in local storage by clicking on the “More” button 2 news is added to DOM
How should I implement this?
const ARR= [
{
id: 1,
Name: 'Name1',
text:'lorem ipsum'
},
{
id: 2,
Name: 'Name2',
text:'lorem ipsum'
},
{
id: 3,
Name: 'Name3',
text:'lorem ipsum'
},
{
id: 4,
Name: 'Name4',
text:'lorem ipsum'
},
];
10 obj
here we save the array in localStorage
and where to execute its JSON.parse (localStorage.getItem ('news')) and I don’t understand how to implement work with local storage by click
```
localStorage.setItem('news', JSON.stringify(ARR));
class NewsOne extends PureComponent {
constructor() {
super();
this.state = {
listItem: ARR,
formAdd: false,
};
this.createItem = this.createItem.bind(this);
this.updateItem = this.updateItem.bind(this);
this.removeItem = this.removeItem.bind(this);
this.addForm = this.addForm.bind(this);
}
updateItem(item) {
const { listItem } = this.state;
this.setState({
listItem: listItem.map(elem => (
elem.id === item.id ? item : elem
))
});
}
removeItem(itemId) {
const { listItem } = this.state;
this.setState({
listItem: listItem.filter(item => item.id !== itemId)
});
}
createItem(item) {
const { listItem } = this.state;
this.setState({
listItem: [item, ...listItem],
});
}
addForm() {
const { formAdd } = this.state;
this.setState({
formAdd: !formAdd,
})
}
render() {
const { listItem, formAdd } = this.state;
return(
<>
<div className="box">
<Title />
<List
data={listItem}
removeFromProps={this.removeItem}
updateFromProps={this.updateItem}
/>
</div>
<button className ="addnews" onClick = {this.addForm}>
Add
</button>
</>
);
}
}
A class that defaultly displays 2 elements on page.
I tried here to interact with the array from localStorage, but it fails
class List extends PureComponent {
constructor() {
super();
this.state = {
count: 2,
}
this.addObj = this.addObj.bind(this);
}
addObj() {
const { count } = this.state;
this.setState({
count: count + 2,
});
}
render() {
const { count } = this.state;
const { data } = this.props;
return(
<>
<ul>
{
data.slice(0, count).map(item => (
<>
<Item
key={item.id}
item={item}
/>
</>
))
}
</ul>
<button className ="addnews" onClick = {this.addObj}>
More
</button>
</>
);
}
}
Table on Render
Table on Render after new row
Table on Render after second new row
I have a page rendering servers names title etc..
It has a count of the servers that are online,offline and warning.
When it first renders it works fine, but when i add a server and it to the array.
It updates the rows but not the server count because it hasnt rendered until after i update the server count.
I used componentDidMount to fix that on startup but not on update.
Not sure if you need more information.
class App extends Component {
constructor(props) {
super(props);
this.state = {
serverCount: [],
servers: [
{
host: "192.168.57.2",
status: "Online",
title: "Server",
location: "Location"
},
{
host: "192.168.57.1",
status: "Offline",
title: "Server",
location: "Location"
},
{
host: "192.168.57.0",
status: "Warning",
title: "Server",
location: "Location"
}
]
};
this.handleFormData = this.handleFormData.bind(this);
}
handleServerCount() {
let newArr = [0,0,0,0]
this.state.servers.map(data => {
let status = data.status
if(status === "Online"){
newArr[1]++
} else if (status === "Warning") {
newArr[2]++
} else {
newArr[3]++
}
newArr[0]++
})
return newArr;
}
handleFormData(data) {
let newArr = this.handleServerCount();
let newState = this.state.servers.slice();
newState.push(data);
this.setState({
servers: newState,
serverCount: newArr
});
}
componentDidMount(){
let newArr = this.handleServerCount();
this.setState({
serverCount: newArr
})
}
render() {
return (
<Default>
<div className="upperContainer">
<ServerCount serverCount={this.state.serverCount} />
<RequestTimer />
</div>
<ServerList serverList={this.state.servers} />
<Input handleFormData={this.handleFormData} />
</Default>
);
}
}
class ServerList extends Component {
render() {
const rows = [];
this.props.serverList.forEach((server) => {
rows.push(
<ServerRow key={server.host}
title={server.title}
host={server.host}
location={server.location}
status={server.status}/>
)
})
return (
<ListTable>
<ServerHeader/>
{rows}
</ListTable>
)
}
}
const ServerCount = (props) => {
return (
<CountContainer>
<div className="circleContainer">
<div className="total serverCircle">
{props.serverCount[0]}
</div>
Total
</div>
<div className="circleContainer">
<div className="Online serverCircle">
{props.serverCount[1]}
</div>
Online
</div>
<div className="circleContainer">
<div className="Warning serverCircle">
{props.serverCount[2]}
</div>
Warning
</div>
<div className="circleContainer">
<div className="Offline serverCircle">
{props.serverCount[3]}
</div>
Offline
</div>
</CountContainer>
)
}
class Input extends Component {
constructor(props) {
super(props);
this.state = {
host: "",
title: "",
location: ""
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e){
const {name, value} = e.target;
this.setState({
[name]: value
})
}
handleSubmit(e) {
e.preventDefault();
var newServer = {
host: this.state.host,
status: "Warning",
title: this.state.title,
location: this.state.location,
}
this.setState({
host:'',
title:'',
location:''
})
this.props.handleFormData(newServer);
}
render() {
return (
<InputForm onSubmit={this.handleSubmit}>
<input name="host" value={this.state.host} onChange={this.handleChange} placeholder="10.10.10.0"></input>
<div><span>Unknown</span></div>
<input name="title" value={this.state.title} onChange={this.handleChange} placeholder="Live Server"></input>
<input name="location" value={this.state.location} onChange={this.handleChange} placeholder="Knutsford"></input>
<button type="submit"></button>
</InputForm>
);
}
}
handleServerCount() {
let newArr = [...this.state.serverCount]
this.state.servers.map(data => {
let status = data.status
if(status === "Online"){
newArr[1]++
} else if (status === "Warning") {
newArr[2]++
} else {
newArr[3]++
}
newArr[0]++
})
return newArr;
}
So my app is a form that has dropZones (amongst other things) and an Add Questions button that adds another dropZone to the form. Whenever I put an image in my dropZone and then click Add Question the image disappears. Here's a CodeSandbox of the whole app.
But if you prefer relevant code only, here's my DropZone component followed by my AddQuestionButton component:
class DropZone extends Component {
constructor(props) {
super(props);
this.dropZoneRef = React.createRef();
this.state = {
fileBlob: props.fileBlob,
fileId: props.fileId
};
this.handleChange = this.handleChange.bind(this);
this._onDragEnter = this._onDragEnter.bind(this);
this._onDragLeave = this._onDragLeave.bind(this);
this._onDragOver = this._onDragOver.bind(this);
this._onDrop = this._onDrop.bind(this);
}
handleChange(file = "") {
this.setState({
fileBlob: URL.createObjectURL(file)
});
console.log(this.state.fileBlob + "OMG")
//document.getElementsByClassName("dropZone").style.backgroundImage = 'url(' + this.state.file + ')';
}
handleUpdate(){
}
componentDidMount(event) {
this.dropZoneRef.current.addEventListener("mouseup", this._onDragLeave);
this.dropZoneRef.current.addEventListener("dragenter", this._onDragEnter);
this.dropZoneRef.current.addEventListener("dragover", this._onDragOver);
this.dropZoneRef.current.addEventListener("dragleave", this._onDragLeave);
this.dropZoneRef.current.removeEventListener("drop", this._onDrop);
window.addEventListener("dragover",function(e){
e = e || event;
e.preventDefault();
},false);
window.addEventListener("drop",function(e){
e = e || event;
e.preventDefault();
},false);
}
componentWillUnmount() {
this.dropZoneRef.current.removeEventListener("mouseup", this._onDragLeave);
this.dropZoneRef.current.removeEventListener("dragenter", this._onDragEnter);
this.dropZoneRef.current.addEventListener("dragover", this._onDragOver);
this.dropZoneRef.current.removeEventListener("dragleave", this._onDragLeave);
this.dropZoneRef.current.removeEventListener("drop", this._onDrop);
}
_onDragEnter(e) {
e.stopPropagation();
e.preventDefault();
return false;
}
_onDragOver(e) {
e.preventDefault();
e.stopPropagation();
return false;
}
_onDragLeave(e) {
e.stopPropagation();
e.preventDefault();
return false;
}
_onDrop(e, event) {
e.preventDefault();
this.handleChange(e.dataTransfer.files[0]);
let files = e.dataTransfer.files;
console.log("Files dropped: ", files);
// Upload files
console.log(this.state.fileBlob);
return false;
}
render() {
const labelId = uuid();
return (
<div>
<input
type="file"
id={labelId}
name={this.state.fileBlobId}
className="inputFile"
onChange={e => this.handleChange(e.target.files[0])}
/>
<label htmlFor={labelId} value={this.state.fileBlob}>
{this.props.children}
<div className="dropZone" id="dragbox" key={this.state.fileBlobId} ref={this.dropZoneRef} onChange={this.handleChange} onDrop={this._onDrop}>
Drop or Choose File {console.log(this.dropZoneRef)}
<img src={this.state.fileBlob} id="pic" name="file" accept="image/*" />
</div>
</label>
<div />
</div>
);
}
}
class AddQuestionButton extends Component {
addQuestion = () => {
this.props.onClick();
};
render() {
return (
<div id="addQuestionButtonDiv">
<button id="button" onClick={this.addQuestion} />
<label id="addQuestionButton" onClick={this.addQuestion}>
Add Question
</label>
</div>
);
}
}
And here's the direct parent of the DropZone component, Question:
class Question extends Component {
constructor(props) {
super(props);
this.state = {
question: props.value.question,
uniqueId: props.value.uniqueId,
answers: props.value.answers,
file: props.file
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
this.setState({
question: value
});
this.props.onUpdate({
uniqueId: this.state.uniqueId,
value
});
}
handleUpdate(event, file) {
//if ("1" == 1) // true
//if ("1" === 1) //false
var questions = this.state.questions.slice();
for (var i = 0; i < questions.length; i++) {
if (questions[i].uniqueId == event.uniqueId) {
questions[i].file = event.value;
break;
}
}
this.setState(() => ({
questions: questions
}));
console.log(event, questions);
}
render() {
return (
<div id={"questionDiv" + questionIdx} key={myUUID + questionIdx + 1}>
Question<br />
<input
type="text"
value={this.state.question}
onChange={this.handleChange}
key={this.state.uniqueId}
name="question"
/>
<DropZone file={this.state.file}/>
<Answers
updateAnswers={this.props.updateAnswers}
answers={this.state.answers}
/>
</div>
);
}
}
And Question's parent component, `Questions':
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
questions: []
};
this.handleUpdate = this.handleUpdate.bind(this);
this.removeQuestion = this.removeQuestion.bind(this);
}
handleUpdate(event) {
//if ("1" == 1) // true
//if ("1" === 1) //false
var questions = this.state.questions.slice();
for (var i = 0; i < questions.length; i++) {
if (questions[i].uniqueId == event.uniqueId) {
questions[i].question = event.value;
break;
}
}
this.setState(() => ({
questions: questions
}));
console.log(event, questions);
}
updateAnswers(answers, uniqueId) {
const questions = this.state.questions;
questions.forEach(question => {
if (question.uniqueId === uniqueId) {
question.answers = answers;
}
});
this.setState({
questions
});
}
addQuestion = question => {
questionIdx++;
var newQuestion = {
uniqueId: uuid(),
question: "",
file: { fileBlob: "", fileId: uuid()},
answers: [
{ answer: "", answerId: uuid(), isCorrect: false },
{ answer: "", answerId: uuid(), isCorrect: false },
{ answer: "", answerId: uuid(), isCorrect: false },
{ answer: "", answerId: uuid(), isCorrect: false }
]
};
this.setState(prevState => ({
questions: [...prevState.questions, newQuestion]
}));
return { questions: newQuestion };
};
removeQuestion(uniqueId, questions) {
this.setState(({ questions }) => {
var questionRemoved = this.state.questions.filter(
props => props.uniqueId !== uniqueId
);
return { questions: questionRemoved };
});
console.log(
"remove button",
uniqueId,
JSON.stringify(this.state.questions, null, " ")
);
}
render() {
return (
<div id="questions">
<ol id="quesitonsList">
{this.state.questions.map((value, index) => (
<li key={value.uniqueId}>
{
<RemoveQuestionButton
onClick={this.removeQuestion}
value={value.uniqueId}
/>
}
{
<Question
onUpdate={this.handleUpdate}
value={value}
number={index}
updateAnswers={answers =>
this.updateAnswers(answers, value.uniqueId)
}
/>
}
{<br />}
</li>
))}
</ol>
<AddQuestionButton onClick={this.addQuestion} />
</div>
);
}
}
Thanks!
Only keep state in the top level component, and you should be good to go.
import React, { Component } from "react";
import "./App.css";
var uuid = require("uuid-v4");
// Generate a new UUID
var myUUID = uuid();
// Validate a UUID as proper V4 format
uuid.isUUID(myUUID); // true
class DropZone extends Component {
constructor(props) {
super(props);
this.dropZoneRef = React.createRef();
this.handleChange = this.handleChange.bind(this);
this._onDragEnter = this._onDragEnter.bind(this);
this._onDragLeave = this._onDragLeave.bind(this);
this._onDragOver = this._onDragOver.bind(this);
this._onDrop = this._onDrop.bind(this);
}
handleChange(file = "") {
this.props.updateFile(URL.createObjectURL(file), this.props.file.fileId);
//document.getElementsByClassName("dropZone").style.backgroundImage = 'url(' + this.state.file + ')';
}
componentDidMount(event) {
this.dropZoneRef.current.addEventListener("mouseup", this._onDragLeave);
this.dropZoneRef.current.addEventListener("dragenter", this._onDragEnter);
this.dropZoneRef.current.addEventListener("dragover", this._onDragOver);
this.dropZoneRef.current.addEventListener("dragleave", this._onDragLeave);
this.dropZoneRef.current.removeEventListener("drop", this._onDrop);
window.addEventListener(
"dragover",
function(e) {
e = e || event;
e.preventDefault();
},
false
);
window.addEventListener(
"drop",
function(e) {
e = e || event;
e.preventDefault();
},
false
);
}
componentWillUnmount() {
this.dropZoneRef.current.removeEventListener("mouseup", this._onDragLeave);
this.dropZoneRef.current.removeEventListener(
"dragenter",
this._onDragEnter
);
this.dropZoneRef.current.addEventListener("dragover", this._onDragOver);
this.dropZoneRef.current.removeEventListener(
"dragleave",
this._onDragLeave
);
this.dropZoneRef.current.removeEventListener("drop", this._onDrop);
}
_onDragEnter(e) {
e.stopPropagation();
e.preventDefault();
return false;
}
_onDragOver(e) {
e.preventDefault();
e.stopPropagation();
return false;
}
_onDragLeave(e) {
e.stopPropagation();
e.preventDefault();
return false;
}
_onDrop(e, event) {
e.preventDefault();
this.handleChange(e.dataTransfer.files[0]);
let files = e.dataTransfer.files;
console.log("Files dropped: ", files);
// Upload files
return false;
}
render() {
const labelId = uuid();
return (
<div>
<input
type="file"
id={labelId}
name={this.props.file.fileId}
className="inputFile"
onChange={e => this.handleChange(e.target.files[0])}
/>
<label htmlFor={labelId} value={this.props.file.fileBlob}>
{this.props.children}
<div
className="dropZone"
id="dragbox"
key={this.props.file.fileId}
ref={this.dropZoneRef}
onChange={this.handleChange}
onDrop={this._onDrop}
>
Drop or Choose File {console.log(this.dropZoneRef)}
<img
src={this.props.file.fileBlob}
id="pic"
name="file"
accept="image/*"
/>
</div>
</label>
<div />
</div>
);
}
}
class Answers extends Component {
constructor(props) {
super(props);
this.state = {
answers: props.answers
};
this.handleUpdate = this.handleUpdate.bind(this);
}
// let event = {
// index: 1,
// value: 'hello'
// };
handleUpdate(event) {
var answers = this.state.answers.slice();
for (var i = 0; i < answers.length; i++) {
if (answers[i].answerId == event.answerId) {
answers[i].answer = event.value;
break;
}
}
this.setState(() => ({
answers: answers
}));
this.props.updateAnswers(answers);
console.log(event);
}
render() {
return (
<div id="answers">
Answer Choices<br />
{this.state.answers.map((value, index) => (
<Answer
key={`${value}-${index}`}
onUpdate={this.handleUpdate}
value={value}
number={index}
name="answer"
/>
))}
</div>
);
}
}
class Answer extends Component {
constructor(props) {
super(props);
this.state = {
answer: props.value.answer,
answerId: props.value.answerId,
isCorrect: props.value.isCorrect
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
this.setState({
answer: value
});
this.props.onUpdate({
answerId: this.state.answerId,
value
});
// let sample = {
// kyle: "toast",
// cam: "pine"
// };
// sample.kyle
// sample.cam
}
render() {
return (
<div>
<input type="checkbox" />
<input
type="text"
value={this.state.answer}
onChange={this.handleChange}
key={this.state.answerId}
name="answer"
/>
{/*console.log(this.state.answerId)*/}
</div>
);
}
}
var questionIdx = 0;
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
questions: []
};
this.handleUpdate = this.handleUpdate.bind(this);
this.removeQuestion = this.removeQuestion.bind(this);
}
handleUpdate(event) {
//if ("1" == 1) // true
//if ("1" === 1) //false
var questions = this.state.questions.slice();
for (var i = 0; i < questions.length; i++) {
if (questions[i].uniqueId == event.uniqueId) {
questions[i].question = event.value;
break;
}
}
this.setState(() => ({
questions: questions
}));
console.log(event, questions);
}
updateAnswers(answers, uniqueId) {
const questions = this.state.questions;
questions.forEach(question => {
if (question.uniqueId === uniqueId) {
question.answers = answers;
}
});
this.setState({
questions
});
}
updateFile(fileBlob, fileId) {
const questions = this.state.questions;
questions.forEach(question => {
if (question.file.fileId === fileId) {
question.file.fileBlob = fileBlob;
}
});
this.setState({
questions
});
}
addQuestion = question => {
questionIdx++;
var newQuestion = {
uniqueId: uuid(),
question: "",
file: { fileBlob: {}, fileId: uuid() },
answers: [
{ answer: "", answerId: uuid(), isCorrect: false },
{ answer: "", answerId: uuid(), isCorrect: false },
{ answer: "", answerId: uuid(), isCorrect: false },
{ answer: "", answerId: uuid(), isCorrect: false }
]
};
this.setState(prevState => ({
questions: [...prevState.questions, newQuestion]
}));
return { questions: newQuestion };
};
removeQuestion(uniqueId, questions) {
this.setState(({ questions }) => {
var questionRemoved = this.state.questions.filter(
props => props.uniqueId !== uniqueId
);
return { questions: questionRemoved };
});
console.log(
"remove button",
uniqueId,
JSON.stringify(this.state.questions, null, " ")
);
}
render() {
return (
<div id="questions">
<ol id="quesitonsList">
{this.state.questions.map((value, index) => (
<li key={value.uniqueId}>
{
<RemoveQuestionButton
onClick={this.removeQuestion}
value={value.uniqueId}
/>
}
{
<Question
onUpdate={this.handleUpdate}
value={value}
number={index}
updateAnswers={answers =>
this.updateAnswers(answers, value.uniqueId)
}
updateFile={this.updateFile.bind(this)}
/>
}
{<br />}
</li>
))}
</ol>
<AddQuestionButton onClick={this.addQuestion} />
</div>
);
}
}
class Question extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
this.props.onUpdate({
uniqueId: this.props.value.uniqueId,
value
});
}
render() {
return (
<div id={"questionDiv" + questionIdx} key={myUUID + questionIdx + 1}>
Question<br />
<input
type="text"
value={this.props.value.question}
onChange={this.handleChange}
key={this.props.value.uniqueId}
name="question"
/>
<DropZone
file={this.props.value.file}
updateFile={this.props.updateFile}
/>
<Answers
updateAnswers={this.props.updateAnswers}
answers={this.props.value.answers}
/>
</div>
);
}
}
class IntroFields extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
author: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
console.log([name]);
this.setState((previousState, props) => ({
[name]: value
}));
}
render() {
return (
<div id="IntroFields">
Title:{" "}
<input
type="text"
value={this.state.title}
onChange={this.handleChange}
name="title"
/>
Author:{" "}
<input
type="text"
value={this.state.author}
onChange={this.handleChange}
name="author"
/>
</div>
);
}
}
class AddQuestionButton extends Component {
addQuestion = () => {
this.props.onClick();
};
render() {
return (
<div id="addQuestionButtonDiv">
<button id="button" onClick={this.addQuestion} />
<label id="addQuestionButton" onClick={this.addQuestion}>
Add Question
</label>
</div>
);
}
}
class RemoveQuestionButton extends Component {
removeQuestion = () => {
this.props.onClick(this.props.value);
};
render() {
return (
<div id="removeQuestionButtonDiv">
<button id="button" onClick={this.removeQuestion} key={uuid()} />
<label
id="removeQuestionButton"
onClick={this.removeQuestion}
key={uuid()}
>
Remove Question
</label>
</div>
);
}
}
class BuilderForm extends Component {
render() {
return (
<div id="formDiv">
<IntroFields />
<Questions />
</div>
);
}
}
export default BuilderForm;
You shouldn't be passing props into state like here from the Question component:
this.state = {
question: props.value.question,
uniqueId: props.value.uniqueId,
answers: props.value.answers,
file: props.file
};
Because then you're allowing two different components to have two logical sources of state that they believe to be the same. Keep one source of truth that has everything that its children depend on. If all of the children components don't depend on the data (state), consider moving it down a level to the state of the child, otherwise pass as props.
I have created two separate components and a parent component. I am trying to see how I can connect them so that I can have the dropdown for the children vanish when their checkbox is unchecked. I think I may have created this so the 2 components can't communicate, but I wanted to see if there was a way to get them to. Been trying different ways, but cannot seem to figure it out.
This is the parent component. It builds sections from some data and renders a checkbox treeview with the first (parent) checkbox having a dropdown. When the third option is selected in this dropdown, it renders in a dropdown for each child checkbox. I am trying to see if I can have the child dropdowns vanish when the checkbox is unchecked, but I can't seem to get the 2 components to communicate.
export default class CheckboxGroup extends PureComponent {
static propTypes = {
data: PropTypes.any.isRequired,
onChange: PropTypes.func.isRequired,
counter: PropTypes.number,
};
mapParents = (counter, child) => (
<li key={child.get('name')} className='field'>
<SegmentHeader style={segmentStyle} title={child.get('label')} icon={child.get('icon')}>
<div className='fields' style={zeroMargin}>
<div className='four wide field'>
<TreeCheckbox
label={`Grant ${child.get('label')} Permissions`}
counter={counter}
onChange={this.props.onChange}
/>
{child.get('items') && this.buildTree(child.get('items'), counter + child.get('name'))}
</div>
<div className='twelve wide field'>
<GrantDropdown label={child.get('label')} childItems={child.get('items')}/>
</div>
</div>
</SegmentHeader>
</li>
)
mapDataArr = (counter) => (child) => (
(counter === 0 || counter === 1000) ?
this.mapParents(counter, child)
:
<li key={child.get('name')}>
<TreeCheckbox label={child.get('label')} onChange={this.props.onChange}/>
{child.get('items') && this.buildTree(child.get('items'), counter + child.get('name'))}
</li>
)
buildTree = (dataArr, counter) => (
<ul key={counter} style={listStyle}>
{dataArr.map(this.mapDataArr(counter))}
</ul>
)
render() {
return (
<div className='tree-view'>
{this.buildTree(this.props.data, this.props.counter)}
</div>
);
}
}
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
const pointer = { cursor: 'pointer' };
class TreeCheckbox extends PureComponent {
static propTypes = {
onChange: PropTypes.func,
label: PropTypes.string,
currentPerson: PropTypes.any,
};
componentDidMount() {
if (this.props.currentPerson.get('permissions').includes(this.props.label)) {
this.checkInput.checked = true;
this.changeInput(this.checkInput);
}
}
getLiParents = (el, parentSelector) => {
if (!parentSelector) parentSelector = document; // eslint-disable-line
const parents = [];
let parent = el.parentNode;
let o;
while (parent !== parentSelector) {
o = parent;
if (parent.tagName === 'LI') parents.push(o);
parent = o.parentNode;
}
return parents;
}
traverseDOMUpwards = (startingEl, steps) => {
let elem = startingEl;
for (let i = 0; i < steps; i++) {
elem = elem.parentNode;
}
return elem;
}
markIt = (nodeElem, checkIt, indeter) => {
const node = nodeElem;
const up = this.traverseDOMUpwards(node, 1);
node.checked = checkIt;
node.indeterminate = indeter;
this.props.onChange(up.children[1].innerText, checkIt);
}
changeInput = (event) => {
const e = event === this.checkInput ? event : event.target;
const selector = 'input[type="checkbox"]';
const querySelector = (el) => el.querySelectorAll(selector);
const container = this.traverseDOMUpwards(e, 2);
const markAllChildren = querySelector(container.parentNode);
const checked = e.tagName === 'LABEL' ? !markAllChildren[0].checked : e.checked;
const siblingsCheck = (element) => {
let onesNotRight = false;
const sibling = [].slice.call(element.parentNode.children);
sibling.filter(child => child !== element).forEach(elem => {
if (querySelector(elem)[0].checked !== querySelector(element)[0].checked) {
onesNotRight = true;
}
});
return !onesNotRight;
};
const checkRelatives = (ele) => {
let el = ele;
if (el.tagName === 'DIV') el = el.parentNode;
if (el.tagName !== 'LI') return;
const parentContainer = this.traverseDOMUpwards(el, 2);
if (siblingsCheck(el) && checked) {
this.markIt(querySelector(parentContainer)[0], true, false);
checkRelatives(parentContainer);
} else if (siblingsCheck(el) && !checked) {
const parent = this.traverseDOMUpwards(el, 2);
const indeter = parent.querySelectorAll(`${selector}:checked`).length > 0;
this.markIt(querySelector(parent)[0], false, indeter);
checkRelatives(parent);
} else {
for (const child of this.getLiParents(el)) {
this.markIt(querySelector(child)[0], false, true);
}
}
};
for (const children of markAllChildren) {
this.markIt(children, checked, false);
}
checkRelatives(container);
};
getRef = (input) => { this.checkInput = input; }
render() {
const { label } = this.props;
return (
<div className='permission-item'>
<div className='ui checkbox'>
<input type='checkbox' onChange={this.changeInput} ref={this.getRef}/>
<label onClick={this.changeInput} style={pointer}>
{label}
</label>
</div>
</div>
);
}
}
const mapStatetoProps = (state) => ({
currentPerson: state.get('currentPerson'),
});
export default connect(mapStatetoProps)(TreeCheckbox);
class GrantDropdown extends AbstractSettingsComponent {
static propTypes = {
label: PropTypes.string,
currentPerson: PropTypes.any,
counter: PropTypes.number,
permissionOptions: PropTypes.any,
};
state = {
items: new List(),
}
componentDidMount() {
if (this.props.childItems) {
this.getAllChildLabels(this.props.childItems);
}
}
getAllChildLabels = (childItems) => {
let list = new List();
for (const item of childItems) {
list = list.push(item.get('label'));
if (item.get('items')) {
for (const childItem of item.get('items')) {
list = list.push(childItem.get('label'));
}
}
}
this.setState({ items: list });
}
handlePermissionChange = (label) => (e, { value }) => {
this.updatePerson(['locationsPermissionsMap', label], value);
}
mapItems = (val, i) => { // eslint-disable-line
const locationVal = this.props.currentPerson.getIn(['locationsPermissionsMap', val]);
return (
<div className={locationVal === 2 ? 'two fields' : 'field'} style={zeroMarginBottom} key={i}>
<OptionSelector
options={this.firstThreePermissionOpt()}
defaultValue={locationVal || 0}
onChange={this.handlePermissionChange(val)}
/>
{locationVal === 2 &&
<div className='field' style={zeroMarginBottom}>
<LocationMultiSelect name={val} {...this.props}/>
</div>
}
</div>
);
}
render() {
const { label, currentPerson } = this.props;
if (!currentPerson.get('permissions').includes(label)) {
return null;
}
const locationLabel = currentPerson.getIn(['locationsPermissionsMap', label]);
return (
<div className={ locationLabel === 2 ? 'two fields' : 'field'} style={zeroMarginBottom}>
<div className='field'>
<OptionSelector
options={this.getPermissionOptions()}
defaultValue={currentPerson.getIn(['locationsPermissionsMap', label]) || 0}
onChange={this.handlePermissionChange(label)}
/>
{locationLabel === 3 && this.state.items.map(this.mapItems)}
</div>
{locationLabel === 2 &&
<div className='field'>
<LocationMultiSelect name={label} {...this.props}/>
</div>
}
</div>
);
}
}
const mapStatetoProps = (state) => ({
currentPerson: state.get('currentPerson'),
locations: state.get('locations'),
});
export default connect(mapStatetoProps)(GrantDropdown);
What you can do is set couple of props to send to child component to re-render them.
Example
export default class CheckBoxComponent extends React.Component {
changeInput() {
this.props.onCheckedChanged();
}
render() {
return(
<div className='permission-item'>
<div className='ui checkbox'>
<input type='checkbox' onChange={this.changeInput} ref={this.getRef}/>
<label onClick={this.changeInput.bind(this)} style={pointer}>
{label}
</label>
</div>
</div>
)
}
}
export default class DropDownComponent extends React.Component {
renderSelect() {
// here render your select and options
}
render() {
return(
<div>
{this.props.checkboxChecked === false ? this.renderSelect : null}
</div>
)
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkboxChecked: false
};
}
onCheckedChanged() {
this.setState({ checkboxChecked: !this.state.checkboxChecked });
}
render() {
return(
<div>
<CheckBoxComponent onCheckedChanged={this.onCheckedChanged.bind(this)} />
<DropDownComponent checkboxChecked={this.state.checkboxChecked} />
</div>
)
}
}
You can set the <input/> name attribute into a corresponding property in your state so the handler can get the name of the list / input via the event parameter and set the state respectively.
Then you can conditionally render the Dropdown according to the state.
Here is a small example of such behavior:
const lists = [
[
{ value: "0", text: "im 0" },
{ value: "1", text: "im 1" },
{ value: "2", text: "im 2" }
],
[
{ value: "a", text: "im a" },
{ value: "b", text: "im b" },
{ value: "c", text: "im c" }
]
];
const DropDown = ({ options }) => {
return (
<select>
{options.map(opt => <option value={opt.value}>{opt.text}</option>)}
</select>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showList0: false,
showList1: true
};
this.toggleCheck = this.toggleCheck.bind(this);
}
toggleCheck(e) {
const listName = e.target.name;
this.setState({ [listName]: !this.state[listName] });
}
render() {
return (
<div>
{lists.map((o, i) => {
const listName = `showList${i}`;
const shouldShow = this.state[listName];
return (
<div>
<input
type="checkbox"
name={listName}
checked={shouldShow}
onChange={this.toggleCheck}
/>
{shouldShow && <DropDown options={o} />}
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>