have some problem, do little pokedex, have after chose the cound of cards on the page i need to reload a container with cards, can u help me?
To get selected item use onSelect,
handleSelect = (e) => {
this.setState({value:e})
}
<DropdownButton
variant="danger"
alignRight
id="dropdown-menu-align-right"
onSelect={this.handleSelect}>
and get it to link in component Pokemon list
<div className="col">
<PokemonList pages={this.value} />
</div>
PokemonList working like this
function PokemonList({ pages }) {
const [pokemonList, setPokemonList] = useState([]);
const [currPage, setCurrPage] = useState(
`https://pokeapi.co/api/v2/pokemon?offset=0&limit=${pages}`
);
const [nextPage, setNextPage] = useState();
const [prevPage, setPrevPage] = useState();
const [pageNum, setPageNum] = useState(0);
useEffect(() => {
let cancel;
axios
.get(currPage, {
cancelToken: new axios.CancelToken((c) => (cancel = c)),
})
.then((res) => {
setPokemonList(res.data.results);
setPrevPage(res.data.previous);
setNextPage(res.data.next);
})
.catch((error) => {
console.log(error);
});
return () => {
cancel();
};
}, [currPage, pageNum]);
i don't know but after select item at deop down, nothing changes, can u please help me
Find the problem, i tried to change only value, but i should to change the link, so answer was:
const handleChange = (e) => {
setCurrPage(`https://pokeapi.co/api/v2/pokemon?offset=${pageNum}&limit=${e}`);
};
The problem is that you try to store props inside state and with useEffect listen on state change. Because of this React can't properly update components. The currPage state doesn't change when pages change. You should avoid this because it's anti-pattern.
Working short example:
const Component = ({pages}) => {
const [pokemonList, setPokemonList] = useState([]);
useEffect( () => {
fetch("/api/pokemons/" + pages )
.then( res => res.json() )
.then( data => setPokemonList(data.pokemonList ))
.catch( err => console.log("handle errors") )
})
return <div>{ pokemonList.map( pokemon => <div>{ pokemon.name }</div>) }</div>
}
Related
I´m new to react. I´m trying to fetch an endpoints array. and I want to update the api's status every 15 seconds. I´m trying to do this
export const endpoints: string[] = [
"accounts/health/status",
"assets/health/status",
"customers/health/status",
"datapoints/health/status",
"devices/health/status",
"documents/health/status",
"forms/health/status",
"invites/health/status",
"media/health/status",
"messages/health/status",
"namespaces/health/status",
"orders/health/status",
"patients/health/status",
"relationships/health/status",
"rules/health/status",
"templates/health/status",
"users/health/status",
"workflows/health/status",
];
and I have this proxy in my package.json
"proxy": "https://api.factoryfour.com/",
Here the rest of my code
const [data, setData] = useState<Response[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string[] | null[]>([]);
const effectRan = useRef(false);
const fetching = async () => {
setLoading(true);
endpoints.map(async (endpoint) => {
return await axios
.get(endpoint)
.then((res) => {
setData((prev) => [...prev, res.data]);
})
.catch((err) => {
setError([...error, err.message]);
});
});
setLoading(false);
};
useEffect(() => {
if (!effectRan.current) {
fetching();
}
return () => {
effectRan.current = true;
};
});
useEffect(() => {
setTimeout(async () => {
setData([]);
setLoading(true);
setError([]);
await fetching();
}, 15000);
}, []);
but when the seTimeout runs every card duplicates and the state gets more data than before. even though I´m reseting the state to setData([]) I just want to update the api's status. What can i do?
if (loading) return <Spinner />;
return (
<div className="card-container">
{data.length ? (
data.map((item) => {
return (
<Card
key={generateKey()}
hostname={item.hostname}
message={item.message}
success={item.success}
time={item.time}
/>
);
})
) : (
<Spinner />
)}
{error.length
? error.map((err) => (
<ErrorCard key={generateKey()} message={err as string} />
))
: null}
</div>
```
Theres a few things wrong here and one or more probably fixes it:
You keep a ref around to track the first fetch but theres no need as you can do that by virtue of using [] in an effects deps array, which you already have.
The loading state does not wait until all requests in flight finished.
The 15 second interval does not wait until all requests launched are finished.
You dont clear down the timer if the component unmounts and remounts.
The data is not keyed against the endpoint which could land you in trouble if using React strictmode that runs affects twice in dev mode.
Your code, by design it seems, does append data each time one of the requests comes back -- but I think that was intentional?
const [data, setData] = useState<Record<string, Response>>({});
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<Record<string, string | null>>({});
const fetching = async () => {
setLoading(true);
await Promise.all(
endpoints.map((endpoint) => {
return axios
.get(endpoint)
.then((res) => {
setData((prev) => ({...prev, [endpoint]: res.data}));
})
.catch((err) => {
setError((prev) => ({...prev, [endpoint]: err.message}));
});
})
);
setLoading(false);
};
useEffect(() => {
let timer: number | null = null;
const intervalFetch = async () => {
await fetching();
timer = setTimeout(async () => {
setError({});
setData({});
intervalFetch();
}, 15000);
};
intervalFetch();
return () => timer !== null && clearTimeout(timer);
}, []);
if (loading) return <Spinner />;
return (
<div className="card-container">
{Object.values(data).length ? (
Object.values(data).map((item) => {
return (
<Card
key={generateKey()}
hostname={item.hostname}
message={item.message}
success={item.success}
time={item.time}
/>
);
})
) : (
<Spinner />
)}
{Object.values(error).length
? Object.values(error).map((err) => (
<ErrorCard key={generateKey()} message={err as string} />
))
: null}
</div>)
I think this piece of code might be adding additional data instead of overwriting the existing one. Is that what you're trying to do?
setData((prev) => [...prev, res.data]);
I can't log or display data in this barebones React component. I am trying to just simply fetch my repos using the fetch api. I am getting a response back when I look at Network tab in dev tools.
I tried to wrap the call in useEffect() (then storing the data from the response into a state variable) - that didn't work so that's why I have this barebones component for now.
const Component = () => {
const [repos, setRepos] = useState([])
useEffect(() => {
// fetch call used to be here
}, [])
const data = fetch('https://api.github.com/users/alexspurlock25/repos')
.then(response => response.json())
.then(data => setRepos(data))
console.log(data)
console.log(repos)
return (
<div>
{
repos.map(items => console.log(items))
}
</div>
)
}
Why can't I log or map the data? Am I doing something wrong?
Create an async function that handles the api call. Then call the function in the useEffect. Since Repos is an empty array, nothing will be logged. Once your api call resolves and the repos state has been updated, react will do it's thing and re render causing the repos.map to run again and log out the repos
const Component = () => {
const [repos, setRepos] = useState([])
const fetchData = async ()=>{
let res = await fetch('https://api.github.com/users/alexspurlock25/repos')
let data = await res.json()
setRepos(data)
}
useEffect(() => {
// fetch call used to be here
fetchData()
}, [])
return (
<div>
{
repos.map(items => console.log(items))
}
</div>
)
}
You have to verify that the repos are defined and contain data to do that you can do the following
//mock up API
const API = (ms = 800) => new Promise(resolve => setTimeout(resolve, ms, {state:200, data:[1,2,3,5]}));
ReactDOM.render(
<App />,
document.body
);
function App(props){
const [repos, setRepos] = React.useState([]);
React.useEffect(() => {
API()
.then(res => setRepos(res.data));
},[])
return <div>{
// Check Here
repos.length > 1 ? repos.map((r,i) => <div key={`${r}-${i}`}>{r}</div>) : <div>Loading ...</div>
}</div>
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
const Component = () => {
const [repos, setRepos] = useState([])
useEffect(() => {
const data = fetch('https://api.github.com/users/alexspurlock25/repos')
.then(response => response.json())
.then(data => setRepos(data))
console.log(data)
}, [])
console.log(repos)
return (
<div>
{
repos.map(items => console.log(items))
}
</div>
)
}
I have a question about useEffect. My useEffect is not fetching the data the first time, I have to switch route for it to have the data I needed
const Comments = ({ ...rest }) => {
const theme = useTheme();
const classes = useStyles({ theme });
const [users, setUsers] = useState([]);
const { push } = useHistory();
const { token, loading } = useContext(AuthContext)
const dispatch = useDispatch();
const allUsers = useSelector(state => state.allUsers);
const comments = useSelector(state => state.listCommentsByBookId);
const listBooks = useSelector((state) => state.userListBooks);
const isFetching = useSelector((state) => state.isFetching);
const [stateReady, setReadyForRender] = useState(false)
const redirectTo = ( rowData ) => {
push({
pathname: ROUTE.USERS_DETAILS,
user: rowData
});
}
const options = {
filterType: 'checkbox',
selectableRowsHeader: false,
selectableRowsHideCheckboxes: false,
selectableRowsOnClick: false,
onRowClick: redirectTo,
};
const getAllComments = async () => {
var allusersId = [];
//get all ids
await allUsers.map((user) => {
allusersId.push(user.uid);
})
//get all books from users
await allusersId.map(async (id) => {
await dispatch(getUserListBooks(apiURL + `api/bdd/userListBooks/${id}`, token))
})
var listArray = [];
//filter the array and delete empty rows
listArray.push(listBooks);
var newArray = listArray.filter(e => e);
//map every user and stock the list of books in string
await newArray.forEach(async (book)=> {
await book.map(async (book) => {
await dispatch(getCommentsByBookId(apiURL + `api/bdd/ratingByBook/${book.id}`, token));
})
})
setReadyForRender(true)
}
useEffect(() => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
}, [stateReady])
console.log('COM', comments);
return (
<div>
{stateReady &&
<Card>
<Box className={classes.tableContainer} sx={{ minWidth: 1050 }}>
<MUIDataTable
data={comments}
columns={columns}
options={options}
/>
</Box>
</Card>}
</div>
);
};
Why? It might be related to async await but I'm stuck here.
If you want to fetch these informations on the first render, you'll have to pass an empty array as the second parameter of your useEffect.
The reason your useEffect is not called is because stateReady does not change during the course of your current code.
See this link, particularly the note section, it explains way better than me how the empty array as second parameter works.
Can you replace the useEffect section to the below code:
useEffect(() => {
(async () => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
})()
}, [stateReady])
You can read more about this in this link
You can use eslint to show errors when coding with hooks. In this case if you want useEffect to handle stateReady, please provide it in the function getAllComments() => getAllComments(stateReady) and when you call this function in useEffect with [stateReady] as dependencies, it'll work.
You should remove stateReady from your dependency array in the useEffect hook. Adding variables in the dependency array means that the use Effect hooks fires only when one of the dependencies changes. Here's how to use useEffect as lifecycle methods https://reactjs.org/docs/hooks-effect.html
useEffect(() => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
});
I want to make an infinite scrolling. The idea is next, when user scroll at the bottom of the scroll area, the http request should occur and to add data to the previous, that exists before. In this way the user if will scroll back to the top will be able to see all options.
For this i created:
import React, { useState } from "react";
import AsyncSelect from "react-select/async";
const WithPromises = () => {
const [page, setPage] = useState(1);
const [allData, setAllData] = useState([]); //here should be added all data
const filterData = (inputValue) => {
const req = fetch(
`https://jsonplaceholder.typicode.com/todos?_limit=15&_page=${page}`
)
.then((response) => response.json())
.then((res) => {
console.log(res, "data");
return res.map(({ title }) => {
return {
label: title,
value: title
};
});
});
return req;
};
const promiseOptions = (inputValue) => {
return filterData(inputValue);
};
const scroll = (e) => {
setPage(page + 1); //when scroll is at the bottom
};
console.log(page);
return (
<AsyncSelect
cacheOptions
onMenuScrollToBottom={scroll}
isClearable={true}
isSearchable={true}
defaultOptions
loadOptions={promiseOptions}
/>
);
};
export default WithPromises;
How you can see i increment the page every time the user is at the bottom of the scroll area.
setPage(page + 1). The value is added in the request.
Question: How to achieve what i decsribed above? When i will scroll down the new values should be added in the whole list and to be saved there and if i will scroll again at the boottm the the page should change and again new data should be added in the whole list.
demo: https://codesandbox.io/s/codesandboxer-example-forked-zsj6i?file=/example.js:0-1070
react-virtualized have an InfiniteLoader HOC which you can use for the implementation of your infinite scrolling menu, let me give you an pseudocode:
function App() {
const [items, setItems] = React.useState([]);
const [rowCount, setRowCount] = React.useState(0);
const rowRenderer = ({ key, index, style }) => (
<div key={key} style={style}>
{items[index]}
</div>
);
const isRowLoaded = ({ index }) => {
return !!items[index];
};
const loadMore = ({ startIndex, stopIndex }) => {
fetch(`https://blahblahblah.com/getData?from=${startIndex}&to=${stopIndex}`)
.then((res) => res.json)
.then((response) => {
setRowCount(response.data.count); //number of results!
return response.data.items.map(({ title }) => ({
label: title,
value: title,
}));
})
.then((formattedData) => setItems((prev) => [...prev, formattedData])); //add new datas to the previous list
};
return (
<InfiniteLoader
isRowLoaded={isRowLoaded}
loadMoreRows={loadMore}
rowCount={rowCount}>
{({ onRowsRendered }) => (
<List
onRowsRendered={onRowsRendered}
rowCount={rowCount}
rowRenderer={rowRenderer}
/>
)}
</InfiniteLoader>
);
}
Alternating between the 2 buttons will display first names or last names, but pressing them together really fast will chain requests and will combine the two. How can I make create a check, and only display the names from the button that was pressed last
export default function App() {
const [name, setName] = useState();
return (
<div className="App">
<button onClick={() => setName("first_name")}>1</button>
<button onClick={() => setName("last_name")}>2</button>
<Users name={name} />
</div>
);
}
export default function Users({ name }) {
const [users, setUsers] = useState([]);
useEffect(() => {
setUsers([]);
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers((prev) => [...prev, ...allUsers]);
})
.catch((e) => {
console.log(e);
});
}, [name]);
return <div className="Users">{users}</div>;
}
Here is a great article by Dan Abramov about the useEffect hook in which he also talks about how to handle race cases- https://overreacted.io/a-complete-guide-to-useeffect/#speaking-of-race-conditions
To solve your issue, create a variable like let didCancel = false at the start of useEffect. Then, you have to return a function from useEffect, which automatically runs at the time when the name changes next time. In that function set didCancel to true. Now, you have to handle fetch response only if didCancel is false. This way, you are discarding all fetch responses received from second-last, third-last, etc. button presses, and handling fetch response only from the last button press.
Here is updated useEffect code:-
useEffect(() => {
let didCancel = false;
setUsers([]);
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
if (!didCancel) {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers((prev) => [...prev, ...allUsers]);
}
})
.catch((e) => {
console.log(e);
});
return () => {
didCancel = true;
};
}, [name]);
return <div className="Users">{users}</div>;
}
you have to create a loading state, and the user should not be able to send a new request until the data is received... you can create a hook for this or use SWR:
let me give you an example:
function Users(usersList) {
return (
<ul>
{usersList.map((user, key) => (
<li key={key}>{user}</li>
))}
</ul>
);
}
const useFetchUsers = (name) => {
const [isLoading, setIsLoading] = React.useState(true);
const [error, setError] = React.useState(null);
const [data, setData] = React.useState([]);
React.useEffect(() => {
setIsLoading(true);
setError(null);
fetch('https://blahblahblah.com/api/users')
.then((res) => res.json())
.then((response) => setData(response))
.catch((err) => setError(err))
.finally(() => setIsLoading(false));
}, [name]);
return {
isLoading,
error,
data,
};
};
function App() {
const [name, setName] = React.useState('Tom');
const { isLoading, error, data } = useFetchUsers(name);
const handleSubmitName = (name) => {
if (isLoading) alert('wait!');
else setName(name);
};
if (error) return <>an error occured</>;
if (data)
return (
<>
<button onClick={() => handleSubmitName('first_name')}>1</button>
<button onClick={() => handleSubmitName('last_name')}>2</button>
<Users name={name} />
</>
);
}
hint/note: it's just pseudocode and there are some tools to do data fetching + caching.
The problem is in this line setUsers((prev) => [...prev, ...allUsers]);. You are assuming that prev is [], but when the second request is resolve prev has data, that is why you see the request are combined:
I recommend to change your useEffect block to avoid the problem you are facing:
useEffect(() => {
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers(...allUsers); //--> with the last name's value
})
.catch((e) => {
console.log(e);
});
}, [name]);