import React, { useState, useEffect } from 'react';
import axios from 'axios';
const App = () => {
const [num, setNum] = useState();
const [name, setName] = useState();
const [moves, setMoves] = useState();
useEffect(() => {
async function pokemonAPI() {
const getPokemon = await axios.get(`https://pokeapi.co/api/v2/pokemon/${num}`);
console.log(getPokemon.data.name);
setName(getPokemon.data.name);
setMoves(getPokemon.data.moves.length);
}
pokemonAPI();
// this function call many times automatically how can I fix this.
**function dropDown() { // this function call many times itself
for (var y = 0; y <101; y++) {
document.getElementById("100dropdown").innerHTML += ("<option value =" + y + " >" + y + "</option>");
}
}
dropDown();**
});
// its works fine on first reload of page but when i choose any number this function call it many times
return (
<>
<div className="main_div">
<h1 className="choose_value">
Please Choose your <strong> Pokemon </strong> Value <br />
<select name="100dropdown" id="100dropdown" value={num} onChange={(event) => {
setNum(event.target.value);
}}>
</select>
</h1>
<div className="pokemonList">
<h1>You've choose <span style={{ color: 'green' }}> {num} </span> value</h1>
<h1>Your Pokemon is <span style={{ color: 'red' }}> {name}</span></h1>
<h1>Your Pokemon has <span style={{ color: 'blue' }}> {moves}</span> moves </h1>
</div>
</div>
</>
);
}
export default App;
Issue
The useEffect is missing dependencies, so it triggers the effect callback every time the component renders. pokemonAPI updates state so a new render is triggered each time. This creates a render loop, so dropDown will also be called each render.
Additionally, I don't see any reason why dropDown should be declared in the useEffect at all. It's rendering static data and mutates the DOM, which is an anti-pattern in React.
Solution
Fix the dependencies, add num that is used in the GET request, and remove dropDown function:
useEffect(() => {
async function pokemonAPI() {
const getPokemon = await axios.get(`https://pokeapi.co/api/v2/pokemon/${num}`);
console.log(getPokemon.data.name);
setName(getPokemon.data.name);
setMoves(getPokemon.data.moves.length);
}
pokemonAPI();
}, [num]); // <-- add dependency array!
If you need to run the effect more than this then add any missing dependencies your linter may also complain about.
Compute and render the select options in your JSX inside the element with id="100dropdown".
<select
name="100dropdown"
id="100dropdown"
value={num}
onChange={(event) => setNum(event.target.value)}
>
{Array.from({ length: 100 }, (v, i) => i)).map(i => (
<option key={i} value={i}>{i}</option>
))}
</select>
Related
I’ve read that you can’t really pass props upwards like that, but you can do so through functions. I went and found a workaround and it worked.
My problem is: I’m trying to get data to App from 2 layers down - App > DataInput > ValidateUser - so I can pass it over from App to 2 other different components that have nothing to do with filling the form.
I can get the data from ValidateUser back to DataInput, just before sending it to App I’m logging the result and it’s all as expected. The problem begins when I try to send it to App and the data I receive is undefined.
I checked a few times to see if I was making a typo or logical error while implementing the second data call. Unless I completely missed it, nothing. I started to think that, maybe, then, the problem might be with the execution order. App is read first so, maybe, it wasn’t getting updated once I assigned the value further down the execution line? But then, I’m updating the state when I click the button and it prints out the undefined and the blank object again being called from App while I can see the object is fully filled when it’s last called over the handler in DataInput…
I’m probably missing something here.
App
import "./App.css";
import Box from "#mui/material/Box";
import React from "react";
import DataInput from "./components/DataInput";
import UserDataTable from "./components/UserDataTable";
const App = () => {
let userData;
let formSubmited = false;
const getUserData = (params) => {
console.log(params);
userData = { ...userData, ...params };
console.log(userData);
};
return (
<div className="App">
<Box
sx={{
width: 1000,
height: 600,
backgroundColor: "rgba(197, 202, 255, 1)",
m: 4,
border: 1.4,
borderColor: "rgba(140, 150, 255, 1)",
}}
>
<DataInput sendData={getUserData} />
<UserDataTable />
<div className="Author">
<h3>André Lourenço</h3>
</div>
</Box>
</div>
);
};
export default App;
DataInput
import {
TextField,
Select,
MenuItem,
FormControl,
InputLabel,
} from "#mui/material";
import { LoadingButton } from "#mui/lab";
import React, { useRef, useState } from "react";
import SaveIcon from "#mui/icons-material/Save";
import "../styles/DataInput.css";
import ValidateUser from "./ValidateUser";
export default function DataInput(props) {
//State & Ref Hooks
const [country, setCountry] = useState("");
const handleCountryChange = (event) => setCountry(event.target.value);
const countryInputRef = useRef();
const nameInputRef = useRef();
const surnameInputRef = useRef();
const birthdayInputRef = useRef();
const [rawData, setRawData] = useState("");
// I/O
let returnUserData;
const handleFormSubmission = (event) => {
event.preventDefault();
let data = {
name: "",
surname: "",
country: "",
birthday: "",
};
data.name = nameInputRef.current.value;
data.surname = surnameInputRef.current.value;
data.country = countryInputRef.current.value;
data.birthday = birthdayInputRef.current.value;
setRawData(data);
};
const getValidatedData = (params) => {
returnUserData = { ...returnUserData, ...params };
returnUserData.isSubmited = true;
console.log(returnUserData);
};
const handleSendData = (data) => props.sendData(data);
return (
<div className="DataInput">
<form>
<div className="Input-Boxes">
<div className="Box-Name">
<TextField
sx={{ input: { color: "blue" } }}
label="Name"
inputRef={nameInputRef}
required
/>
</div>
<div className="Box-Surname">
<TextField
sx={{ input: { color: "blue" } }}
label="Surname"
inputRef={surnameInputRef}
required
/>
</div>
<div className="Box-Country">
<FormControl variant="filled" sx={{ minWidth: 220 }}>
<InputLabel id="demo-simple-select-filled-label">
Countries
</InputLabel>
<Select
required
labelId="demo-simple-select-filled-label"
id="demo-simple-select-filled"
label="Countries"
value={country}
autoWidth
onChange={handleCountryChange}
inputRef={countryInputRef}
>
<MenuItem value={"Brazil"}> Brazil </MenuItem>
<MenuItem value={"Portugal"}> Portugal </MenuItem>
</Select>
</FormControl>
</div>
<div className="Box-Birthday">
<TextField
sx={{ input: { color: "blue" } }}
label="Birthday"
inputRef={birthdayInputRef}
type="date"
InputLabelProps={{ shrink: true }}
required
/>
</div>
</div>
<div className="Button-Save">
<LoadingButton
loadingPosition="end"
endIcon={<SaveIcon />}
variant="outlined"
type="submit"
onClick={handleFormSubmission}
>
Save
</LoadingButton>
<ValidateUser data={rawData} sendData={getValidatedData} />
</div>
</form>
{handleSendData(returnUserData)}
</div>
);
}
Your implementation to send data to App component is correct. But you don't need handleSendData function and you are calling it from return which is wrong. You can send data to App from inside getValidatedData function like below.
const getValidatedData = (params) => {
returnUserData = { ...returnUserData, ...params };
returnUserData.isSubmited = true;
console.log(returnUserData);
props.sendData(returnUserdata)
};
You Have To Chooses
1.use Glabal State
2.declare states in top-parent component and send them to children components
as props
Trying to find a work around to get data back up to the parent components has never worked to well for me. I would recommend using a library such as Redux. With Redux you can put data in its current state into a Store. This data can then be called into any component within your React App. React-Redux has hooks that allows you to get this data. Although Redux does take a good amount of set-up and a learning curve it might work for your projects needs. Redux-Toolkit can help with some of the set-up.
Some tips on using Redux. Redux wraps your React app, so make sure that you don't miss this step. Your gonna need a Store that will hold the current state of your data. Your gonna use hooks to store and get your data. Primarily the useDispatch hook to update your Store data and useSelector to grab your data.
Once you have Redux all set-up you can do useDispatch(returnUserData). To save your data to the store. You could then call the data into App.js using let data = useSelector(state => state.data.setdata). Of course what is actually inside of useSelector needs to match your Store set-up.
I am coding a react app in which a user can click a button to swap an item in an array with the item to its left. I wrote a function to implement this without mutating the original items array that is rendering on the page, but this function is not doing anything to my code, nor is it returning any errors.
Here is my app component, which defines the function swapLeft then passes that function down to the Item component as props:
import React, { useState } from "react";
import Form from "./components/Form";
import Item from "./components/Item";
import { nanoid } from "nanoid";
import './App.css';
function App(props) {
const [items, setItems] = useState(props.items);
function deleteItem(id) {
const remainingItems = items.filter(item => id !== item.id);
setItems(remainingItems);
}
function swapLeft(index) {
const index2 = index - 1;
const newItems = items.slice();
newItems[index] = items[index2];
newItems[index2] = items[index];
return newItems;
}
const itemList = items
.map((item, index) => (
<Item
id={item.id}
index={index}
name={item.name}
key={item.id}
deleteItem={deleteItem}
swapLeft={swapLeft}
/>
));
function addItem(name) {
const newItem = { id: "item-" + nanoid(), name: name };
setItems([...items, newItem]);
}
return (
<div className="form">
<Form addItem={addItem} />
<ul className="names">
{itemList}
</ul>
</div>
);
}
export default App;
And the Item component:
import React from "react";
import { Button, Card, CardContent, CardHeader } from 'semantic-ui-react'
export default function Item(props) {
return (
<Card>
<CardContent>
<CardHeader> {props.name}</CardHeader>
<Button onClick={() => props.deleteItem(props.id)}>
Delete <span className="visually-hidden"> {props.name}</span>
</Button>
</CardContent>
<CardContent style={{ display: 'flex' }}>
<i className="arrow left icon" onClick={() => props.swapLeft(props.index)} style={{ color: 'blue'}}></i>
<i className="arrow right icon" style={{ color: 'blue'}}></i>
</CardContent>
</Card>
);
}
Is there a better way for me to write this function and implement this? I suppose I could do something with the React setState hook, but this seemed like an easier solution. I am new to React so any insight would be helpful
The way React knows if the state has changed is whether the state is refers to an entirely different address in memory. In case of arrays, if you want React to rerender the page because the array in the state changed, you need to provide it an entirely new array. Modifying the existing array will not trigger the render process.
Basically, what you need to do is changed the last line of swapLeft function to
setItems(newItems)
If you want the changes to take effect immediately (which is what I guess you want to do here)
You can also use the return value from the function and change the state in another component, FYI.
EDIT:
I looked at this again, and your implementation of swap is also wrong, but even if you corrected it you still wouldn't see a change, unless you did what I mentioned above
The full correct function would be
function swapLeft(index) {
const index2 = index - 1;
const newItems = items.slice();
const temp = items[index];
newItems[index] = items[index2];
newItems[index2] = temp;
setItems(newItems);
}
Just to maybe clarify the previous one. If you don't call setState, your component doesn't rerender. This means that no matter what you do with those arrays, it won't be visible on the screen.
So guys, I've been working on my Landing and Dashboard page.
So workflow of the page is this:
User gets on Landing page where he can choose to insert into form location, or press a button to recive all locations. Now on the backend I've made two APIs one to get all locations, and second where I've added :location as req.body.param and then filter locations based on that param. And everything works fine in postman.
Now because I've got two ways of user getting locations(all of them or some that he wants) I've thinked that I place two useEffects inside if statement like this:
const filter = props.location.data;
if (filter) {
useEffect(() => {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter);
setContent(res.data);
};
fetchFiltered();
}, []);
} else {
useEffect(() => {
const fetchPosts = async () => {
const res = await ArticleService.articles();
setContent(res.data);
};
fetchPosts();
}, []);
}
So my logic behind this was if there is filter inside props.location execute me useEffect which gets data from ArticleService who then send filter inside of a api url. If there is no filter just retrieve me all data, and setContent to res.data.
But when I compiled the code error is this: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render
Is there some way of doing this with my logic or I need to create two components: one normal dashboard and second for filtered result?
Landing.js where user sends location
<Form>
<div className='form-group'>
<Input
type='text'
className='form-control text-center'
name='name'
placeholder='Enter desired location'
value={location}
onChange={onChangeLocation}
/>
<Link to={{ pathname: '/dashboard', data: location }}>
<i className='fas fa-check'></i>
</Link>
</div>
<p className='text-center'>or</p>
<Link className='btn btn-primary btn-block' to='/dashboard'>
Show all locations
</Link>
</Form>
Dashboard.js
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import Pagination from 'react-js-pagination';
import ArticleService from '../../services/article.service';
const Dashboard = (props) => {
const [content, setContent] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(10);
const filter = props.location.data;
if (filter) {
useEffect(() => {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter);
setContent(res.data);
};
fetchFiltered();
}, []);
} else {
useEffect(() => {
const fetchPosts = async () => {
const res = await ArticleService.articles();
setContent(res.data);
};
fetchPosts();
}, []);
}
let counter = content.length;
// Get current posts
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = content.slice(indexOfFirstPost, indexOfLastPost);
// Change page
const handlePageChange = (pageNumber) => {
setCurrentPage(pageNumber);
};
const render = (item, index) => {
return (
<tr key={index}>
<td className='text-center'>
<div key={item.id}>
<img
src={`${item.pictures}`}
alt='slika artikla'
className='rounded'
></img>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<h4>{item.descr}</h4>
<br></br>
<h6 className='text-left'>Number of m2: {item.sqm}m2</h6>
<div className='text-left'>
<small className='text-left'>
{' '}
<a href={item.link} target='_blank' rel='noopener noreferrer'>
Show on website
</a>
</small>
</div>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<h4>{item.price}</h4>
<small className='text-left'>Price per m2: {item.ppm2}</small>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<Link to={`/article/${item.id}`}>
<h4>Show</h4>
</Link>
</div>
</td>
</tr>
);
};
return (
<div>
<div className='container'>
<h4 className='text-center'>
Number {counter}
</h4>
<div className='table-responsive'>
<table className='table'>
<thead className='thead-dark'>
<tr>
<th className='text-center' scope='col'>
Pic
</th>
<th className='text-center' scope='col'>
Description
</th>
<th className='text-center w-25' scope='col'>
Price
</th>
<th className='text-center' scope='col'>
Show offer
</th>
</tr>
</thead>
<tbody>{currentPosts.map(render)}</tbody>
</table>
</div>
</div>
<nav>
<div className='w3-bar w3-xlarge'>
<ul className='pagination justify-content-center'>
<li className='page-item'>
<Pagination
hideDisabled
hideNavigation
hideFirstLastPages
currentPage={currentPage}
itemsCountPerPage={10}
totalItemsCount={content.length}
pageRangeDisplayed={indexOfLastPost}
onChange={handlePageChange}
/>
</li>
</ul>
</div>
</nav>
</div>
);
};
export default Dashboard;
Thanks! :D
Basic answer, no, you cannot conditionally call useEffect. You must put the conditional logic inside the useEffect callback.
const filter = props.location.data
useEffect(() => {
if (filter) {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter)
setContent(res.data)
}
fetchFiltered()
} else {
const fetchPosts = async () => {
const res = await ArticleService.articles()
setContent(res.data)
}
fetchPosts()
}
}, [filter, setContent, ArticleService.filterByName, ArticleService.articles])
Hooks in React do not really follow the standard rules of javascript. There are performance reasons around the way they have to be implemented, often some caching is done to stop excess code being executed every time a render pass is done.
The useEffect hook will only run its callback function during a render where one of the values in the dependency array (the second arg of useEffect) has changed. It's standard to put in all external values that could change. Thus when the value of filter changes, the app will rerender, the useEffect will do a comparision, realise that something has changed and run it's call back again which will then perform the if statement.
You can read more about this in the performance part of the docs
In addition to the above answer,from the official documentatation
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.
I'm working with react-infinite-scroll-component library and randomuser.me api. I can fetch the data and the infinite scroll works just fine but the problem I have is that since the component makes a request at every scroll, I'm getting repeatable results.
I've found a workaround that removes arrays that are the same but it is not working properly with the infinite scroll package.
This is my code:
function App() {
const [people, setPeople] = useState([]);
const fetchPeopleImages = () => {
axios.get(`https://randomuser.me/api/?results=30&nat=br`).then((res) => {
const result = res.data.results;
setPeople([...people, ...result]);
});
// if (people.length > 30) {
// const removedDuplicatedPeople = people.filter(
// (ele, ind) =>
// ind ===
// people.findIndex(
// (elem) => elem.picture.medium === ele.picture.medium,
// ),
// );
// setPeople([...people, removedDuplicatedPeople]);
// }
};
useEffect(() => {
fetchPeopleImages();
// commented below because ESLINT was asking me to use useCallback
// inside fetchPeopleImage func. idk why
// eslint-disable-next-line
}, []);
return (
<div className="App">
<InfiniteScroll
dataLength={people.length}
next={() => fetchPeopleImages()}
hasMore={true}
loader={<h4>Carregando...</h4>}
endMessage={
<p style={{ textAlign: 'center' }}>
<b>Yay! You have seen it all</b>
</p>
}
>
{people.length > 1 &&
people.map((people, i) => (
<div>
<img src={people.picture.medium} alt="Imagem de uma pessoa" />
</div>
))}
</InfiniteScroll>
</div>
);
}
export default App;
What is commented was the workaround I've found to remove arrays that have the same image link.
Codesandbox: https://codesandbox.io/s/gracious-wildflower-opjx5?file=/src/App.js
Ideally don't do the filtering business specially with frequent fetches (like scroll). It is better if you maintain a state say page and pass it to your api and the api should return the data.
The problem with the inconsistency you are facing is due to the limitations of randomuser.me api. The api has only limited images to serve, so it will mix up the names, ages, images etc and tries its best to serve unique records. Hence you will often see duplicate images. You can check by rendering the name along with image and you will see 2 same images will have different names.
Some suggestions to solve your issue:
- provide a low value to results query param say 10
- use seed option to the api
- on every scroll increment the page and pass it to the api query param
See updated demo here - it works to some extent
Note - you are still not guaranteed to see unique images rendered. However, you can use the updated code and it will work when you use it with real api.
updated code snippet
function App() {
const [people, setPeople] = useState([]);
const [page, setPage] = useState(0);
const fetchPeopleImages = () => {
axios
.get(`https://randomuser.me/api/?results=10&nat=br&page=${page}&seed=abc`)
.then(res => {
const result = res.data.results;
setPeople([...people, ...result]);
setPage(prev => prev + 1);
});
console.log("page", page);
};
useEffect(() => {
fetchPeopleImages();
// commented below because ESLINT was asking me to use useCallback inside
// fetchPeopleImage func. idk why
// eslint-disable-next-line
}, []);
return (
<div className="App">
<InfiniteScroll
dataLength={people.length}
next={() => fetchPeopleImages()}
hasMore={true}
loader={<h4>Loading.....</h4>}
endMessage={
<p style={{ textAlign: "center" }}>
<b>Yay! You have seen it all</b>
</p>
}
>
{people.length > 1 &&
people.map((people, i) => (
<div key={i}>
<img src={people.picture.medium} alt="Person" />
<p>{people.name.first}</p>
</div>
))}
</InfiniteScroll>
</div>
);
}
I am adding a component onclick and keeping track of the components using useState Array. However when I go to remove one of the added components, it doesn't recognize the full component Array size, only the state that was there when that component was initially added.
Is there a way to have the current state recognized within that delete function?
https://codesandbox.io/s/twilight-water-jxnup
import React, { useState } from "react";
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => deleteSpan(props.index)}>DELETE</button>
Length: {spans.length}
</div>
);
};
//set initial span w/ useState
const [spans, setSpans] = useState([<Span key={0} index={Math.random()} />]);
//add new span
const addSpan = () => {
let key = Math.random();
setSpans([...spans, <Span key={key} index={key} />]);
};
//delete span
const deleteSpan = index => {
console.log(spans);
console.log(spans.length);
};
//clear all spans
const clearInputs = () => {
setSpans([]);
};
return (
<>
{spans}
<button onClick={() => addSpan()}>add</button>
<button onClick={() => clearInputs()}>clear</button>
</>
);
}
UPDATE - Explaining why you are facing the issue descibed on your question
When you are adding your new span on your state, it's like it captures an image of the current values around it, including the value of spans. That is why logging spans on click returns you a different value. It's the value spans had when you added your <Span /> into your state.
This is one of the benefits of Closures. Every <Span /> you added, created a different closure, referencing a different version of the spans variable.
Is there a reason why you are pushing a Component into your state? I would suggest you to keep your state plain and clean. In that way, it's also reusable.
You can, for instance, use useState to create an empty array, where you will push data related to your spans. For the sake of the example, I will just push a timestamp, but for you might be something else.
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => setSpans(spans.filter(span => span !== props.span))}>DELETE</button>
Length: {spans.length}
</div>
);
};
const [spans, setSpans] = React.useState([]);
return (
<>
{spans.length
? spans.map((span, index) => (
<Span key={span} index={index} span={span} />
))
: null}
<button onClick={() => setSpans([
...spans,
new Date().getTime(),
])}>add</button>
<button onClick={() => setSpans([])}>clear</button>
</>
);
}
I hope this helps you find your way.