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>
)
}
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]);
Link to CodeSandBox of what I am experiencing:
https://codesandbox.io/s/intelligent-chaum-eu1le6?file=/src/About.js
I am stuggling to figure out why a component will not re-render after a state changes. In this example, it is an array prop given from App.js to About.js.
a fetch request happens three times in a useEffect. Each time, it pushes it to stateArr before finally setState(stateArr)
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
setState(stateArr);
The About component is imported, and the useState variable is passed to it as a prop.
return (
<div>
<About arrayProp={state} />
</div>
);
Finally, About.js destructs the prop, and arrayProp.map() is called to render each array item on the page.
const About = ({ arrayProp }) => {
const [rerender, setRerender] = useState(0);
return (
<>
{arrayProp.map((e) => (
<div key={e.length}>
<h6>Break</h6>
{e.fact}
</div>
))}
</>
);
};
In the CodeSandBox example, I've added a button that would manually re-render the page by incrementing a number on the page.
The prop should prompt a component re-render after the fetch requests are completed, and the state is changed.
The issue is that useEffect is not behaving as described.
Each time, it pushes it to stateArr before finally setState(stateArr)
The individual fetches are not pushing to "before finally" calling setState.
const [state, setState] = useState([]);
useEffect(() => {
let stateArr = [];
function getReq() {
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
setState(stateArr);
}
getReq();
}, []);
What is actually happening is: fetch 1 is starting, then fetch 2 is starting, then fetch 3 is starting, then setState(stateArr) is being called.
There's no guarantee that these fetch will resolve before setState is called (there's similarly no guarantee that the fetches won't complete before calling setState). Though, in normal circumstances none of the fetches will resolve before setState is called.
So the only thing that's guaranteed is that state will be updated to reference the same array as stateArr. For this reason, pushing to stateArr is the same as pushing to state which is mutating state without using setState. This can cause results to be overwritten on future setState calls and it does not cause a re-render.
Well then, why does forcing re-render in About work?
As each fetch resolves it pushes values to stateArr (which is the same array as is referenced by state) for this reason the values are in the state there's just been nothing to tell React re-render (like a setState call).
Here's a small snippet which logs the promises as they complete. It also has a button that will console log the state array. (Nothing will ever render here as nothing will cause the state to update despite the state array being modified)
// Use import in normal cases; const is how use* are accessed in Stack Snippets
const {useState, useEffect} = React;
const App = () => {
const [state, setState] = useState([]);
useEffect(() => {
let stateArr = [];
function getReq() {
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
console.log('Promise 1 resolves', stateArr);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
console.log('Promise 2 resolves', stateArr);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
console.log('Promise 3 resolves', stateArr);
});
console.log('Calling Set State')
setState(stateArr);
}
getReq();
}, []);
return (
<div>
<button onClick={() => console.log(state)}>Log State Array</button>
{state.map((e) => (
<div key={e.length}>
<h6>Break</h6>
{e.fact}
</div>
))}
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>
To resolve this, simply wait for all promises to complete with Promise.all, then call setState with all the values.
const [state, setState] = useState([]);
useEffect(() => {
Promise.all([
// Promise 1
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 2
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 3
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
})
]).then((newStateArr) => {
// Wait for all promises to resolve before calling setState
setState(newStateArr);
});
}, []);
And here's a snippet demoing the result when waiting for all promises to resolve:
// Use import in normal cases; const is how use* are accessed in Stack Snippets
const {useState, useEffect} = React;
const App = () => {
const [state, setState] = useState([]);
useEffect(() => {
Promise.all([
// Promise 1
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 2
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 3
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
})
]).then((newStateArr) => {
// Wait for all promises to resolve before calling setState
setState(newStateArr);
});
}, []);
return (
<div>
{state.map((e) => (
<div key={e.length}>
<h6>Break</h6>
{e.fact}
</div>
))}
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>
I'm trying to learn ReactJS..
Today I was trying to create an array of objects with fetch results and after that create the cards, but I just can update the state but the cards are not re-render.. can you help me?
App.js
const teamsForLoop = [
Team1,
Team2,
Team3
];
const [allPlayers, setAllPlayers] = useState([]);
const [team, setTeam] = useState([]);
const [allTeams] = useState(teamsForLoop);
const [loading, setLoading] = useState(true);
useEffect(() => {
const playerInfo = async() => {
setLoading(true)
allTeams.map(async(teamArray) => {
setTeam([])
teamArray.map(async (player) => {
let playerName = player.split(" ");
const result = await axios.get(
`https://www.thesportsdb.com/api/v1/json/2/searchplayers.php?p=${playerName[0]}%20${playerName[1]}`
);
if (result.data.player === null) {
setTeam((state) => {
return [...state];
});
} else {
setTeam((state) => {
return [...state, result.data.player[0]];
});
}
});
setAllPlayers(team);
});
setLoading(false);
};
playerInfo();
},[]);
if (loading) return "...Loading...";
return (
<>
<PlayerList allPlayers={allPlayers} />
</>
);
}
export default App;
PlayerList.js
function PlayerList({ allPlayers }) {
const myData = []
.concat(allPlayers)
.sort((a, b) => (a.idTeam !== b.idTeam ? 1 : -1))
return (
<div>
{myData.map((player,index) =>
(
<div key={index}>
...........
</div>
)
)}
</div>
);
}
I think my problem was on the useEffect hook or maybe on my fetch function..
I already have done it using just arrays but without state.
Issue
The issue I see now is that you are attempting to cache the fetched players in the team state in the loops and then use the team state to update the players state. The problem here is that React state updates are asynchronously processed, so team hasn't updated when setAllPlayers(team); is called.
Solution
It would be simpler to map the allTeams arrays to the GET requests, wait for them to resolve, and enqueue a single allPlayers state update. Flatten the arrays of team's players and map these to the axios GET Promise. Wait for these to resolve and map the results to the array of players.
Example:
function App() {
const [allPlayers, setAllPlayers] = useState([]);
const [allTeams] = useState(teamsForLoop);
const [loading, setLoading] = useState(true);
const playerInfo = async () => {
setLoading(true);
const response = await Promise.all(
allTeams
.flat()
.map((player) =>
axios.get(
`https://www.thesportsdb.com/api/v1/json/2/searchplayers.php?p=${player}`
)
)
);
const players = response.map((result) => result.data.player[0]);
setAllPlayers(players);
setLoading(false);
};
useEffect(() => {
playerInfo();
}, []);
if (loading) return "...Loading...";
return <PlayerList allPlayers={allPlayers} />;
}
I have a component the uses useEffect to fetch data from a file.
In the component i have a condiiton that only shows the content of the component if we have data.
Now how can a test the conditional part of the content i my test case?
This is what i have right now:
Component:
function MunicipalityInfo() {
const [municipalityData, setMunicipalityData] = useState({})
const fetchData = async () => {
try{
const result = await fetch(XMLFile)
const data = await result.text();
const xml = new XMLParser().parseFromString(data);
const res = XMLMapper(xml)
setMunicipalityData(res)
}catch(e){
console.log(e)
}
}
useEffect(() => {
fetchData();
}, []);
return(
<>
{ municipalityData.units &&
municipalityData.units.map((city, index) => {
return (
<Div key={index} data-testid="municipalityInfo-component" className="mt-5 p-3">
<HeaderMain data-testid="header-main">{city.City}</HeaderMain>
<HeaderSub data-testid="header-sub" className="mt-4">{city.venamn}</HeaderSub>
<BodyText data-testid="body-text">{city.Address}, {city.City}</BodyText>
<MapLink href={"#"} data-testid="map-link"><i data-testid="map-icon" className="fas fa-map-marker-alt"></i> Show on map</MapLink>
<LinkList data-testid="link-list">
<LinkListItem data-testid="list-item-first">
<Link href={city.BookingURL} data-testid="link-book-vaccination">Some text</Link>
</LinkListItem>
</LinkList>
<Calendar data={city.unit}/>
</Div>
)
})
}
<CitiesSideBar>
<Sidebar data={municipalityData.cities}/>
</CitiesSideBar>
</>
)
}
export default MunicipalityInfo;
And this is my test:
describe("<MunicipalityInfo />", () => {
it("renders without crashing", async () => {
const {queryByTestId, findByText, findByTestId} = render(<MunicipalityInfo/>, {})
expect(queryByTestId("municipalityInfo-component")).not.toBeInTheDocument();
expect(await findByTestId("municipalityInfo-component")).toBeInTheDocument(); <--- this line fails
})
})
And the error of my testcase:
TestingLibraryElementError: Unable to find an element by: [data-testid="municipalityInfo-component"]
if your problem is trying to test if something shouldn't be in the page...
use the queryBy
if you're want it to wait for something... then you want to await findBy (or wrap in a waitFor)
here's the docs: https://testing-library.com/docs/react-testing-library/cheatsheet/
I'm assuming you're mocking the fetch request so it wouldn't be the test problem...
if you're not mocking it... then you probably should mock and return either data or no data to test if it should or not render.
one way to elegantly "avoid" mocking would be by abstracting it in a custom hook:
function useCustomHook(){
const [municipalityData, setMunicipalityData] = useState({})
useEffect(() => {
fetch(XMLData)
.then((res) => res.text())
.then(async (data) => {
let xml = new XMLParser().parseFromString(data);
let result = await XMLMapper(xml)
setMunicipalityData(await result)
})
.catch((err) => console.log(err));
}, []);
return municipalityData;
}
function MunicipalityInfo({municipalityData = useCustomHook()}) { ... }
then in the test you can simply
render(<MunicipalityInfo municipalityData={'either null or some mocked data'} />)
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>
}