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
}
Related
I am developing a project, I created a custom hook, using localhost, the project works correctly, it lists the information, however with the project built and sent to the cloud, the data does not return to me, as if the re-render did not work to launch the data from a .map on screen
Three files are being used to get this information:
one containing a JSON with the list of requests,
another containing the custom hook,
and another containing the function call with a .map
// listRequest.js
export const listUrlGestaoQualidade = [
{
url: 'cadastro-liberacao-termofusaos',
name: 'Pré - Qualificação de solda por Termorusão',
qtd: 0,
aprovado: 0,
reprovado: 0,
aproveitamento: 0
},
];
// hooks
import { useCallback, useContext } from "react";
import { GlobalState } from "../store";
import { listUrlGestaoQualidade } from "../json/listRequests";
import api from "../services/api";
const tokenUser = localStorage.getItem('#app-token');
const licenca = localStorage.getItem('#app-licenca');
const projeto = localStorage.getItem('#projeto');
export const useGetQualidade = () => {
const { setRegistersQualidade } = useContext(GlobalState);
const executeGetQualidade = useCallback(async () => {
const params = `?filters[licenca][$eq]=${licenca}&filters[numero_projeto][$eq]=${projeto}`;
listUrlGestaoQualidade.forEach(async (request, index) => {
await api.get(request.url + params, { headers: { Authorization: `Bearer ${tokenUser}` } })
.then(res => {
if (request.name === 'Pré - Qualificação de solda por Termorusão') {
const statusAprovado = [];
const statusReprovado = [];
if (res.data.data.length > 0) {
res.data.data.forEach(status => {
if (status.attributes.status_deslocamento === 'Aprovado' && status.attributes.status_cisalhamento === 'Aprovado') {
statusAprovado.push('Aprovado')
} else {
statusReprovado.push('Reprovado')
}
});
listUrlGestaoQualidade[index].qtd = res.data.data.length;
listUrlGestaoQualidade[index].aprovado = statusAprovado.length;
listUrlGestaoQualidade[index].reprovado = statusReprovado.length;
listUrlGestaoQualidade[index].aproveitamento = (statusAprovado.length / res.data.data.length * 100).toFixed(2)
}
}
}).catch(err => {
});
});
console.log('listUrlGestaoQualidade', listUrlGestaoQualidade)
setRegistersQualidade(listUrlGestaoQualidade)
}, [setRegistersQualidade]);
return { executeGetQualidade };
}
// hook call containing .map
import { useContext, useEffect } from 'react';
import { GlobalState } from '../../../store';
import { useGetQualidade } from '../../../hooks/useGetQualidade';
import { Container, Table, AreaRows, SessionTable, TitleSession, Title, AreaTable } from "./styles";
export default function Acompanhamento() {
const { registersQualidade } = useContext(GlobalState);
const { executeGetQualidade } = useGetQualidade();
useEffect(() => {
executeGetQualidade();
}, [executeGetQualidade]);
return (
<Container>
<Table>
<AreaRows>
<td colSpan={9}>
<SessionTable>
<TitleSession>
<Title>Gestão de Qualidade e Ensaios de Campo</Title>
</TitleSession>
<AreaTable>
<table>
<tr>
<th>Descrição</th>
<th>Quantidade</th>
<th>Aprovado</th>
<th>Reprovado</th>
<th>Aproveitamento %</th>
</tr>
{registersQualidade.map(rows => (
<tr>
<td>{rows.name}</td>
<td>{rows.qtd}</td>
<td>{rows.aprovado}</td>
<td>{rows.reprovado}</td>
<td>{rows.aproveitamento}%</td>
</tr>
))}
</table>
</AreaTable>
</SessionTable>
</td>
</AreaRows>
</Table>
</Container>
);
}
In localhost, it works correctly listing the information, but the build in the cloud does not list, guys if anyone can help me I would be very grateful.
It seems the re-render is not working, so I tried using the global state with useContext
Thank you for your attention #skyboyer, in my case I needed to loop through each url of the array and make a request, but the foreach does not wait for each promise to be resolved, it passes directly without waiting, for that you must use Promisse.all(), or if you use axios axios.all
here is the resolution of my problem, using Prommise.all which expects all promises to be resolved
import { useState, useCallback } from 'react';
import api from '../services/api';
export const useGetLancamentos = () => {
const [acumulado, setAcumulado] = useState([]);
const executeGetAcumulados = useCallback(async () => {
const tokenUser = localStorage.getItem('#app-token');
const licenca = localStorage.getItem('#app-licenca');
const projeto = localStorage.getItem('#projeto');
const params = `?filters[licenca][$eq]=${licenca}&filters[numero_projeto][$eq]=${projeto}`;
const endpoints = [
'lancamento-geomembranas',
'lancamento-geotextils',
'lancamento-geogrelhas',
'lancamento-gcls',
'lancamento-geocelulas',
'lancamento-geocompostos',
];
Promise.all(endpoints.map((endpoint) => api.get(endpoint + params, { headers: { Authorization: `Bearer ${tokenUser}` } }))).then(allResponses => {
console.log(allResponses);
setAcumulado(allResponses);
});
}, []);
return { executeGetAcumulados }
}
I'm able to fetch the data from API, but not able to set the data into react state variable. Using useEffect. It's Weird because Initially Code was working fine, I was able to set the data into state variable, but after writing bunch of code. I'm getting this error.
App.js
const fetchData = async () => {
try {
const response = await axios.get(
"https://60d007f67de0b200171079e8.mockapi.io/bakery"
);
const { data } = response;
return data;
} catch (err) {
console.error(err)
}
};
const extractData = (bakerys) => {
const bakery = bakerys[0];
const header = [];
Object.keys(bakery).forEach((objKeys) => {
const value = bakery[objKeys];
// if(type of value !== 'object'){
header.push(objKeys);
})
return header;
};
export default function App() {
const [bakerys, setBakerys] = useState([]);
const [flatbakery, setFlatbakery] = useState([]);
useEffect(() => {
fetchData().then((randomData) => {
console.log('randomData ->', randomData) // able to console data as an Array of object
setBakerys(randomData); // Not able to set the randomData into state variable
console.log('bakerys', bakerys)
})
}, []);
useEffect(() => {
setFlatbakery(extractData(bakerys));
}, [bakerys]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Edit to see some magic happen!</h2>
<table>
<thead>
<tr>
{flatbakery.map((headers, idx) => (
<th key={idx}>
{headers}
</th>
))
}
</tr>
</thead>
</table>
</div>
);
}
Output
This case would come up when your bakerys array is empty, that is bakerys[0] is essentially undefined. You probably need to add some sort of check before you try to iterate the keys of it.
const extractData = (bakerys) => {
const bakery = bakerys[0];
const header = [];
if(bakery) { // only run the following block if bakery is not undefined(or falsey)
Object.keys(bakery).forEach((objKeys) => {
const value = bakery[objKeys];
// if(type of value !== 'object'){
header.push(objKeys);
})
}
return header;
};
EDIT: It appears I have forgotten to mention WHY bakerys may be empty initially. UseEffect runs when the component mounts as well, so the first time that it is called, bakerys is still an empty array. As subsequent updates are made to it, it will eventually be populated with data, so you should always attempt to check if the value has been populated before attempting to run any operations on it.
My problem is that my component doesnt rerender, when my state changes. I am managing my state in a custom Hook and after an put request to my backend my state gets updated. This works completely fine, but the content of my page doesnt get refreshed when changing my sate after the put request.
Component:
import React, { useEffect, useState } from 'react';
import { CONTROLLERS, useBackend } from '../../hooks/useBackend';
import Loading from '../Alerts/loading';
import {Table} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import DropdownForm from '../Forms/dropdown';
function AdminPanel() {
const headers = ['ID', 'Title', 'Release Date', 'Producer', 'Director', 'Status', 'UTC Time', '#', '#'];
const [error, setError] = useState(false);
const [loaded, setLoaded] = useState(false);
const [requests, backend] = useBackend(error, setError);
useEffect(() => {
backend(CONTROLLERS.REQUESTS.getRequestsAdmin());
}, [])
useEffect(() => {
setLoaded(requests !== undefined);
console.log(requests);
}, [requests])
const handleUpdate = (e, result) => {
backend(CONTROLLERS.REQUESTS.put({requestStatus: result, accessToken: localStorage.accessToken}, e));
}
if(!loaded) return <Loading/>
if(error) return <p>No Access</p>
return(
<>
<DropdownForm items={['A-Z', 'Z-A', 'None']} title={'Filter'} first={2} setHandler={setFilter}/>
<DropdownForm items={['+20', '+50', 'All']} title={'Count'} first={0} setHandler={setCount}/>
{/* <DropdownForm/> */}
<Table bordered hover responsive="md">
<thead>
<tr>
{headers.map((item, index) => {
return( <th className="text-center" key={index}>{item}</th> );
})}
</tr>
</thead>
<tbody>
{requests.map((item, index) =>{
return(
<tr>
<td>{index + 1}</td>
<td>{item.movie.movieTitle}</td>
<td>{item.movie.movieReleaseDate}</td>
<td>{item.movie.movieProducer}</td>
<td>{item.movie.movieDirector}</td>
<td>{(item.requestStatus === 1 ? 'Success' : item.requestStatus ===2 ? 'Pending' : 'Denied')}</td>
<td className="col-md-3">{item.requestDate}</td>
{/* <td><span onClick={() => handleDelete(item.requestID)}><i className="fas fa-times"></i></span></td> */}
<td><span onClick={() => handleUpdate(item.requestID, 3)}><i className="fas fa-times"></i></span></td>
<td><span onClick={() => handleUpdate(item.requestID, 1)}><i className="fas fa-check"></i></span></td>
</tr>);
})}
</tbody>
</Table>
</>
);
}
// }
export default AdminPanel;
customHook:
import axios from "axios";
import { useEffect, useRef, useState } from "react";
import notify from "../Components/Alerts/toasts";
const BASE_URL = 'https://localhost:44372/api/';
const R = 'Requests/'; const M = 'Movies/'; const U = 'Users/';
const buildParams = (url, type, header, param) => {
return {url: url, type: type, header: header, param: param};
}
export const CONTROLLERS = {
REQUESTS: {
getRequestsAdmin: () => buildParams(`${R}GetRequestsAdmin`, 'post', true, {accessToken:
}
export const useBackend = (error, setError) => {
const [values, setValues] = useState([]);
async function selectFunction(objc) {
switch(objc.type) {
case 'put': return buildPutAndFetch(objc.url, objc.param, objc.header);break;
default: console.log("Error in Switch");
}
}
async function buildPutAndFetch(url, param, header) {
const finalurl = `${BASE_URL}${url}`;
return axios.put(finalurl, param, {headers: {
'Authorization': `Bearer ${(localStorage.accessToken)}`
}})
.then(res => {
if(res.data && 'accessToken' in res.data) localStorage.accessToken = res.data.accessToken;
else {
//When an object gets updated, the backend returns the updated object and replaces the old one with the //new one.
const arr = values;
const found = values.findIndex(e => e[(Object.keys(res.data))[0]] == res.data.requestID);
arr[found] = res.data;
setValues(arr);
}
setError(false);
return true;
})
.catch(err => {
setError(true);
return false;
})
}
}
function response(res) {
setValues(res.data)
setError(false);
}
return [values,
async (objc) => selectFunction(objc)];
}
It's likely due to the fact that your buildPutAndFetch function is mutating the values array in state, rather than creating a new reference. React will bail out on state updates if the reference doesn't change.
When you declare your arr variable, it's setting arr equal to the same reference as values, rather than creating a new instance. You can use the spread operator to create a copy: const arr = [...values].
It's also worth noting that because this is happening asynchronously, you may want to use the function updater form of setValues to ensure you have the most current set of values when performing the update.
setValues(prev => {
const arr = [...prev];
const found = prev.findIndex((e) => e[Object.keys(res.data)[0]] == res.data.requestID);
arr[found] = res.data;
return arr;
});
I have a component that uses axios to access the PubMed api (in componentDidMount), retrieves some publication ids then stores them in state as "idlist". A second function is then called (addPapers) which passes in this id list and makes a second api call to retrieve further details (title, journal, authors) for each id. All this seems to work fine and when I use react tools to check state there is an array ("paperList") full of objects that have the expected key:value pairs. However, when I try to map over this array and access the values within the objects in the render function (ie paper.title, paper.author, paper.journal) they are returning as undefined. I haven't been using react for long and suspect I am making a basic mistake but cant figure it out.
I have tried console.logging each step and the expected data is in state and correct in react tools
import axios from 'axios'
import './App.css';
import rateLimit from 'axios-rate-limit';
class App extends Component {
state= {
idlist: [],
papersList : ""
}
componentDidMount () {
console.log("incomponent")
axios.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=1000&term=((Australia%5Bad%5D)%20AND%20(%222019%2F07%2F01%22%5BDate%20-%20Publication%5D%20%3A%20%223000%22%5BDate%20-%20Publication%5D))%20AND%20(%22nature%22%5BJournal%5D%20OR%20%22Nature%20cell%20biology%22%5BJournal%5D%20OR%20%22Nature%20structural%20%26%20molecular%20biology%22%5BJournal%5D)")
.then (response =>
this.setState({idlist: response.data.esearchresult.idlist}, () => {
this.addPapers(this.state.idlist)
}
)
)}
addPapers = (idlist) => {
if (idlist) {
const http = rateLimit(axios.create(), { maxRequests: 6, perMilliseconds: 1000 })
const list = this.state.idlist.map(id => {
let paperObj ={};
let paperList =[]
http.get(`https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=${id}&api_key=9476810b14695bd14f228e63433facbf9c08`)
.then (response2 => {
const title = response2.data.result[id].title
const journal = response2.data.result[id].fulljournalname
const authorList = []
const authors = response2.data.result[id].authors
authors.map((author, idx) =>
idx > 0 ? authorList.push(" " + author.name) : authorList.push(author.name))
paperObj.title = title
paperObj.journal = journal
paperObj.authors = authorList.toString()
paperList.push(paperObj)
})
return paperObj
})
this.setState({papersList: list})
}
}
render () {
let article = ""
if (this.state.papersList.length){
article = this.state.papersList.map(paper =>
console.log (paper.title)
console.log (paper.authors)
console.log (paper.journal)
)
}
return (
<div className="App">
<h1>Publications</h1>
{article}
</div>
);
}
}
export default App;
I expect that when I map over paperList and extract each paper I should be able to return the title, journal or authors using console.log(paper.title), console.log(paper.title), console.log(paper.title). These are all returning undefined.
You have two issues in code
1) paperList array declaration should be out of map loop.
2) paperList should be returned instead of paperObj
Working code below make some enhancements in render function
Also codesandbox link
import React from "react";
import ReactDOM from "react-dom";
import rateLimit from "axios-rate-limit";
import axios from "axios";
import "./styles.css";
class App extends React.Component {
state = {
idlist: [],
papersList: ""
};
componentDidMount() {
console.log("incomponent");
axios
.get(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&retmax=1000&term=((Australia%5Bad%5D)%20AND%20(%222019%2F07%2F01%22%5BDate%20-%20Publication%5D%20%3A%20%223000%22%5BDate%20-%20Publication%5D))%20AND%20(%22nature%22%5BJournal%5D%20OR%20%22Nature%20cell%20biology%22%5BJournal%5D%20OR%20%22Nature%20structural%20%26%20molecular%20biology%22%5BJournal%5D)"
)
.then(response =>
this.setState({ idlist: response.data.esearchresult.idlist }, () => {
this.addPapers(this.state.idlist);
})
);
}
addPapers = idlist => {
if (idlist) {
const http = rateLimit(axios.create(), {
maxRequests: 6,
perMilliseconds: 1000
});
let paperList = [];
this.state.idlist.forEach(id => {
let paperObj = {};
http
.get(
`https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=${id}&api_key=9476810b14695bd14f228e63433facbf9c08`
)
.then(response2 => {
const title = response2.data.result[id].title;
const journal = response2.data.result[id].fulljournalname;
const authorList = [];
const authors = response2.data.result[id].authors;
authors.map((author, idx) =>
idx > 0
? authorList.push(" " + author.name)
: authorList.push(author.name)
);
paperObj.title = title;
paperObj.journal = journal;
paperObj.authors = authorList.toString();
paperList.push(paperObj);
})
.then(result => {
this.setState({ papersList: paperList });
});
});
}
};
render() {
return (
<div className="App">
<h1>Publications</h1>
{this.state.papersList.length &&
this.state.papersList.map(data => {
return <div>{data.title}</div>;
})}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Hope it helps!!!
Do it like this:
render () {
let article;
if (this.state.papersList.length){
article = this.state.papersList.map(paper => <p>span>Title is {paper.title}</span></p> )
}
return (
<div className="App">
<h1>Publications</h1>
{article}
</div>
);
}
I am trying to update state in componentdidUpdate and for this I want to call a function but the console shows a typeError:
Uncaught (in promise) TypeError: this.props.updateMarketCap is not a function
I have imported that func in my file..below is an example:
import { fetchMarketCap } from '../Actions/Marketcap';
import { updateMarketCap } from '../Actions/Marketcap';
componentDidMount(){
// setInterval(this.props.fetchMarketCap(), 3000);
this.props.fetchMarketCap();
this.interval = setInterval(() => {
this.props.fetchMarketCap();
}, 20000);
}
componentDidUpdate(prevProps, prevState){
const prev = prevProps.marketcap.map((coin, i) => (
<tr key={this.props.marketcap[i].CoinInfo.Id}>
<td className="crypt-up"><b>{this.props.marketcap[i].DISPLAY.USD.MKTCAP}</b></td>
<td className={coin.DISPLAY.USD.PRICE < this.props.marketcap[i].DISPLAY.USD.PRICE ? 'crypt-up' : (coin.DISPLAY.USD.PRICE > this.props.marketcap[i].DISPLAY.USD.PRICE ? 'crypt-down' : 'equal')}>{this.props.marketcap[i].DISPLAY.USD.PRICE}>{this.props.marketcap[i].DISPLAY.USD.PRICE}</td>
</tr>
));
this.props.updateMarketCap(prev);
}
And at the end file:
const mapStateToProps = state => ({
marketcap: state.marketcap.coins
});
export default connect ( mapStateToProps, { fetchMarketCap } )(Marketcap);
And the action function is
export const updateMarketCap = (newData) => dispatch => {
dispatch({
type: UPDATE_MARKET_CAP,
payload: newData
})
}
I have imported Action types and other things properly
Something has to be wrong in connect function i think. The mapDispatchToProps is not defined properly and this is the wire that connect your actions imported with the props you are trying to call.
mapDispatchToProps = {
fetchMarketCap ,
updateMarketCap
};
You need to have componentDidUpdate return a true or false, so in your situation maybe something like. I'm assuming you have a mapDispatchToProps on this page right?
componentDidUpdate(prevProps, prevState){
if(prevProps!==prevState){
let marketCap
marketCap = (
const prev = prevProps.marketcap.map((coin, i) => (
<tr key={this.props.marketcap[i].CoinInfo.Id}>
<td className="crypt-up"><b>{this.props.marketcap[i].DISPLAY.USD.MKTCAP}</b></td>
<td className={coin.DISPLAY.USD.PRICE < this.props.marketcap[i].DISPLAY.USD.PRICE ? 'crypt-up' : (coin.DISPLAY.USD.PRICE > this.props.marketcap[i].DISPLAY.USD.PRICE ? 'crypt-down' : 'equal')}>{this.props.marketcap[i].DISPLAY.USD.PRICE}>{this.props.marketcap[i].DISPLAY.USD.PRICE}</td>
</tr>
));
}
this.props.updateMarketCap(prev);
}
)
Then in your return statement throw {marketCap} in where you want it