I am in the process of creating a questionnaire. I currently have 9 questions (hence resData having id values ranging from 1-9. I have a child component, Child, that represents the question text and selection, but these components are iteratively rendered within the parent component, QF (questionnaire form).
Here is the parent component:
import React from "react";
import Child from "./Child";
import Records from "./CriterionQuestions.json";
class QF extends React.Component {
state = {
response: "",
};
handleCallback = (childData) => {
this.setState({ response: childData });
};
render() {
let applicableCriteria = [];
// Push all possible criteria to list
for (let q = 0; q < Records.length; q++) {
for (let c = 0; c < Records[q].criterion.length; c++) {
applicableCriteria.push(Records[q].criterion[c]);
}
}
const idVals = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const resData = [
{ id: 1, response: "" },
{ id: 2, response: "" },
{ id: 3, response: "" },
{ id: 4, response: "" },
{ id: 5, response: "" },
{ id: 6, response: "" },
{ id: 7, response: "" },
{ id: 8, response: "" },
{ id: 9, response: "" },
];
const { response } = this.state;
return (
<div>
<h1> Questionnaire </h1>
<form>
{resData.length > 0 &&
resData.map((id, index) => (
<Child
questionText={Records[index].question}
name={`resData.${index}.value`}
parentCallback={this.handleCallback}
></Child>
))}
<button type="submit">Click to submit</button>
</form>
</div>
);
}
}
export default QF;
And here is the child component:
import React from "react";
class Child extends React.Component {
onTrigger = (event) => {
this.props.parentCallback(event.target.booleanResponse.value);
event.preventDefault();
};
render() {
return (
<div>
<label>{this.props.questionText}</label>
<br></br>
<select name="booleanResponse" id="booleanResponse">
<option value="No">No</option>
<option value="Yes">Yes</option>
</select>
{this.onTrigger}
<br></br>
</div>
);
}
}
export default Child;
My goal is to store the response from booleanResponse and store it in resData (e.g., if question 1 is Yes then the response's value for id=1 would be Yes). However, I'm having some trouble with moving this information between the parent and child components. I've started messing with some handlers, but I don't think I have it quite right. How should I be performing a task like such? I'm still learning React, so sorry if the code is a bit messy.
Related
I have two very simple react components, QF and Child. I am trying to pass data from a json file I have to the Child class, but I keep getting a 'props' is not defined error. I feel like I'm missing something small, but my React knowledge isn't strong enough for me to find it. Is there a different way I should be passing a prop to a child component? Thank you in advance!
I am a little confused since I keep seeing different ways to set up React components.
Here is the parent component:
import React from "react";
import Child from "./Child";
import Records from "./CriterionQuestions.json";
class QF extends React.Component {
state = {
response: "",
};
handleCallback = (childData) => {
this.setState({ response: childData });
};
render() {
let applicableCriteria = [];
// Push all possible criteria to list
for (let q = 0; q < Records.length; q++) {
for (let c = 0; c < Records[q].criterion.length; c++) {
applicableCriteria.push(Records[q].criterion[c]);
}
}
const idVals = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const resData = [
{ id: 1, response: "" },
{ id: 2, response: "" },
{ id: 3, response: "" },
{ id: 4, response: "" },
{ id: 5, response: "" },
{ id: 6, response: "" },
{ id: 7, response: "" },
{ id: 8, response: "" },
{ id: 9, response: "" },
];
const { response } = this.state;
return (
<div>
<h1> Questionnaire </h1>
<Child
parentCallback={this.handleCallback}
questionText={Records[0].question}
/>
{response}
</div>
);
}
}
export default QF;
And here is the child component:
import React from "react";
class Child extends React.Component {
onTrigger = (event) => {
this.props.parentCallback(event.target.booleanResponse.value);
event.preventDefault();
};
render() {
return (
<div>
<label>{props.questionText}</label>
<form onSubmit={this.onTrigger}>
<select name="booleanResponse" id="booleanResponse">
<option value="No">No</option>
<option value="Yes">Yes</option>
</select>
<br></br>
<br></br>
<input type="submit" value="Submit" />
<br></br>
<br></br>
</form>
</div>
);
}
}
export default Child;
So I have this component called counters where the state looks something like this
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 3 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
],
};
And I'm trying to increment the value every time a corresponding button is clicked. This is the function I wrote for it.
handleIncrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters });
};
It doesn't work. And here are some additional observations.
When I console.log the local counters object (copied from state.counters) it returns an additional row at the end with id: -1 and value: NaN
The variable counter (thats being passed as a parameter) is from a child component. It's supposed to return 0 if the first button is clicked, 1 if the second button is clicked and so on. When I console.log it it seems to be returning the correct values.
by the looks of it, the problem seems to lie in the line
const index = counters.indexOf(counter);
As the value of index is always returned as -1.
You can use the index for updating the corresponding record in the array.
const idx = this.state.counters.findIndex((counter) => counter.id === id);
Complete implementation:-
import React from "react";
import "./styles.css";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 3 },
{ id: 3, value: 0 },
{ id: 4, value: 0 }
]
};
}
handleClick = (id) => {
const idx = this.state.counters.findIndex((counter) => counter.id === id);
const counters = [...this.state.counters];
counters[idx] = { ...counters[idx], value: counters[idx].value++ };
this.setState(counters);
};
render() {
return (
<div>
{this.state.counters.map((counter) => (
<div>
<button onClick={() => this.handleClick(counter.id)}>
Button {counter.id}
</button>
<span>Value {counter.value}</span>
<hr />
</div>
))}
</div>
);
}
}
Codesandbox - https://codesandbox.io/s/musing-black-cippbs?file=/src/App.js
try this for get the index:
const index = counters.findIndex(x => x.id === counter.id);
Adding my answer here just in case another confused soul stumbles upon it.
Although it seemed that the problem lied within line
const index = counters.indexOf(counter);
It actually was in the child component where the function was being invoked. Within the parameter I was passing counters.value whereas the handleincrement function within the parent component was expecting not the value, rather the complete object.
The code in its working condition is as below
Parent Component:
import React, { Component } from "react";
import Counter from "./counter";
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 3 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
],
};
handleIncrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
console.log(index);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters });
};
Child Component:
<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>
import React, { Component } from "react";
class Counter extends Component {
render() {
return (
<button
onClick={() => this.props.onIncrement(this.props.counter)}
className="btn btn-secondary btn-sm"
>
Increment
</button>
)
}
]
import React, { Component } from 'react';
import './App.css';
const list = [
{
title: 'React',
url: 'https://facebook.github.io/react/',
author: 'Jordan Walke',
num_comments: 3,
points: 4,
objectID: 0,
},
{
title: 'Redux',
url: 'https://github.com/reactjs/redux',
author: 'Dan Abramov, Andrew Clark',
num_comments: 2,
points: 5,
objectID: 1,
},
];
class App extends Component {
state = {
list,
text: 'abc',
searchTerm: ''
}
onDisMiss = (id) => {
const updateList = this.state.list.filter((item) => item.objectID != id)
return () => this.setState({ list: updateList })
}
onSearchChange = (event) => {
this.setState({ searchTerm: event.target.value })
}
isSearched = (searchTerm) => {
return (item) => item.title.toLowerCase().includes(searchTerm.toLowerCase())
}
render() {
const { searchTerm, list } = this.state
return (
<div>
<Search value={searchTerm}
onChange={this.onSearchChange}>Search</Search>
<Table list={list} pattern={searchTerm} onDissMiss={this.onDisMiss} />
</div>
);
}
}
class Search extends Component {
render() {
const { value, onChange, children } = this.props
return (
<div>
<form>
{children}<input type="text" onChange={onChange} value={value} />
</form>
</div>
);
}
}
class Table extends Component {
render() {
const { list, pattern, onDisMiss } = this.props
return (
<div>
{list.filter(isSearched(pattern)).map(item =>
<div key={item.objectID}>
<span><a href={item.url}>{item.title}</a></span>
<span>{item.author}</span>
<span>{item.num_comments}</span>
<span>{item.points}</span>
<span>
<button onClick={onDisMiss(item.objectID)} type="button">Dismiss</button>
</span>
</div>)
}
</div>
);
}
}
export default App;
Road to react Book The Table component related.I get undefined for the isSearched method. how can I fix it so it works correctly its from the book road to react it seems like the book has a few error which I have problems solving because am just learning react. can you help with the solution and why this problem is actually happening
You should put the isSearched method inside the Table class and not the App class
I have a list of buttons and I'm trying to toggle the classname when one is clicked. So that only when I click on a specific button is highlighted. I have a TagList component that looks like this:
const Tags = ({tags, onTagClick}) => {
return (
<div className="tags-container">
{ tags.map(tag => {
return (
<span
key={tag.name}
className="tag"
onClick={() => onTagClick(tag)}
>
{tag.name} | {tag.numberOfCourses}
</span>
)
})
}
</div>
)
}
And this is found in the parent component:
onTagClick = (tag) => {
this.filterCourses(tag)
}
render() {
const { tags, courses } = this.state
return (
<div>
<h1> Course Catalog Component</h1>
<Tags tags={tags} onTagClick={this.onTagClick} />
<Courses courses={courses} />
</div>
)
}
I know how I could toggle the class for a single button but I'm a little confused when it comes to a list of buttons. How can I toggle one specifically from a list of buttons? Am I going to need a seperate Tag component and add state to that one component?
EDIT:
This is what my state currently looks like:
constructor(props) {
super(props)
this.state = {
tags: this.sortedTags(),
courses: courses
}
}
And this is what filterCourses looks like:
filterCourses = (tag) => {
this.setState({
courses: courses.filter(course => course.tags.includes(tag.name))
})
}
To start, you would want to give each tag object you're working with a selected property. That will make it easier for you to toggle the class. During the rendering of that markup.
Here is the working sandbox: https://codesandbox.io/s/stupefied-cartwright-6zpxk
Tags.js
import React from "react";
const Tags = ({ tags, onTagClick }) => {
return (
<div className="tags-container">
{tags.map(tag => {
return (
<div
key={tag.name}
className={tag.selected ? "tag selected" : "tag"}
onClick={() => onTagClick(tag)}
>
{tag.name} | {tag.numberOfCourses}
</div>
);
})}
</div>
);
};
export default Tags;
Then in the Parent component, we simply toggle the selected prop (True/False) when the tag is clicked. That will update the tags-array and it gets passed back down to the Child-component which now has the new selected values.
Parent Component
import React from "react";
import ReactDOM from "react-dom";
import Tags from "./Tags";
import Courses from "./Courses";
import "./styles.css";
class App extends React.Component {
state = {
tags: [
{ id: 1, name: "math", numberOfCourses: 2, selected: false },
{ id: 2, name: "english", numberOfCourses: 2, selected: false },
{ id: 3, name: "engineering", numberOfCourses: 2, selected: false }
],
courses: [
{ name: "Math1a", tag: "math" },
{ name: "Math2a", tag: "math" },
{ name: "English100", tag: "english" },
{ name: "English200", tag: "english" },
{ name: "Engineering101", tag: "engineering" }
],
sortedCourses: []
};
onTagClick = tag => {
const tagsClone = JSON.parse(JSON.stringify(this.state.tags));
let foundIndex = tagsClone.findIndex(tagClone => tagClone.id == tag.id);
tagsClone[foundIndex].selected = !tagsClone[foundIndex].selected;
this.setState(
{
tags: tagsClone
},
() => this.filterCourses()
);
};
filterCourses = () => {
const { tags, courses } = this.state;
const selectedTags = tags.filter(tag => tag.selected).map(tag => tag.name);
const resortedCourses = courses.filter(course => {
return selectedTags.includes(course.tag);
});
this.setState({
sortedCourses: resortedCourses
});
};
render() {
const { tags, sortedCourses, courses } = this.state;
return (
<div>
<h1> Course Catalog Component</h1>
<Tags tags={tags} onTagClick={this.onTagClick} />
<Courses courses={!sortedCourses.length ? courses : sortedCourses} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
There are no errors in the code, what is the problem?
I am not able to dismiss the list items and change the state for my component!
What should i do to make my dismiss work and is there a better way as above to do so ??
Here is the code :
import React, { Component } from 'react';
import './App.css';
const list = [
{
title: 'React',
url: 'https://facebook.github.io/react/',
author: 'Jordan Walke',
num_comments: 3,
points: 4,
objectID: 0,
},
{
title: 'facebook github',
url: 'https://facebook.github.io/',
author: 'Janardhan',
num_comments: 3,
points: 6,
objectID: 1,
},
]
class App extends Component {
constructor(props) {
super(props);
this.state = {
list
}
this.onDismiss = this.onDismiss.bind(this);
}
onDismiss(id) {
const isNotID = item => item.objectID !== id;
const updatedList = this.state.list.filter(isNotID)
this.setState({ list: updatedList })
console.log("dismissed??")
}
render() {
return (
<div className="App">
{list.map(item =>
<div key={item.objectID}>
<span>
<a href={item.url}>{item.title}</a>
</span>
<span>{item.author}</span>
<span>{item.points}</span>
<span>{item.num_comments}</span>
<button onClick={() => this.onDismiss(item.objectID)} >Dismiss</button>
</div>
)}
</div>
);
}
}
export default App;
the problem comes from here :
const isNotID = item => item.objectId !== id;
it should be objectID not objectId
Edit
And in the render method, it should be this.state.list.map