React js- page fails to get actual state in render - reactjs

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>
);
}
}
#

Related

How to add className in React with condition

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

How to loop through complex object using Map in React

I have a functional component which is reading data from an API. I have defined an Interface but unable to assign API data to Interface, followed by loop and display in table using Map in react.
Interface
export interface IEziTrackerStatus{
Schedules: EziSchedules [],
eziClient: {
clientId: number,
isActive: boolean,
name: string
}
}
..
export interface EziSchedules
{
id: number,
startTime: Date,
endTime: Date
}
component
const MyComponent = () => {
const [eziStatusCollection, setEziTrackerStatus] = useState<IEziTrackerStatus>();
useEffect(() =>{
getEziTrackerStatusReport();
},[]);
const getEziTrackerStatusReport = () =>{
(async () =>{
try{
const resp = await apiRequest(EcpApiMethod.GET, `${api.eziTrackerStatus}`, null);
setEziTrackerStatus(resp);
var x= eziStatusCollection; //HELP HERE - Undefined error
debugger;
}
catch (error) {
console.log(error);
}
})();
}
need help here
{eziStatusCollection && eziStatusCollection.eziAreaManager ????
<table className="table">
<tr>
<td>SideIt</td>
</tr>
{
eziStatusCollection.Schedules.map(item => (
<tr>
<td>{item.siteId}</td>
</tr>
))
}
Why do you have a Immediately Invoked Function Expression which is wrap getEziTrackerStatusReport method.
Define it like this,
const getEziTrackerStatusReport = async () => {
try{
const resp = await apiRequest(EcpApiMethod.GET, `${api.eziTrackerStatus}`, null);
setEziTrackerStatus(resp);
var x= eziStatusCollection; //HELP HERE - Undefined error
debugger;
}
catch (error) {
console.log(error);
}
}
When you wrap it with immediately invoked function it act as kind of similar to a namespace. If you want to keep it same as above code in your question, you can pass down the parameters you want like below,
(async (setVal, val) =>{
try{
const resp = await apiRequest(EcpApiMethod.GET, `${api.eziTrackerStatus}`, null);
setVal(resp);
var x= val; //HELP HERE - Undefined error
debugger;
}
catch (error) {
console.log(error);
}
})(setEziTrackerStatus, eziStatusCollection);
You can read more from here - https://stackoverflow.com/a/2421949/11306028
I have found the answer, the eziClient is nested object so need to access object data against it
return (
<div>
<h2>EziTracker Dashboard Report</h2>
{eziStatusCollection && eziStatusCollection.length >0 && (
<table className="table">
<thead>
<tr>
<th>ClientId</th>
<th>Is Active</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{
eziStatusCollection.map((item, index) => {
return(
<tr key={index}>
<td>{item.eziClient.clientId}</td>
<td>{item.eziClient.isActive}</td>
<td>{item.eziClient.name}</td>
</tr>
)})
}
</tbody>
</table>)}
</div>
);
};

component doesn't update after state update

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

JSON Array mapping in ReactJS from request

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
}

Call a method from Child to Child

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.

Resources