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
Related
Currently i'm rewriting a class component to a function component. I need to do this since i need to use the useSelector hook from redux. Now i'm getting pretty close but i'm having some trouble with the json array getting mapped. It's letting me know it's not a function. In the fetch i'm logging the leaderboard which has returned. This gives me the json i was expecting.
[
{
"ID": 1,
"teamName": "Developers",
"time": "19:54"
},
{
"ID": 1591621934400,
"teamName": "h435hfg",
"time": "19:54"
}
]
Then here is my code that im having trouble with:
import React, {useEffect, useState} from 'react';
import '../style/App.scss';
import {useSelector} from "react-redux";
function Leaderboard() {
const io = require('socket.io-client');
const socket = io.connect("http://localhost:3001/", {
reconnection: false
});
const [leaderboard, setLeaderboard] = useState([]);
const timerState = useSelector(state => state.timerState);
useEffect(() => {
socket.emit("addTeamToLeaderboard", getTeam());
fetch('http://localhost:3000/leaderboard')
.then(response => response.json())
.then(leaderboard => {
leaderboard.push(getTeam()); // this is just so your team score renders the first time
setLeaderboard({leaderboard})
console.log(leaderboard)
});
}, [socket]);
const getTeam = () => {
let team = JSON.parse(sessionStorage.getItem('currentTeam')) ;
team.time = timerState;
return team;
}
const leaderboardElements = leaderboard.map((data, key) => {
return (
<tr key={key} className={ data.ID === getTeam().ID ? "currentTeam" : "" }>
<td>{data.teamName}</td>
<td>{data.time}</td>
</tr>
)
})
return (
<div>
<h1>Leaderboard</h1>
<table className="leaderboard">
<tr>
<th>Team</th>
<th>Time</th>
</tr>
{leaderboardElements}
</table>
</div>
);
}
export default Leaderboard;
The old code which im rewriting:
import React from 'react';
import '../style/App.scss';
class Leaderboard extends React.Component {
state = {
leaderboard: []
}
compare(a, b) {
if (a.time < b.time) {
return -1;
}
if (a.time > b.time) {
return 1;
}
return 0;
}
getTeam(){
let team = JSON.parse(sessionStorage.getItem('currentTeam')) ;
team.time = 12.13; //Todo add actual playing time
return team;
}
componentDidMount() {
const io = require('socket.io-client');
const socket = io.connect("http://localhost:3001/", {
reconnection: false
});
socket.emit("addTeamToLeaderboard", this.getTeam());
fetch('http://localhost:3000/leaderboard')
.then(response => response.json())
.then(leaderboard => {
leaderboard.push(this.getTeam()); // this is just so your team score renders the first time
this.setState({ leaderboard })
});
}
render() {
return (
<div>
<h1>Leaderboard</h1>
<table className="leaderboard">
<tr>
<th>Team</th>
<th>Time</th>
</tr>
{
this.state.leaderboard.sort(this.compare).map((data, key) => {
return (
<tr key={key} className={ data.ID == this.getTeam().ID ? "currentTeam" : "" }>
<td>{data.teamName}</td>
<td>{data.time}</td>
</tr>
)
})
}
</table>
</div>
);
}
}
export default Leaderboard;
I'm not following why you are changing leaderboard data type. If it is an array you shouldn't do setLeaderboard({leaderboard}) because you are assigning an object to the state.
You should pass a new array to the setLeaderboard like:
setLeaderboard([...leaderboard]);
Also if you do
setLeaderboard([...leaderboard]);
console.log(leaderboard);
You will not get the updated state right in the log, because set state is an asynchronous call.
Another tip, I would highly recommend you to put the socket connection not in the useEffect function, put outside the functional component.
const io = require('socket.io-client');
const socket = io.connect("http://localhost:3001/", {
reconnection: false
});
function Leaderboard() {
...
}
It's letting me know it's not a function
/* fetch data */
leaderboard.push(getTeam());
setLeaderboard({leaderboard}) // => change to setLeaderboard(leaderboard.concat(getTeam()))
console.log(leaderboard)
/* other functions below */
the difference between setState and the setLeaderboard that is returned from useState is that (when giving none callback argument)
setState expects an object with {[key: stateYouAreChanging]: [value: newState],
setLeaderboard expects the newStatValue as the argument.
So your code above is setting leaderboard state to be an object with that looks like this
leaderboard = {
leaderboard: NEW_LEADERBOARD_FETCHED_FROM_REQUEST
}
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 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.
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.