Data from map isn't rendering in React.js - html

I have successfully gathered data from an API and gathered it in a list, in my return statement the data isn't being rendered, in the console.log it logs fine, the data is there I just can't figure out why it isn't being put on the screen.
Class:
import React, { useEffect, useState } from 'react';
const PokemonRender = () => {
const [pokemonList, setPokemonList] = useState([]);
const tempPokemonArray = []
const getPokemonData = async (id) => {
try {
fetch("https://pokeapi.co/api/v2/pokemon/" + id).then(res => res.json()).then((pokemon) => tempPokemonArray.push(pokemon))
} catch (e) {
console.log(e);
}
};
useEffect((i) => {
for (i = 1; i < 5; i++) {
getPokemonData(i)
}
setPokemonList(tempPokemonArray);
tempPokemonArray.length = 0;
}, []);
return (
<>
<table>
<tbody>
{pokemonList.map(poke=>(
console.log(poke.name),
<tr key={poke.id}>
<td key={1}>{poke.id} Test</td>
<td key={2}>{poke.name} Test 2</td>
</tr>
))}
</tbody>
</table>
</>
)
};
export default PokemonRender;

You need to use Promise.all to gather all the async results from the API and then set the state.
Tips: try not to mix Promise.then().catch() syntax with async/await within a single function. Pick one and stick with it. The async/await is probalby the best option due to its readability and sync-like syntax.
You might also consider adding a isLoading state that will render a "loading..." message or Component while the API is fetching the results.
import React, { useEffect, useState } from 'react';
const PokemonRender = () => {
const [pokemonList, setPokemonList] = useState([]);
const getPokemonData = async (id) => {
try {
const res = await fetch("https://pokeapi.co/api/v2/pokemon/" + id);
const pokemon = await res.json();
return pokemon;
} catch (e) {
console.log(e);
}
};
useEffect((i) => {
const promises = [];
for (let i = 1; i < 5; i++) {
promises.push( getPokemonData(i) )
}
Promise.all(promises).then((results)=>{
setPokemonList(results);
})
}, []);
return (
<>
<table>
<tbody>
{pokemonList.map(poke=>(
<tr key={poke.id}>
<td key={1}>{poke.id} Test</td>
<td key={2}>{poke.name} Test 2</td>
</tr>
))}
</tbody>
</table>
</>
)
};
export default PokemonRender;
Live Snippet
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
const PokemonRender = () => {
const [pokemonList, setPokemonList] = useState([]);
const getPokemonData = async (id) => {
try {
const res = await fetch("https://pokeapi.co/api/v2/pokemon/" + id);
const pokemon = await res.json();
return pokemon;
} catch (e) {
console.log(e);
}
};
useEffect((i) => {
const promises = [];
for (let i = 1; i < 5; i++) {
promises.push( getPokemonData(i) )
}
Promise.all(promises).then((results)=>{
setPokemonList(results);
})
}, []);
return (
<>
<table>
<tbody>
{pokemonList.map(poke=>(
<tr key={poke.id}>
<td key={1}>{poke.id}</td>
<td key={2}>{poke.name}</td>
</tr>
))}
</tbody>
</table>
</>
)
};
ReactDOM.render(
<PokemonRender />,
document.getElementById('root')
);
</script>
</body>
</html>

Related

Conditional rendering inside map function ReactJs

I know there's similar thread already but i'm a total newbie and i can't work it. I'm not able include conditional rendering after my map function, do i need some sortf of HTML-tag inbetween?
const renderTableData = () => {
let id = 1;
const activeButton = () => {
}
return (
<tr>
{days.map((val) => (
<td>
{timeSlot.map((n, i) => (
// if(freeSlotsList.includes(id)){ THIS CODE SNIPPET
// return (<h1>Testing</h1> DON'T WORK
// )}
<button id={id++} className={activeButton}> {n} {id}</button>
))}
</td>
))}
</tr>
);
};
EDIT:
i can't seem to access my freeSlotsList, it looks like this
const [freeSlotsList, setFreeSlotsList] = useState([])
useEffect(() => {
Axios.post('http://localhost:3001/api/get/week1/ex').then((response) => {
setFreeSlotsList(response.data)
})
}, [])
Try this, You need to wrap your code into {} if you want to write js code. As () will simply return that as jsx.
const renderTableData = () => {
let id = 1;
const activeButton = () => {
}
return (
<tr>
{days.map((val) => (
<td>
{timeSlot.map((n, i) => {
if(freeSlotsList.includes(id)){
return (<h1>Testing</h1>
)}
return <button id={id++} className={activeButton}> {n} {id}</button>
)}}
</td>
))}
</tr>
);
};

Having trouble rendering items in React.js

I'm pretty new to React, being very used to OOP this is kind of twisting my brain a bit!
I'm playing around with the PokeAPI, to show different stats of pokemon, I've gotten all my data into the array fine, I've just hit a brick wall when it comes to rendering it, nothing apears.
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import PokemonRender from './PokemonRender';
ReactDOM.render(
<React.StrictMode>
<PokemonRender />
</React.StrictMode>,
document.getElementById('root')
);
PokemonRender:
import React, { useEffect } from 'react';
import axios from 'axios';
const PokemonRender = () => {
const pokemonList = [];
const getPokemonData = async (id) => {
try{
const dataArray = [];
const url = 'https://pokeapi.co/api/v2/pokemon/' + id;
const res = await axios.get(url);
dataArray.push(res.data);
pokemonList.push(dataArray[0]);
dataArray.length = 0;
} catch(e) {
console.log(e);
}
}
const Pokemon = ({ id, name }) => (
<div>
<p>{id}</p>
<p>{name}</p>
</div>
)
useEffect((i) => {
for(i = 1; i < 152; i++) {
getPokemonData(i);
} }, []);
return (
pokemonList.map((pokemon) => (
<Pokemon id={pokemon.id} name={pokemon.name} />
))
)
}
export default PokemonRender;
edit:
I've boiled it down to this, the arrays all contain the right data (which they didn't before) but the return statement isn't returning what it should? Any ideas?
import React, { useEffect, useState } from 'react';
const Pokemon = ({ id, name }) => (
<div>
<p>{id}</p>
<p>{name}</p>
</div>
);
const PokemonRender = () => {
const [pokemonList, setPokemonList] = useState([]);
const tempPokemonArray = []
const getPokemonData = async (id) => {
try {
fetch("https://pokeapi.co/api/v2/pokemon/" + id).then(res => res.json()).then((pokemon) => tempPokemonArray.push(pokemon))
} catch (e) {
console.log(e);
}
};
useEffect((i) => {
for (i = 1; i < 5; i++) {
getPokemonData(i)
}
setPokemonList(tempPokemonArray);
tempPokemonArray.length = 0;
}, []);
return pokemonList.map((pokemon) => <Pokemon id={pokemon.id} name={pokemon.name}/>);
};
export default PokemonRender;
You need to use the state to handle data inside the component. You can do it by following this link.
By the way, you should not getPokemonData in the loop like that. It will re-render your component 151 times.
You need to call useState to make a re-rendering. Without it, React won't understand when to re-render your UI. In your case, you need to update pokemon list with useState
Did a small refactoring for your code which are removing dataArray, fixing useEffect, and moving your Pokemon component outside of PokemonRender to avoid calling it many times
import React, { useEffect, useState } from "react";
import axios from "axios";
const Pokemon = ({ id, name }) => (
<div>
<p>{id}</p>
<p>{name}</p>
</div>
);
const PokemonRender = () => {
const [pokemonList, setPokemonList] = useState([]);
const getPokemonData = async (id) => {
try {
const url = "https://pokeapi.co/api/v2/pokemon/" + id;
const res = await axios.get(url);
//pokemonList.push(res.data);
setPokemonList([...pokemonList, res.data]); //add current data to the pokemon list
} catch (e) {
console.log(e);
}
};
useEffect(() => {
const blankArray = new Array(152);
for (const i = 1; blankArray.length < 152; i++) {
getPokemonData(i);
}
}, []);
return pokemonList.map((pokemon) => (
<Pokemon id={pokemon.id} name={pokemon.name} />
));
};
export default PokemonRender;
useState is also asynchronous, so I think you keep updating data and UI at the same time which will make your data unsafe (maybe lost during re-rendering). So you should return data in a new array and then render all of them together. For example
const getPokemonData = async (id) => {
try{
const url = 'https://pokeapi.co/api/v2/pokemon/' + id;
const res = await axios.get(url);
dataArray.push(res.data);
return res.data
} catch(e) {
console.log(e);
}
}
Update useEffect with a list of pokemon
useEffect(() => {
const blankArray = new Array(152)
for(const i = 1; blankArray.length < 152; i++) {
pokemonListData.push(getPokemonData(i));
}
setPokemonList(pokemonListData) //only run once
}, []);
Since you're new to these stuff, I'd suggest you follow up this document

Why is async await very slow?

I want to make selector for the user to select name of section, and according to what he select, view the section types in another selector, then when he select the section type display the students in a table.
I write code and get the data true but because i use (async..await) so the project get very slow and then closed.
What's the wrong?
function Teacher() {
const [data1, setData1] = useState([]);
const [data2, setData2] = useState([]);
const [data3, setData3] = useState([]);
const [SectionName, setSectionName] = useState('بستان');
const [Type, setType] = useState('أ');
useEffect(() => {
async function getName() {
await Axios
.get(`http://localhost:3003/getSectionsName`)
.then(result => setData1(result.data));
}
getName()
}, []);
const nameSelector = (
<select className="custom-select" onChange={(e) => {
const selectedSectionName = e.target.value;
setSectionName(selectedSectionName);
}}>
{data1.map((item) =>
<option key={item.id} value={item.SectionName}>
{item.SectionName}
</option>
)}
</select>
)
async function typeSelector() {
await Axios.put(`http://localhost:3003/getSectionTypes`, { SectionName: SectionName }).then(result => setData2(result.data))
}
const typeSelect = (
typeSelector(),
<select className="custom-select" onChange={(e) => {
const selectedSectionType = e.target.value;
setType(selectedSectionType);
}}>
{data2.map((item) =>
<option key={item.id}>
{item.Type}
</option>
)}
</select>
)
function student() {
Axios.put(`http://localhost:3003/getStudents`, { Type: Type, SectionName: SectionName }).then(result => setData3(result.data))
}
const studentTable = (
student(),
<table className="table" >
<thead className="thead-dark">
<tr>
<th scope="col">الطلاب</th>
</tr>
</thead>
<tbody>
{data3.map(item => {
return <tr key={item.Id}>
<td>{item.FullName}</td>
</tr>
})}
</tbody>
</table>
)
return (
<div className="container p-2">
<h4> اختر الصف </h4>
{nameSelector}
<br />
<h4> اختر الشعبة </h4>
{typeSelect}
<br /><br />
<h4>
{studentTable}
</h4>
</div>
)
}
export default Teacher;
async function typeSelector() {
await Axios.put(`http://localhost:3003/getSectionTypes`, { SectionName: SectionName }).then(result => setData2(result.data))
}
const typeSelect = (
typeSelector(),
...
is plain wrong - it means you're calling typeSelector() with the comma sequencing operator as a side effect of rendering the component, and likely end up in an infinite render loop. This would happen with a non-async typeSelector() function too.
You will need to wrap those fetch calls within suitable useEffect() hooks, maybe like so (I took the liberty of also extracting the components into, well, components.)
function StudentTable({ students }) {
return (
<table className="table">
<thead className="thead-dark">
<tr>
<th scope="col">الطلاب</th>
</tr>
</thead>
<tbody>
{students.map((item) => {
return (
<tr key={item.Id}>
<td>{item.FullName}</td>
</tr>
);
})}
</tbody>
</table>
);
}
function NameSelector({ sections }) {
return (
<select
className="custom-select"
onChange={(e) => setSectionName(e.target.value)}
>
{sections.map((item) => (
<option key={item.id} value={item.SectionName}>
{item.SectionName}
</option>
))}
</select>
);
}
function TypeSelect({ types }) {
return (
<select
className="custom-select"
onChange={(e) => setType(e.target.value)}
>
{types.map((item) => (
<option key={item.id} value={item.id}>
{item.Type}
</option>
))}
</select>
);
}
function Teacher() {
const [sections, setSections] = useState([]);
const [types, setTypes] = useState([]);
const [students, setStudents] = useState([]);
const [sectionName, setSectionName] = useState("بستان");
const [type, setType] = useState("أ");
// Load sections on mount
useEffect(() => {
Axios.get(
`http://localhost:3003/getSectionsName`,
).then((result) => setSections(result.data));
}, []);
// Load types based on selected section
useEffect(() => {
Axios.put(`http://localhost:3003/getSectionTypes`, {
SectionName: sectionName,
}).then((result) => setTypes(result.data));
}, [sectionName]);
// Load students based on section and type
useEffect(() => {
Axios.put(`http://localhost:3003/getStudents`, {
Type: type,
SectionName: sectionName,
}).then((result) => setStudents(result.data));
}, [sectionName, type]);
return (
<div className="container p-2">
<h4> اختر الصف </h4>
<NameSelector sections={sections} />
<br />
<h4> اختر الشعبة </h4>
<TypeSelect types={types} />
<br />
<br />
<h4>
<StudentTable students={students} />
</h4>
</div>
);
}
export default Teacher;
Try useEffect() that has a dependency sectionName. When it changes, then you will call typeSelector() and student().
const [data1, setData1] = useState([]);
const [data2, setData2] = useState([]);
const [data3, setData3] = useState([]);
const [sectionName, setSectionName] = useState('بستان');
const getName = async () => {
const data = await Axios.get(`http://localhost:3003/getSectionsName`);
setData1(data);
};
const typeSelector = async () => {
const data = await Axios.put(`http://localhost:3003/getSectionTypes`, {
SectionName: SectionName
});
setData2(data);
};
const student = async () => {
const data = Axios.put(`http://localhost:3003/getStudents`, {
Type: Type,
SectionName: SectionName
});
setData3(data);
};
useEffect(() => {
getName();
}, []);
useEffect(() => {
typeSelector();
student();
}, [sectionName]);

How to disable a button until all the fields are filled in a textfield

I have a table in a modal whose code looks like this.
<div>
<Table>
<tbody>
{props.data.map((p) => <>
<tr>
<th> STC </th>
<th> Edit Text</th>
</tr>
<tr index={p}>
<td key={p.stc}><h3>{p.stc}</h3></td>
<td >
<TextField name={p.stc} type="text" value={p.readValue} onChange={handleChange} required={true} size="small" label="Required" variant="outlined" />
</td>
</tr>
</>)}
</tbody>
</Table>
<div >
<Button disabled={inputState.disable} className="buttonStyle" onClick={(e) => submit()}>SUBMIT</Button>
<Button onClick={handleClose}>CANCEL</Button>
</div>
</div>
And their corresponding functions and declarations as below -
const [formInput, setFormInput] = useReducer(
(state, newState) => ({ ...state, ...newState }),
);
const [inputState, setInputState] = useState({disable: true});
const handleOpen = (e) => {
setOpen(true);
};
const handleClose = () => {
window.location.reload(false);
setOpen(false);
};
const [readValue, writeValue] = useState("");
const submit = (e) => {
console.log("Submitted!")
handleClose();
}
const handleChange = (event) => {
const newValue = event.target.value;
writeValue(event.target.value)
setInputState({disable: event.target.value===''})
}
I want to -
disable the buttons until and unless all the TextFields are filled.
In handleClose(), is there any alternate solution for clearing the values of TextFields in stead of window.reload?
The format looks like the picture I'm attaching below-
enter image description here
import React, { useState } from "react";
import "./style.css";
export default function App() {
const textFields = ["field1", "field2"];
const [inputValue, setInputValue] = useState({});
const [buttonDisabled, setButtonDisabled] = useState(true);
const validateButton = accInputs => {
let disabled = false;
textFields.forEach(field => {
if (!accInputs[field]) {
disabled = true;
}
});
return disabled;
};
const handleChange = ({ currentTarget }) => {
const { name, value } = currentTarget;
const inputObj = {};
inputObj[name] = value;
const accInputs = { ...inputValue, ...inputObj };
setInputValue(accInputs);
setButtonDisabled(validateButton(accInputs));
};
const handleSubmit = () => {
console.log("submit clicked");
};
const handleCancel = () => {
const inputObj = {};
textFields.forEach(field => {
inputObj[field] = "";
});
setInputValue(inputObj);
};
return (
<div>
<table border="1px">
<tr>
<th> STC </th>
<th> Edit Text</th>
</tr>
{textFields.map(field => {
console.log("rendered");
return (
<tr>
<td>
<h3>p.stc</h3>
</td>
<td>
<input
placeholder="required *"
value={inputValue[field]}
name={field}
onChange={handleChange}
/>
</td>
</tr>
);
})}
</table>
<input type="submit" disabled={buttonDisabled} onClick={handleSubmit} />
<input type="submit" onClick={handleCancel} value="cancel" />
</div>
);
}
can be easily achieved with the above code. Please refer working example here
updated to add second point aswell.

Fetch data.slice is not a function issue

I have a problem with slicing my json. I was using data.json and everything worked fine, but when I'm trying to use the same with fetch. Console tells me that data.slice is not a function. This is my code in React:
const left = '<';
const right = '>';
const { currentPage, usersPerPage } = this.state;
const lastPage = currentPage * usersPerPage;
const firstPage = lastPage - usersPerPage;
const data = fetch('https://jsonplaceholder.typicode.com/photos')
.then(response => response.json());
const currentPhotos = data.slice(firstPage, lastPage);
const renderPhotos = currentPhotos.map((photo) => {
return <tr key={photo.id}>
<td className="number">{photo.title}</td>
</tr>
});
const numbers = [];
for (let i = 1; i <= Math.ceil(data.length / usersPerPage); i++) {
numbers.push(i);
}
const renderPagination = numbers.map(number => {
return (
<li className="controls" key={number} id={number} onClick={this.handlePages}>
{number}
</li>
);
});
fetch is async, which means it returns a promise.
const data = fetch('https://jsonplaceholder.typicode.com/photos')
.then(response => response.json())
.then(json => console.log(json));
the constant data here is a Promise. which awaits to get resolved, to get your code to work you either have to use async/await like this:
const data = await fetch('https://jsonplaceholder.typicode.com/photos')
.then(response => response.json())
.then(json => console.log(json));
and you will also have to add the async keyword to your top function that wraps your code, but if this is a website you'll need to use babel for this to work in all browsers.
another take is using the callback technique but you will have to do some rewrite, but here is a start:
fetch('https://jsonplaceholder.typicode.com/photos')
.then(response => response.json())
.then(data => {
const currentPhotos = data.slice(firstPage, lastPage);
const renderPhotos = currentPhotos.map((photo) => {
return <tr key={photo.id}>
<td className="number">{photo.title}</td>
</tr>
});
});
fetch returns a Promise, so if you want to use slice method, you should use it inside the last .then(), but it would be better if you fetch your data in componentDidMount, save your data in React state, and after that use in render method;
for example, your code should look like:
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/photos')
.then(response => {
const data = response.json();
this.setState({
data: data,
});
);
}
render() {
const { currentPage, usersPerPage, data } = this.state;
const currentPhotos = data.slice(firstPage, lastPage);
const renderPhotos = currentPhotos.map((photo) => (
<tr key={photo.id}>
<td className="number">{photo.title}</td>
</tr>
);
const numbers = [];
for (let i = 1; i <= Math.ceil(data.length / usersPerPage); i++) {
numbers.push(i);
}
const renderPagination = numbers.map(number => {
return (
<li className="controls" key={number} id={number} onClick={this.handlePages}>
{number}
</li>
);
});
}