What is the problem?
I have a functional component that should render a list of player stats based on what team the user selects. The functionality of getting the data works and when I console log the state array using a useEffect I get an array with data inside it, but when I try to render the objects in html so you can see them on the screen sometimes they flicker on and then disappear, sometimes nothing happens at all.
What I've tried
I've tried using both a state array and just an ordinary variable array to see if that makes any difference. I've tried using .forEach and just a for loop to see if that would work. I've messed around with how I store the data and just trying to use a simple array instead of an object nothing so far has managed to get it rendered. As a note it is clear that the component does render as the div (className = Player-Stats) that contains the .map function is visible when inspected.
Thanks for any help and suggestions, I've spent days on this one functionality because the NHL api stores it's data super weirdly and you need to do all kinds of things to get the data you want. I didn't want to spam this question with tons of my code so if you need anything else like the parent components please ask and I can provide them.
Code Snippets
Landing Page
import { useState } from 'react';
import '../CSS/LandingPage.css';
import Instruction from './Instruction';
import LeagueLeaders from './LeagueLeaders';
import NavBar from './NavBar';
import TeamSelector from './TeamSelector';
import TeamStandings from './TeamStandings';
function LandingPage() {
const [teamSelected, setTeamSelected] = useState(false);
const [listOfTeams, setListOfTeams] = useState([]);
return (
<div className = 'Landing-Page-Container'>
<NavBar/>
<div className = 'Stats-Standings-Container'>
<div className = 'Team-Select-Container'>
<TeamSelector toggleStats = {setTeamSelected} setListTeams = {setListOfTeams}/>
</div>
<div className = 'Stats-Container'>
<LeagueLeaders showStats = {teamSelected} getListTeams = {listOfTeams} />
</div>
<div className = 'Standings-Container'>
<TeamStandings/>
</div>
</div>
</div>
);
}
export default LandingPage;
LeagueLeaders code
import { useState } from 'react';
import {FaChevronLeft, FaChevronRight} from 'react-icons/fa';
import '../CSS/LeagueLeaders.css';
import Instruction from './Instruction';
import LeaderStats from './LeaderStats.js';
function LeagueLeaders({showStats, getListTeams}){
var title = ['Skaters', 'Goalies', 'Defencemen'];
var [titleNo, setTitleNo] = useState(0);
var goalieOptions = ['GAA', 'SV%', 'SHUTOUTS'];
var nonGoalieOptions = ['POINTS', 'GOALS', 'ASSISTS'];
function selectPosition(task){
if(task === '+' && titleNo <2){
setTitleNo(titleNo+1);
}else if (task === '+' && titleNo == 2){
setTitleNo(0);
}else if(task === '-' && titleNo >0){
setTitleNo(titleNo-1);
}else{
setTitleNo(2);
}
}
return(
<div className = 'Leaders-Container'>
<div className = 'Leaders-Title'>
<FaChevronLeft className = 'toggleArrow' size = {24} color = 'white' onClick={() => selectPosition('-')}/>
<h1>{title[titleNo]}</h1>
<FaChevronRight className = 'toggleArrow' size = {24} color = 'white' onClick={() => selectPosition('+')}/>
</div>
<div className = 'Leaders-Selection-Container'>
<div className = 'Stat-Select-1'>
<p>{titleNo == 1 ? goalieOptions[0]: nonGoalieOptions[0]}</p>
</div>
<div className = 'Stat-Select-2'>
<p>{titleNo == 1 ? goalieOptions[1]: nonGoalieOptions[1]}</p>
</div>
<div className = 'Stat-Select-3'>
<p>{titleNo == 1 ? goalieOptions[2]: nonGoalieOptions[2]}</p>
</div>
</div>
<div className = 'Leaders-Stats-Container'>
{showStats ? <LeaderStats playerPos = {titleNo} teams = {getListTeams}/> : <Instruction/>}
</div>
</div>
);
}
export default LeagueLeaders;
TeamSelector component code
import '../CSS/TeamSelector.css';
import { useEffect, useState } from "react";
import teamDetail from "../Assets/teamDetail";
function TeamSelector( {toggleStats, setListTeams}) {
const [listOfTeams, setListOfTeams] = useState([]);
const [listOfURL, setListOfURL] = useState([]);
const [selectedTeams, setSelectedTeams] = useState([]);
useEffect(()=>{
console.log(selectedTeams);
setListTeams(selectedTeams);
}, [selectedTeams])
function handleClick(e){
const selectedTeamsCopy = [...selectedTeams];
if(selectedTeams.includes(e.currentTarget.id)){
if(selectedTeams.length <= 1){
toggleStats(false);
selectedTeamsCopy.splice(selectedTeamsCopy.indexOf(e.currentTarget.id, 1), 1);
setSelectedTeams(selectedTeamsCopy);
}else{
selectedTeamsCopy.splice(selectedTeamsCopy.indexOf(e.currentTarget.id, 1), 1);
setSelectedTeams(selectedTeamsCopy);
}
}else {
if(selectedTeams.length === 0){
toggleStats(true);
selectedTeamsCopy.push(e.currentTarget.id);
setSelectedTeams(selectedTeamsCopy);
}else{
selectedTeamsCopy.push(e.currentTarget.id);
setSelectedTeams(selectedTeamsCopy);
}
}
if(e.target.style.opacity === '1'){
e.target.style.opacity = '25%';
}else {
e.target.style.opacity = '100%';
}
}
return (
<div className = 'Team-Logo-Container'>
{teamDetail.map((Teams)=>(
<div>
<img onClick={(e) => handleClick(e)} key = {Teams.ID} id = {Teams.ID} alt = {Teams.Name +' Logo'} src = {Teams.URL} className = 'logo'/>
</div>
))}
</div>
);
}
export default TeamSelector;
Array of objects layout
[{ ID: "8480003", Name: "Jesper Boqvist", Points: "1", … }, { ID: "8475193", Name: "Tomas Tatar", Points: "10", … }, etc. etc.]
Rendering the array
return(
<div className = 'Player-Stats'>
{triggerStats ? listOfStats.map((d)=>{
return <p className = 'Stats' key={d.ID}>{d.ID}</p>}
) : <p className = 'Stats'> Sorry theres no available data</p>}
</div>
)
LeaderStats component script (Where the rendering issue is)
import { useEffect, useState, useRef } from "react";
import '../CSS/LeaderStats.css';
function LeaderStats({playerPos, teams}){
const isInitialMount = useRef(true);
const [listOfStats, setListOfStats] = useState([]);
const [triggerStats, setTriggerStats] = useState(false);
//If it's not the first render and the listOfStats state has changed it will render the stats of the players
useEffect(()=>{
if(!isInitialMount.current){
console.log(listOfStats);
setTriggerStats(true);
}
}, [listOfStats])
//When teams prop changes, run the function to get player data from API
useEffect(()=>{
if (isInitialMount.current) {
isInitialMount.current = false;
}else{
if(teams.length !== 0){
getPlayerIDs(teams);
}else{
setTriggerStats(false);
}
}
},[teams])
//This function runs all the axios calls and gathers data from multiple endpoints and saves it to an array
function getPlayerIDs(teamID){
const axios = require('axios');
var playerList=[];
var tempObj;
teamID.forEach(d =>
axios.get(`https://statsapi.web.nhl.com/api/v1/teams/${parseInt(d)}/roster`).then(res => {
//If user has selected the forward position filter roster by that player position and get the stats and save to temp array.
if(playerPos === 0){
res.data.roster.filter(obj => obj.position.type === 'Forward').map(e=>
axios.get(`https://statsapi.web.nhl.com/api/v1/people/${e.person.id}/stats?stats=statsSingleSeason&season=20212022`).then(res =>{
if(typeof res.data.stats[0].splits[0] !== 'undefined'){
if(playerPos !== 1 ){
tempObj = { ID: `${e.person.id}`, Name: `${e.person.fullName}` ,Points: `${res.data.stats[0].splits[0].stat.points}`, Goals: `${res.data.stats[0].splits[0].stat.goals}`, Assists: `${res.data.stats[0].splits[0].stat.assists}`};
playerList.push(tempObj);
}
}
})
);
}
//If user has selected the goalie position filter roster by that player position and get the stats and save to temp array.
else if(playerPos === 1){
res.data.roster.filter(obj => obj.position.type === 'Goalie').map(e=>
axios.get(`https://statsapi.web.nhl.com/api/v1/people/${e.person.id}/stats?stats=statsSingleSeason&season=20212022`).then(res =>{
if(typeof res.data.stats[0].splits[0] !== 'undefined'){
if(playerPos !== 1 ){
tempObj = { ID: `${e.person.id}`, Name: `${e.person.fullName}`, GAA: `${res.data.stats[0].splits[0].stat.goalAgainstAverage}`, SavePercentage: `${res.data.stats[0].splits[0].stat.savePercentage}`, Shutouts: `${res.data.stats[0].splits[0].stat.shutouts}`};
playerList.push(tempObj);
}
}
})
);
}
//If user has selected the defense position filter roster by that player position and get the stats and save to temp array.
else if(playerPos === 2){
res.data.roster.filter(obj => obj.position.type === 'Defenseman').map(e=>
axios.get(`https://statsapi.web.nhl.com/api/v1/people/${e.person.id}/stats?stats=statsSingleSeason&season=20212022`).then(res =>{
if(typeof res.data.stats[0].splits[0] !== 'undefined'){
if(playerPos !== 1 ){
tempObj = { ID: `${e.person.id}`, Name: `${e.person.fullName}` ,Points: `${res.data.stats[0].splits[0].stat.points}`, Goals: `${res.data.stats[0].splits[0].stat.goals}`, Assists: `${res.data.stats[0].splits[0].stat.assists}`};
playerList.push(tempObj);
}
}
})
);
}
})
);
//Set the state to the temp array that will be used to render the stats
setListOfStats(playerList);
}
return(
<div className = 'Player-Stats'>
{triggerStats ? listOfStats.map((d)=>{
return <p className = 'Stats' key={d.ID}>{d.ID}</p>}
) : <p className = 'Stats'> Sorry theres no available data</p>}
</div>
)
}
export default LeaderStats;
I have managed to figure out the issue so I'll post it here for those of you who in the future might find yourself in the same position as me without being able to find an answer. Turns out the way I was setting setListOfStats(playerList); made the state change without me realising it and by the time it got to rendering the .map there was nothing actually there as console.log takes a snapshot of the state at the time of the console.log. The solution (which I had experimented with before a few times but couldn't get working) was to remove the temprorary array of playerList completely and change the following code playerList.push(tempObj); to setListOfStats(listOfStats => [...listOfStats, tempObj]) thise line of code essentially sets the list of stats as I did before but it captures the previous state as well. This comes up with another issue of when you want to remove some data from the list but that's not related to this question. Hopefully someone can make use of this answer in the future.
I am using React and I am getting the following error: TypeError: Cannot set property 'innerHTML' of null. I did see some questions related to this error but I did not find any that helped me.
//React
class Pagination extends Component {
pagincationScript = (totalPages, page) =>{
const ulTag = document.querySelector("ul");
let liTag = '';
if(page > 1){
liTag = `<li className="Btn Previous"><span><i>←</i>Previous</span></li>`
}
ulTag.innerHTML = liTag;
}
render() {
return (
<div className="Pagination">
<ul>
{this.pagincationScript(10, 5)}
</ul>
</div>
)
}
}
export default Pagination;
When writing React you should always be writing JSX, like in the render function you wrote. To render the <li> separately you can do something like the following:
function Li() {
return (
<li className="Btn Previous"><span><i>←</i>Previous</span></li>
)
}
export default function Pagination() {
// Loop as many times as needed
const lis = [];
for (let i = 0; i < 10; i++) {
lis.push(<Li key={i} />);
}
return (
<div className="Pagination">
<ul>
{lis}
</ul>
</div>
)
}
**I have a code that displays the news of the day. https://ibb.co/QMLY2Kx I have 10 classes named "block". Inside the "block" class there are two classes named "blockText". I need to get two different class names and not the same, I want to get this result "blockText1" and "blockText2". How to do it? **
import React from 'react';
import newsStyle from './News_module.css';
export class News extends React.Component {
render() {
const resultsRender = [];
for (var i = 0; i < this.props.news.length; i += 2) {
resultsRender.push(
<div class="block">
{
this.props.news.slice(i, i + 2).map((news, index) => {
return (
<div class="blockText" key={index}>
<p class="text">{news.title}</p>
{console.log(this.props.news.length)}
</div>
);
}
)
}
</div>
);
}
return (
<div>
<div className="headlineSecond">
<div className="Second">
{resultsRender}
</div>
</div>
</div>
);
}
}
You can use ternary operator for this . Here is an example where i chose the value of class based on the value of index and deciding upon whether it is even or odd
<div class={ index%2 ===0 ? "blockText1": "blockText2" } key={index}>
..... rest of code
</div>
Recently I started practicing React.
I'm trying to create a simple app that would count how many kg of plastic do we use, according on how many bottles of water we use every day.
Using this info, I want to show what could be produced out of recycled plastic that we use per year.
So I have a component Calculator:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = {
resultNumber: null,
resultKg: null
}
}
count = (e) => {
let val = e.target.value;
let resultNumber = val * 52;
let resultKg = Math.round(resultNumber * 0.04);
this.setState({
resultNumber: resultNumber,
resultKg: resultKg
})
}
render() {
return ( <div >
<Menu / >
<div className = "component" >
<h1 > I use < input type = "text" className = "input-data" onChange = {this.count}/> bottles per week</h1 >
<div className = "resultInfo" > {this.state.resultNumber != null ?
<Info resultNumber = {this.state.resultNumber}
resultKg = { this.state.resultKg } /> :null} < /div>
</div>
</div>
)
}
}
I have a component that shows the result:
const Info = (props) => {
return(
<div>
<div className="info">
<div><p>{props.resultNumber} bottles per year</p>
<img src={bottle1} />
</div>
<div><p>{props.resultKg} kg of plastic per year</p>
<img src={trash} />
</div>
</div>
<p>What could be produced out of {props.resultKg} kg of plastic?</p>
</div>
)
}
The problem is that I don't know how to get the {props.resultKg} in another component that renders what could be produced:
const Recycle = (props) => {
return({props.resultKg})
}
The last component, of course, returns "undefined".
I tried to learn Redux but it became overwhelming and I got even more confused. Could you help me to understand how I can get state from Calculator in my Recycle component?
In the end I had to learn Redux to make it. :)
Explanation to why this is not a duplicate: My code is already working, I have included as a comment. The question is why the this context change when I include it to click handler function.
I'm attempting a calculator project in React. The goal is to attach onclick handlers to number buttons so the numbers are displayed on the calculator display area. If the handler is written directly to render method it is working, however, if I'm trying from the ComponentDidMount I get an error this.inputDigit is not a function. How do I bind this.inputDigit(digit) properly?
import React from 'react';
import './App.css';
export default class Calculator extends React.Component {
// display of calculator initially zero
state = {
displayValue: '0'
}
//click handler function
inputDigit(digit){
const { displayValue } = this.state;
this.setState({
displayValue: displayValue+String(digit)
})
}
componentDidMount(){
//Get all number keys and attach click handler function
var numberKeys = document.getElementsByClassName("number-keys");
var myFunction = function() {
var targetNumber = Number(this.innerHTML);
return this.inputDigit(targetNumber); // This is not working
};
for (var i = 0; i < numberKeys.length; i++) {
numberKeys[i].onclick = myFunction;
}
}
render() {
const { displayValue } = this.state;
return (
<div className="calculator">
<div className="calculator-display">{displayValue}</div>
<div className="calculator-keypad">
<div className="input-keys">
<div className="digit-keys">
{/*<button className="number-keys" onClick={()=> this.inputDigit(0)}>0</button> This will Work*/}}
<button className="number-keys">0</button>
<button className="number-keys1">1</button>
<button className="number-keys">2</button>
<button className="number-keys">3</button>
<button className="number-keys">4</button>
<button className="number-keys">5</button>
<button className="number-keys">6</button>
<button className="number-keys">7</button>
<button className="number-keys">8</button>
<button className="number-keys">9</button>
</div>
</div>
</div>
</div>
)
}
}
Thats because you are writing it inside a function which is not bound,
Use
var myFunction = function() {
var targetNumber = Number(this.innerHTML);
return this.inputDigit(targetNumber);
}.bind(this);
or
const myFunction = () => {
var targetNumber = Number(this.innerHTML);
return this.inputDigit(targetNumber);
}
After this you need to bind the inputDigit function as well since it also uses setState
//click handler function
inputDigit = (digit) => {
const { displayValue } = this.state;
this.setState({
displayValue: displayValue+String(digit)
})
}
Since you want to use the button text as well, in that case you should use a separate variable in place of this to call the inputDigit function like
class Calculator extends React.Component {
// display of calculator initially zero
state = {
displayValue: '0'
}
//click handler function
inputDigit(digit){
const { displayValue } = this.state;
this.setState({
displayValue: displayValue+String(digit)
})
}
componentDidMount(){
//Get all number keys and attach click handler function
var numberKeys = document.getElementsByClassName("number-keys");
var that = this;
var myFunction = function() {
var targetNumber = Number(this.innerHTML);
console.log(targetNumber);
return that.inputDigit(targetNumber); // This is not working
};
for (var i = 0; i < numberKeys.length; i++) {
numberKeys[i].onclick = myFunction;
}
}
render() {
const { displayValue } = this.state;
return (
<div className="calculator">
<div className="calculator-display">{displayValue}</div>
<div className="calculator-keypad">
<div className="input-keys">
<div className="digit-keys">
{/*<button className="number-keys" onClick={()=> this.inputDigit(0)}>0</button> This will Work*/}
<button className="number-keys">0</button>
<button className="number-keys">1</button>
<button className="number-keys">2</button>
<button className="number-keys">3</button>
<button className="number-keys">4</button>
<button className="number-keys">5</button>
<button className="number-keys">6</button>
<button className="number-keys">7</button>
<button className="number-keys">8</button>
<button className="number-keys">9</button>
</div>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<Calculator/>, document.getElementById('app'))
<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="app"></div>
Bind it in the constructor
constructor(props) {
super(props);
this.inputDigit = this.inputDigit.bind(this);
}