I would like to know if there's a way to call a method from a child to another child? For instance, the user clicks on "Most Recent" head table. Then props.func is called and is bound with "rearangeList" method. But it stops right there and cannot reach the method "content" in TableContent. Is anyone could tell me what is wrong?
//Get the users API leaderboard
function ajaxCall(callback){
$.getJSON("https://fcctop100.herokuapp.com/api/fccusers/top/recent", function(json30){
$.getJSON("https://fcctop100.herokuapp.com/api/fccusers/top/alltime", function(jsonAll){
json30.forEach(function(j30){
var i = 0;
jsonAll.forEach(function(jAll) {
i++;
if(jAll.username == j30.username) {i = 0; return false;}
else if(i == jsonAll.length) {
// Join json that is not appeared in the json alltime score
jsonAll = jsonAll.concat([
{
"username": j30["username"],
"img": j30["img"],
"alltime": j30["alltime"],
"recent": j30["recent"],
"lastUpdate": j30["lastUpdate"]
}
]);
};
});
});
callback(jsonAll);
});
});
}
ajaxCall(function(jsonAll) {
class LeaderBoard extends React.Component {
//call method from TableContent
rearangeList(title){this.child.content(title)}
render() {
return (
<div className="container">
<table>
<thead>
<tr>
<th>
User
</th>
<TableHead func={this.rearangeList} titleHead="Most Recent"/>
<TableHead func={this.rearangeList} titleHead="All Time"/>
</tr>
</thead>
<TableContent onRef={ref => (this.child = ref)} />
</table>
</div>
);
}
}
class TableContent extends React.Component {
componentDidMount(){
this.props.onRef(this);
}
componentWillUnmount() {
this.props.onRef(null)
}
content(title = null) {
var constr = "";
var i = 1;
console.log(title)
jsonAll.forEach(function(el){
constr += "<tr>" +
"<td class='user'>"+
"<img class='avatar' src='"+ el.img+"' alt='avatar'/>" +
"<span class='user-tag'>" + el.username +"</span>"+
"</td>"+
"<td class='number'>"+ el.recent +"</td>" +
"<td class='number'>"+ el.alltime + "</td>" +
"</tr>" ;
i++;
});
return constr;
}
render() {
return (
<tbody dangerouslySetInnerHTML={{ __html: this.content() }}>
</tbody>
);
}
}
class TableHead extends React.Component {
constructor() {
super();
this.state = {
carret: "pd fas fa-angle-up fa-sm"
}
this.carretUp = "pd fas fa-angle-up fa-sm";
this.carretDown = "pd fas fa-angle-down fa-sm";
}
toggleCarret(carret, title) {
if(carret == this.carretUp) {
this.setState({carret: this.carretDown});
} else if(carret == this.carretDown) {
this.setState({carret: this.carretUp});
}
this.props.func(title);
}
render() {
return (
<th className="btn noselect" onClick={() => this.toggleCarret(this.state.carret, this.props.titleHead)}>
{this.props.titleHead}<i className={this.state.carret}></i>
</th>
);
}
}
const app = document.getElementById("app");
ReactDOM.render(<LeaderBoard/>, app);
});
The link to the codepen. It works perfectly except when the user clicks on the column title to order by the Highest or the lowest value.
https://codepen.io/dancinoman/pen/VQRWrM
The React way to do this is to pass a callback method from the parent to each child. The child then calls the callback which causes the parent to update its state and trigger a render cycle. During the render cycle the new state is passed down to the children as usual.
Related
I tried to "2. Adding a class of "error" to a row will highlight it red. Provide this
visual feedback on rows which have duplicate ranks selected." You can view my code at js_react_problem_after folder under https://github.com/HuydDo/plm_challenge. Please let me know if you have any questions. Thanks,
MainPage.jsx
import React from 'react';
import _ from 'lodash';
import FormRow from './FormRow.jsx';
import Animal from './Animal.js';
class MainPage extends React.Component {
constructor(props) {
super(props);
this.state = {
animals: ['panda','cat','capybara','iguana','muskrat'].map((name) => {
return new Animal(name);
//instantiating an object of that class. animal is a class that has
//a constructor that takes in one variable which is name and it's
//creating a bunch of objects which are instances of that class
}),
error: 'Ranks must be unique',
errorFlag: false
};
}
render() {
// console.log('this.state.animal',this.state.animals)
const errCount = {}
// this.state.animals.forEach((animal) => {
// if (animal.rank && errCount[animal.rank]){
// errCount[animal.rank] += 1
// return
// }
// errCount[animal.rank] = 1
// })
const rows = this.state.animals.map((animal) => {
// console.log(animal)
// if (animal.rank && errCount[animal.rank]){
// errCount[animal.rank] += 1
// return
// }
// errCount[animal.rank] = 1
// for (let prop in errCount){
// if(errCount[prop] >=2 && prop){
// // this.setState({
// // errorFlag : true
// // })
// }
// }
return (
<FormRow
// errorFlag={this.state.errorFlag}
animalRank={animal.rank}
selectButton={(updateRank) => {
animal.setRank(updateRank)
this.setState({animal: this.state.animals})
}}
animalName={animal.name}
key={animal.name}
/>
);
});
//create headers
const headers = _.range(1, 6).map((i) => <th key={`header-${i}`}>{i}</th>);
let disabled = false
let error = false
const count = {}
this.state.animals.forEach((animal) => {
if(!animal.rank){ //disable the button if any rank is not selected
disabled = true
}
if (animal.rank && count[animal.rank]){
count[animal.rank] += 1
return
}
count[animal.rank] = 1
})
for (let prop in count){
if(count[prop] >=2 && prop){
error = true
disabled = true
// console.log(prop + " counted " + count[prop] + " times")
}
}
// console.log('disabled:', disabled, 'error:', error)
// console.log(count)
return (
<div>
<table>
<thead>
<tr>
<th></th>
{headers}
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
<div>{error ? this.state.error : null}</div>
<input type="submit" disabled={disabled} />{/*disabled is a prop of the input tag*/}
</div>
);
}
}
export default MainPage;
FormRow.jsx
import React from 'react';
import _ from 'lodash';
/**
* TODO: 2. Adding a class of "error" to a row will highlight it red. Provide this visual feedback on rows which have duplicate ranks selected.
* TODO: 3. There is a place to display an error message near the submit button. Show this error message: `Ranks must be unique` whenever the user has selected the same rank on multiple rows.
*/
class FormRow extends React.Component {
constructor(props){
super(props)
this.state = {
rank: this.props.animalRank
}
}
//*if we receive new props and the animal props
//*doesn't match whats inside the component then
//*we should update the component
componentDidUpdate(){
if(this.props.animalRank !== this.state.rank){
this.setState({rank: this.props.animalRank})
// console.log(`rank is updated to: ${this.state.rank}`)
}
}
render() {
//created array 1-6, iterating over it,
// and for each number in that array creating
// a new array and returning this HTML chunk
const cells = _.range(1, 6).map((i) => {
return (
<td key={`${this.props.animalName}-${i}`}>
<input
onClick={() => {
// i is the number clicked on
this.props.selectButton(i)
}}
type="radio"
name={this.props.animalName}
value={i}
/>
</td>
);
});
// console.log(this.props)
const done = this.state.rank ? "done" : null
// console.log(done)
return (
<tr className={done}>
<th>{this.props.animalName}</th>
{cells}
</tr>
)
}
}
export default FormRow;
Here is a solution. In future questions, please try to limit code instead of wholesale copying everything with commented code included.
const trProps = {}
if(error){
trProps.className= "errorClass"
}
else if(this.state.rank){
trProps.className = "done"
}
<tr {...trProps}>
https://reactjs.org/docs/jsx-in-depth.html#spread-attributes
the following code doesn't update my component.
the state is updated with another function. so I'd assume the component would update aswell.
Entire Class is here.
class QuestionList extends Component
{
constructor(props)
{
super(props);
this.state = {
questions : []
}
this.childHandler = this.childHandler.bind(this);
}
updateData()
{
const get = '/application/questions/'
api.get(get)
.then(response => {
console.log(response);
this.setState({questions : response.data});
})
.catch(err => console.log(err));
}
componentDidMount(){
var tempArray = [];
const get = '/application/questions/'
api.get(get)
.then(response => {
console.log(response);
this.setState({questions : response.data});
})
.catch(err => console.log(err));
}
childHandler( update )
{
const {questions} = this.state;
let tempQs = questions;
const length = questions.length;
var temp = [];
var temp1 = [];
console.log ( tempQs );
for(var i = 0; i < length; i++)
{
if(questions[i].q_id == update[1])//find New
{
temp = [questions[i].q_id,questions[i].question];
for(var x = 0; x < length; x++)//find old
{
if(questions[x].q_id == update[0] && questions[x].q_id != questions[i].q_id )
{
temp1 = [questions[x].q_id,questions[x].question];
break;
}
}
break;
}
}
tempQs[temp[0]-1].question = temp1[1];
tempQs[temp1[0]-1].question = temp[1];
this.setState({questions : tempQs},console.log(questions));
}
render()
{
var { questions } = this.state;
console.log(questions);
var qs;
qs = questions.map(val => {
return(
<QuestionCards q_id={val.q_id} max={questions.length} action={this.childHandler}>{val.question}</QuestionCards>
)
});
return(
<Table hover>
<tbody>
<tr className="title">
<th>Id</th>
<th>Question</th>
<td colspan="3" ><Button color="primary">Add Question</Button></td>
</tr>
{qs}
</tbody>
</Table>
);
}
}
here is the cards component
class QuestionCards extends Component
{
constructor ( props )
{
super(props)
this.state = {
fireModal : false,
modal : false,
q_id : this.props.q_id,
question : this.props.children,
max : this.props.max
}
this.handleClick = this.handleClick.bind(this);
this.handleModal = this.handleModal.bind(this);
this.triggerModal = this.triggerModal.bind(this);
this.moveUp = this.moveUp.bind(this);
this.moveDown = this.moveDown.bind(this);
}
triggerModal ( trig )
{
const {q_id} = this.state;
if (trig)
return (
<QListModal q_id={q_id} trigger={trig} action={this.childHandler}/>
);
}
handleModal ( val )
{
const { fireModal } = this.state;
console.log('fireModel: ' + fireModal)
if( !fireModal )
{
this.setState({
mTarget : val,
fireModal : true ,
update : []
});
}
else
{
this.setState({fireModal:false})
}
}
moveUp ()
{
var tempArray = [];
const { q_id } = this.state;
const dir = 'up';
const get = '/application/move/' + q_id +'/'+ dir;
api.get(get).then(res => {
console.log(res);
this.setState({
update : [res.data.newId,res.data.oldId]
})
return this.props.action(this.state.update);
});
//return this.props.action(this.state.update);
}
moveDown ()
{
var tempArray = [];
const { q_id } = this.state;
const dir = 'down';
const get = '/application/move/' + q_id +'/'+ dir;
api.get(get).then(res => {
this.setState({
update : [res.data.newId,res.data.oldId]})
return this.props.action(this.state.update);
});
//return this.props.action();
}
render()
{
const {
fireModal,
q_id,
question,
max,
update
} = this.state
let ButtonUp;
let ButtonDown;
if( q_id <= 1)
{
ButtonUp = <td></td>
}
else
{
ButtonUp = <td><Button id={q_id} onClick={this.moveUp}>▲</Button></td>
}
if( q_id == max)
{
ButtonDown = <td></td>
}
else
{
ButtonDown = <td><Button id={q_id} onClick={this.moveDown}>▼</Button></td>
}
return(
<tr>
<th>{q_id}</th>
<td>{question}</td>
<td><Button onClick={this.handleModal}>Edit</Button></td>
{ButtonUp}
{ButtonDown}
{this.triggerModal(fireModal)}
</tr>
)
}
}
render()
{
var { questions } = this.state;
var qs = questions.map(val => {
return(
<QuestionCards q_id={val.q_id} max={questions.length} action={this.childHandler}>{val.question}</QuestionCards>
)
});
return(
<Table hover>
<tbody>
{qs}
</tbody>
</Table>
);
}
}
what the app is trying to do is every time the up or down arrow is pressed. it updates it on the page.
For some reason after updating the state it doesn't update the output itself.
though when i console.log the state it self it is updated.
this is my first independent project I'm still learning React/Javascript as a whole.
as you can see the state updates properly. but just doesn't re render anything.
the profiler tool in react-dev-tools outputs nothing rendered. could it be because of the parent component?
Solution
My problem was with the constructor for question cards.
super(props)
this.state = {
fireModal : false,
modal : false,
q_id : this.props.q_id,
question : this.props.children, // This line in particular
max : this.props.max
}
I wasn't updating the state with the new Info.
so i just assign the value of this.props.children to a constant in the render function
this is the updated render for QuestionCards
render()
{
const {
fireModal,
q_id,
max
} = this.state
const question = this.props.children;
let ButtonUp;
let ButtonDown;
if( q_id <= 1)
{
ButtonUp = <td></td>
}
else
{
ButtonUp = <td><Button id={q_id} onClick={this.moveUp}>▲</Button></td>
}
if( q_id == max)
{
ButtonDown = <td></td>
}
else
{
ButtonDown = <td><Button id={q_id} onClick={this.moveDown}>▼</Button></td>
}
return(
<tr>
<th>{q_id}</th>
<td>{question}</td>
<td><Button onClick={this.handleModal}>Edit</Button></td>
{ButtonUp}
{ButtonDown}
{this.triggerModal(fireModal)}
</tr>
)
}
also removed the console logs to declutter the post!
Thank you all for helping me trouble shoot!
Issue
Looks like a state mutation in cardHandler
childHandler( update ) {
const {questions} = this.state;
let tempQs = questions; // <-- saved current state ref to tempQs
...
tempQs[temp[0]-1].question = temp1[1]; // <-- mutated state ref
tempQs[temp1[0]-1].question = temp[1]; // <-- mutated state ref
this.setState({questions : tempQs},console.log(questions)); // <-- saved state ref
}
Solution
Shallow copy questions into new array reference to update. This should allow react's state/props reference test to detect that state is a new object and rerender.
const {questions} = this.state;
let tempQs = [...questions]; // <-- spread existing array into new array
I am working on a quiz component, where user can appear for test. Questions are shown one after another to user and user checks the right answer.
But I am facing the below issue.
Description:
Checkbox does not uncheck for next question. It remains checked, once user click on any of the checkbox.
Steps:
1. Click on any checkbox options for the question.
2. Click on next for next question. [checkbox is checked from previous question]
[]2
Expected:
When next question appears, the checkbox should not be checked.
Actual:
When next questions appears, the checkbox is checked.
Code: On click of next, this component gets its data as a props from parent component.
// This component show one question at a time
import React from 'react';
import TextEditorDisplay from '../../texteditor/TextEditorDisplay';
import Form from 'react-bootstrap/Form';
class TestComponent extends React.PureComponent {
handleCheck = (e, idx) => {
console.log('inside handleCheck',e.target.value)
this.props.setAnswerGivenByUser(idx, e.target.checked);
};
render() {
return (
<div className="container">
<h3 className="quiz-question">
<TextEditorDisplay editorContent={this.props.quizQuestion.question} />
</h3>
<Form>
<table>
<tbody>
{this.props.quizQuestion.options && this.props.quizQuestion.options.map((option, idx) => (
<tr key={idx}>
<td>
<Form.Group controlId="formBasicCheckbox">
<Form.Check type="checkbox" value={option.data} onChange={e => this.handleCheck(e, idx)}/>
</Form.Group>
</td>
<td>
<p key={idx}>{option.data}</p>
</td>
</tr>
))}
</tbody>
</table>
</Form>
</div>
);
}
}
export default TestComponent;
Parent component:
import React from 'react';
import TestComponent from '../components/skill-assessment/users/TestComponent';
import Button from 'react-bootstrap/Button';
import api from '../services/remote/api';
class TestHomePage extends React.PureComponent {
x = 0;
y = 0;
arr = [];
constructor(props) {
super(props);
this.getQuizQuestionsAsPerLevel = this.getQuizQuestionsAsPerLevel.bind(this);
this.state = {
quizQuestion: [],
show: true,
options: [],
answers: []
};
}
getIdFromUrl = () => {
var url = this.props.location.pathname;
var splitUrl = url.split('/');
return splitUrl[2].toString();
};
componentDidMount() {
this.getQuizQuestionsAsPerLevel(1);
this.getQuizQuestionsAsPerLevel(2);
this.getQuizQuestionsAsPerLevel(3);
this.getQuizQuestionsAsPerLevel(4);
this.getQuizQuestionsAsPerLevel(5);
this.getQuizQuestionsAsPerLevel(6);
console.log('component did mount arr', this.arr);
}
getQuizQuestionsAsPerLevel(level) {
try {
api.getQuizQuestionsAsPerLevel({ id: this.getIdFromUrl(), level: level }).then(response => {
this.arr.push(response.data);
console.log('arr inside api', this.arr);
});
} catch (exception) {
console.log('exception', exception);
}
}
addUserQandA() {
try {
api.addUserQandA({
quizId: this.getIdFromUrl(),
quizQandA: [{ quizQuestionId: this.state.quizQuestion._id }, { answers: this.state.answers }]
}).then(response => {
console.log('add QandA response', response);
});
} catch (exception) {
console.log('exception', exception);
}
}
nextQuestion = () => {
// send prev Question data to QandA
if (this.state.quizQuestion && this.state.answers) {
this.addUserQandA();
}
if (this.x < this.arr.length - 1 && this.y >= this.arr[this.x].length) {
this.x = this.x + 1;
this.y = 0;
this.setState({ quizQuestion: this.arr[this.x][this.y], answers: [] });
} else if (this.x < this.arr.length && this.y < this.arr[this.x].length) {
this.setState({ quizQuestion: this.arr[this.x][this.y] });
this.y = this.y + 1;
} else {
// hide next button and highlight submit button
this.setState({ show: false });
}
};
setAnswerGivenByUser = (answerId, shouldAdd) => {
const answers = this.state.answers.slice();
if (shouldAdd) {
if (!answers.includes(answerId)) {
answers.push(answerId);
}
} else {
if (answers.includes(answerId)) {
const answerIndex = answers(a => a === answerId);
answers.splice(answerIndex, 1);
}
}
this.setState({ answers });
};
render() {
console.log('answers', this.state.answers);
return (
<div className="container">
<TestComponent quizQuestion={this.state.quizQuestion} setAnswerGivenByUser={this.setAnswerGivenByUser} />
{this.state.show && (
<Button variant="primary" onClick={this.nextQuestion}>
Next
</Button>
)}
<Button variant="primary">Submit</Button>
</div>
);
}
}
export default TestHomePage;
quiz Data Strcuture
Not refreshing problem was caused by not forced rerenderings.
For each levels <tr /> elements was rendered with numbered key always starting from 0. This way next level renders was besed on exiting nodes (updating), not rendered as new ones. First not changed node (in sense of the same props) stops deeper analisys. In this case it stops on <Form.Group controlId="formBasicCheckbox"> - it's child is not updated even when option.data differs.
Solution
key is used for distinguish nodes rendered in loops. key should be unique. It should not be a number only ... they should be always unique.
Simple fix is to use additionally passed prop level:
<tr key={this.props.level * 10 + idx} />
In fact ... as <p key={idx}>{option.data}</p> was updated ... it should be enough to use this (or similar) unique key/prop for <Form.Group/> (f.e. controlId). Using unique on <tr/> level we're forcing render of a new structure (can be costly in some scenarios).
I am new to react and having a weird bug in my application. On page load the app checks if user has some subscribed tests, the respective tests are shown or else a error than no test found. Page initially shows no error message and then loads up the test which is weird. Please help
#
export default class PlaytestsIndex extends React.Component {
constructor (props, context) {
super(props, context);
this._onPlaytestStoreChange = this._onPlaytestStoreChange.bind(this);
this.state = {
playtests: PlaytestStore.getActivePlaytests()
};
}
componentDidMount () {
PlaytestStore.addChangeListener(this._onPlaytestStoreChange);
}
componentWillUnmount () {
PlaytestStore.removeChangeListener(this._onPlaytestStoreChange);
}
_onPlaytestStoreChange () {
this.setState({
playtests: PlaytestStore.getActivePlaytests()
});
}
render () {
const playtests = this.state.playtests;
if (playtests.length === 0) {
return (
<table className="table table-fixed content-section">
<tbody>
<tr className="disabled">
<td className="no-results center">No playtests found</td>
</tr>
</tbody>
</table>
);
}
const today = Moment().minute(0).hour(0).second(0).millisecond(0);
// how the groups of playtests are sorted
const sortOrderLabels = [
'Currently Active',
'Upcoming'
];
const timelineDateRows = _.chain(playtests)
.groupBy((playtest) => {
const startsOn = Moment(playtest._startsOnDateTime).minute(0).hour(0).second(0).millisecond(0);
if (today.isAfter(startsOn) || today.isSame(startsOn, 'day')) {
return 0;
}
return 1;
})
.toPairs() // [[idx, playtests], ...]
.sortBy((paired) => paired[0]) // by idx
.map((paired) => {
const [idx, playtests] = paired;
return (<TimelineDateRow key={sortOrderLabels[idx]} label={sortOrderLabels[idx]} playtests={playtests}/>);
})
.value();
return (
<div>
{timelineDateRows}
</div>
);
}
}
#
I'm still new in React. I get some data from a JSON file. I managed to get the search to work. But I also want to be able to click my name table header and filter my data by name. How do I make that work with filter.
import React, { PropTypes } from 'react';
// Mock Data
let MockData = require('./generated.json');
let searchValue = '';
export default class Employees extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
searchValue: '',
sortValue: null
};
this.searchInputChange = this.searchInputChange.bind(this);
this.searchSubmit = this.searchSubmit.bind(this);
}
// Sort function
sortFunction(sortValue, event) {
alert(sortValue);
this.setState({sortValue: sortValue});
}
// Update search value state function
searchInputChange(event) {
this.searchValue = event.target.value;
}
// Search function
searchSubmit(event) {
this.setState({searchValue: this.searchValue});
}
render() {
let sortedEmployeesBySearch = MockData.filter(
(employee) => {
// If state searchValue is not null
if (this.state.searchValue) {
return employee.name.indexOf(this.state.searchValue) !== -1 || employee.gender.indexOf(this.state.searchValue) !== -1 || employee.company.indexOf(this.state.searchValue) !== -1 || employee.email.indexOf(this.state.searchValue) !== -1;
}
else {
return employee;
}
}
);
return (
<div className="container">
<input className="search" type="text" name="search" placeholder="Search table" onChange={this.searchInputChange} />
<input className="searchButton" type="button" value="Search" onClick={this.searchSubmit} />
<table className="employeesList">
<thead>
<tr>
<th onClick={this.sortFunction.bind(this,'name')}>Name</th>
<th onClick={this.sortFunction.bind(this,'gender')}>Gender</th>
<th onClick={this.sortFunction.bind(this,'company')}>Company</th>
<th onClick={this.sortFunction.bind(this,'email')}>E-mail</th>
</tr>
</thead>
<tbody>
{ sortedEmployeesBySearch.map((employee) => (
<tr key={employee.id}>
<td>{employee.name}</td>
<td>{employee.gender}</td>
<td>{employee.company}</td>
<td>{employee.email}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
}
You can sort the data by:
1) Storing in state the property to sort on, which you are already doing
2) Chaining a sort function after the filter of your MockData
The second task can be accomplished by:
MockData.filter(...).sort((a, b) => {
aVal = a[this.state.sortValue];
bVal = b[this.state.sortValue];
switch(typeof aVal) {
case 'string':
return aVal.localeCompare(bVal);
case 'number':
return aVal - bVal;
default:
throw new Error("Unsupported value to sort by");
}
});
You can even pass a custom function into the sort method that takes the two values and does custom sorting logic based on the sortValue property.