I am trying to implement load more button for my small project GiF generator. First I thought of appending next set of 20 response at the bottom, but failed to do.
Next, I thought of implementing loading the next set of 20 results by simply removing the current one. I tried to trigger a method on click of button, but I failed to do so. Its updating the state on second click of load more and then never updating it again.
Please help me find what I am missing, I have started learning React yesterday itself.
import React, { useEffect, useState } from 'react';
import './App.css';
import Gif from './Gif/Gif';
const App = () => {
const API_KEY = 'LIVDSRZULELA';
const [gifs, setGif] = useState([]);
const [search, setSearch] = useState('');
const [query, setQuery] = useState('random');
const [limit, setLimit] = useState(20);
const [pos, setPos] = useState(1);
useEffect(() => {
getGif();
}, [query])
const getGif = async () => {
const response = await fetch(`https://api.tenor.com/v1/search?q=${query}&key=${API_KEY}&limit=${limit}&pos=${pos}`);
const data = await response.json();
setGif(data.results);
console.log(data.results)
}
const updateSearch = e => {
setSearch(e.target.value);
}
const getSearch = e => {
e.preventDefault();
setQuery(search);
setSearch('');
}
const reload = () => {
setQuery('random')
}
const loadMore = () => { // this is where I want my Pos to update with 21 on first click 41 on second and so on
let temp = limit + 1 + pos;
setPos(temp);
setQuery(query);
}
return (
<div className="App">
<header className="header">
<h1 className="title" onClick={reload}>React GiF Finder</h1>
<form onSubmit={getSearch} className="search-from">
<input className="search-bar" type="text" value={search}
onChange={updateSearch} placeholder="type here..." />
<button className="search-button" type="submit">Search</button>
</form>
<p>showing results for <span>{query}</span></p>
</header>
<div className="gif">
{gifs.map(gif => (
<Gif
img={gif.media[0].tinygif.url}
key={gif.id}
/>
))}
</div>
<button className="load-button" onClick={loadMore}>Load more</button>
</div>
);
}
export default App;
Please, help me find, what I am doing wrong, As I know the moment I will update setQuery useEffect should be called with new input but its not happening.
Maybe try something like this:
// Fetch gifs initially and then any time
// the search changes.
useEffect(() => {
getGif().then(all => setGifs(all);
}, [query])
// If called without a position index, always load the
// initial list of items.
const getGif = async (position = 1) => {
const response = await fetch(`https://api.tenor.com/v1/search?q=${query}&key=${API_KEY}&limit=${limit}&pos=${position}`);
const data = await response.json();
return data.results;
}
// Append new gifs to existing list
const loadMore = () => {
let position = limit + 1 + pos;
setPos(position);
getGif(position).then(more => setGifs([...gifs, ...more]);
}
const getSearch = e => {
e.preventDefault();
setQuery(search);
setSearch('');
}
const updateSearch = e => setSearch(e.target.value);
const reload = () => setQuery('random');
Basically, have the getGifs method be a bit more generic and then if loadMore is called, get the next list of gifs from getGift and append to existing list of gifs.
Related
I'm testing a React app for the first time and I'm struggling to write tests that check if an array in a component has 2 elements on first render and on clicking a button.
The error I'm getting is TypeError: Expected container to be an Element, a Document or a DocumentFragment but got string.
Here's the component where I need to test usersCards - it needs to have two elements on first render and every time the user clicks 'deal'.
I'm not sure how to deal with variables in components - do I mock it up in the test file? Ant help appreciated!
\\imports
export default function Home(){
const startHandSize = 2
const [starterDeck, setStarterDeck] = useState(shuffle(deckArray))
const [howManyDealt, setHowManyDealt] = useState(startHandSize)
const [total, setTotal] = useState(0)
const [ace, setAce] = useState(0)
const deal = () => {
setHowManyDealt(startHandSize)
setStarterDeck(shuffle(deckArray))
setAce(0)
}
const hit = () => !bust && setHowManyDealt(prev => prev + 1)
const usersCards = starterDeck.slice(-howManyDealt)
const bust = total > 21;
useEffect(() => {
setTotal(usersCards.reduce((a, e) => a + e.value, 0) + ace)
}, [ace, usersCards])
return(
<div>
{
<>
<button data-testid="deal" onClick={deal}>DEAL</button>
<button data-testid="hit" disabled={bust} onClick={hit}>HIT</button>
<button disabled={bust}>STAND</button>
<Total total={total}/>
{usersCards.map(card => (
<Card data-testid="test-card" key={card.index}
card={card} setTotal={setTotal} total={total}
ace={ace} setAce={setAce}
/>
))}
</>}
</div>
)
}
Here's the test:
//Deal button test
test("on initial render, two cards are displayed", () => {
render(<Home />)
const cards = getAllByTestId('test-card')
expect(cards.length).toEqual(2)
})
I guess something like that would work:
test("on initial render, two cards are displayed", () => {
const { getAllByTestId } = render(<Home />);
const cards = getAllByTestId('test-card');
expect(cards.length).toEqual(2);
});
test("two new cards should be displayed after clicking the button", () => {
const { getAllByTestId, getByTestId } = render(<Home />);
const dealButton = getByTestId('deal');
fireEvent.click(dealButton);
const cards = getAllByTestId('test-card');
expect(cards.length).toEqual(2);
});
How can we handle disabling of dropdown selection in react hooks web app without refreshing the page ? In my case, I have a multiselect dropdown box. When I selected an item from the dropdown, display a text field, once after typing some text and submit it, details get saved into database. Once saved into DB, the respective dropdown item should be disabled for selection.
But in my case, its not immediately disabled after submit. Its is disabled only after i manually refresh the page. How can I fix this issue, can someone please advise ?
const [option, setOption] = useState([]);
const [selectedOption, setSelectedOption] = useState([]);
const {
register,
handleSubmit,
watch,
formState: { errors },
reset,
} = useForm();
const refSelect = useRef(null);
const [submittedNominees, setSubmittedNominees] = useState([{}]);
const [maxOptions, setMaxOptions] = useState(0);
const [showOptions, setShowOptions] = useState(false);
const focusOnInput = () => {
setTimeout(() => {
document.querySelector("input").focus();
// Adding some delay to allow the component to re-mount
}, 10);
};
const handleTypeSelect = (e, i) => {
const copy = [...selectedOption];
copy.push(e[3 - maxOptions]); //A.H-fix error: select one more record it still console log the pre selected one
setSelectedOption(copy);
setMaxOptions((prevState) => prevState - 1); //A.H-making maxOption dynamic
focusOnInput();
};
const handleTypeRemove = (e) => {
const copy = [...selectedOption];
let index = copy.indexOf(e);
copy.splice(index, 1);
setSelectedOption(copy);
setMaxOptions((prevState) => prevState + 1);
// immutating state (best practice)
const updateList = nomRegister.map((item) => {
return { ...item };
});
//delete the specific array case depends on the id
updateList.splice(index, 1);
setNomRegister(updateList);
focusOnInput();
};
useEffect(() => {
const fetchData = async () => {
const userEmail = localStorage.getItem("loginEmail");
try {
let res = [];
res = await Axios.get(
`${appURL}/service/submittednominations`,
{params:{userEmail}}
);
const data1 = res.data;
console.log(data1, "data1");
setSubmittedNominees(data1);
setMaxOptions(3 - data1.length); //maxOption dynamic because we don't the length of data from submittednominations
console.log("Submitted nominations :" + JSON.stringify(data1));
} catch (e) {
console.log(e);
}
};
fetchData();
}, []);
Droddown box:
<section className="col1">
<div className='nomineeSelectBox'>
<div id='dialog2' className='triangle_down1' />
<div className='arrowdown'>
<Multiselect
ref={refSelect}
onSelect={(e) => handleTypeSelect(e, selectedOption.length)}
onRemove={handleTypeRemove}
options={!showOptions ? [] : option}
displayValue='displayValue'
disablePreSelectedValues={true}
selectedValues={submittedNominees}
showCheckbox={true}
emptyRecordMsg={"Maximum nominees selected !"}
/>
</div>
</div>
</section>
If you add the changed state to the dependency list of the useEffect(), it will re-run that section of code inside.
useEffect(() => {
// this code will re-run every time "selectedOptions" is changed
// therefore every time "handleTypeSelect()" or "handleRemove()" is run
const fetchData = async () => {
...
}, [selectedOption])
This will trigger a fresh list of submittedNominees, and cause a rerender (just the component, not the whole page) because submittedNominees is one of the render properties
<Multiselect
...
selectedValues={submittedNominees}
...
/>
Alternatively (and probably quicker UI), make a call to setSubmittedNominees() inside handleTypeSelect() and handleRemove(). That will also trigger a component rerender.
What I want is to paginate my data but the problem is when I'm searching for specific data if I'm on page 3 the result shows on page 1 always and I can't see anything because I was on page no 3. I want to go to page 1 automatically when I'm searching for something. Also when I press the next button if there is no data at all it still increases the page number.
Here is my code:
import { React, useState, useEffect } from "react";
import UpdateDialogue from "./UpdateDialogue";
function List(props) {
const API_URL = "http://dummy.restapiexample.com/api/v1/employees";
const [EmployeeData, setEmployeeData] = useState([]);
const [pageNumber, setPageNumber] = useState(1);
const [postNumber] = useState(8);
const currentPageNumber = pageNumber * postNumber - postNumber;
const handlePrev = () => {
if (pageNumber === 1) return;
setPageNumber(pageNumber - 1);
};
const handleNext = () => {
setPageNumber(pageNumber + 1);
};
useEffect(() => {
fetch(API_URL)
.then((response) => response.json())
.then((response) => {
setEmployeeData(response.data);
})
.catch((err) => {
console.error(err);
});
}, []);
const filteredData = EmployeeData.filter((el) => {
if (props.input === "") {
return el;
} else {
return el.employee_name.toLowerCase().includes(props.input)
}
});
const paginatedData = filteredData.splice(currentPageNumber, postNumber);
return (
<>
<ul>
{paginatedData.map((user) => (
<UpdateDialogue user={user} key={user.id} />
))}
</ul>
<div>Page {pageNumber} </div>
<div>
<button style={{marginRight:10}} onClick={handlePrev}>prev</button>
<button onClick={handleNext}>next</button>
</div>
</>
);
}
export default List;
Maybe with a useEffect on your input:
useEffect(() => {
if (props.input) {
setPageNumber(1);
}
}, [props.input]);
That way, whenever your input changes, your page number is set to 1.
I am building a chat app and trying to match the id params to render each one on click.I have a RoomList component that maps over the rooms via an endpoint /rooms
I then have them linked to their corresponding ID. THe main components are Chatroom.js and RoomList is just the nav
import moment from 'moment';
import './App.scss';
import UserInfo from './components/UserInfo';
import RoomList from './components/RoomList';
import Chatroom from './components/Chatroom';
import SendMessage from './components/SendMessage';
import { Column, Row } from "simple-flexbox";
import { Route, Link, Switch } from 'react-router-dom'
function App() {
const timestamp = Date.now();
const timeFormatted = moment(timestamp).format('hh:mm');
const [username, setUsername] = useState('');
const [loggedin, setLoggedin] = useState(false);
const [rooms, setRooms] = useState([]);
const [roomId, setRoomId] = useState(0);
const handleSubmit = async e => {
e.preventDefault();
setUsername(username)
setLoggedin(true)
};
useEffect(() => {
let apiUrl= `http://localhost:8080/api/rooms/`;
const makeApiCall = async() => {
const res = await fetch(apiUrl);
const data = await res.json();
setRooms(data);
};
makeApiCall();
}, [])
const handleSend = (message) => {
const formattedMessage = { name: username, message, isMine: true};
}
return (
<div className="App">
<Route
path="/"
render={(routerProps) => (
(loggedin !== false) ?
<Row>
<Column>
{/*<Chatroom roomId={roomId} messages={messages} isMine={isMine}/>*/}
</Column>
</Row>
:
<form onSubmit={handleSubmit}>
<label htmlFor="username">Username: </label>
<input
type="text"
value={username}
placeholder="enter a username"
onChange={({ target }) => setUsername(target.value)}
/>
<button type="submit">Login</button>
</form>
)}
/>
<Switch>
<Route
exact
path="/:id"
render={(routerProps) => (
<Row>
<Column>
<UserInfo username={username} time={timeFormatted}/>
<RoomList rooms={rooms}/>
</Column>
<Column>
<Chatroom {...routerProps} roomId={roomId}/>
<SendMessage onSend={handleSend}/>
</Column>
</Row>
)}
/>
</Switch>
</div>
);
}
export default App;
RoomList.js
import { Row } from "simple-flexbox";
const RoomList = (props) => {
return (
<div className="RoomList">
<Row wrap="false">
{
props.rooms.map((room, index) => {
return (
<Link to={`/${room.id}`} key={index}>{room.id} {room.name}</Link>
)
})
}
</Row>
</div>
)
}
export default RoomList;
Chatroom.js
this is the main component that should render based on the ID
import Message from './Message';
import { Link } from 'react-router-dom'
const Chatroom = (props) => {
const [roomId, setRoomId] = useState(0);
const [name, setName] = useState('Roomname')
const [messages, setMessages] = useState([]);
useEffect(() => {
let apiUrl= `http://localhost:8080/api/rooms/`;
const id = props.match.params.id;
const url = `${apiUrl}${id}`;
const makeApiCall = async () => {
const res = await fetch(url);
const data = await res.json();
setRoomId(data.id);
setUsers(data.users)
setName(data.name)
};
makeApiCall();
}, []);
useEffect(() => {
const id = props.match.params.id;
const url = `http://localhost:8080/api/rooms/${id}/messages`;
const makeApiCall = async() => {
const res = await fetch(url);
const data = await res.json();
setMessages(data);
};
makeApiCall();
}, [])
return (
<div className="Chatroom">
{name}
</div>
)
}
export default Chatroom;```
when I click on the links I want the change to refresh the new content but it wont? any ideas why ? thank you in advance!
Notice that your functional component named App does not have any dependencies and that is fine since data should just be fetched once, on mount. However, on ChatRoom we want a new fetch everytime that roomId changes.
First thing we could do here is adding props.match.params.id directly into our initial state.
const [roomId, setRoomId] = useState(props.match.params.id); // set up your initial room id here.
Next we can add an effect that checks if roomId needs updating whenever props change. Like this:
useEffect(()=>{
if(roomId !== props.match.params.id) {
setRoomId(props.match.params.id)
}
}, [props])
Now we use roomId as our state for the api calls and add it in the brackets (making react aware that whenever roomId changes, it should run our effect again).
useEffect(() => {
let url = "http://localhost:8080/api/rooms/" + roomId; // add room id here
const makeApiCall = async () => {
const res = await fetch(url);
const data = await res.json();
setUsers(data.users)
setName(data.name)
};
makeApiCall();
}, [roomId]); // very important to add room id to your dependencies as well here.
useEffect(() => {
const url = `http://localhost:8080/api/rooms/${roomId}/messages`; // add room id here as well
const makeApiCall = async() => {
const res = await fetch(url);
const data = await res.json();
setMessages(data);
};
makeApiCall();
}, [roomId]) // very important to add room id to your dependencies as well here.
I believe that it should work. But let me build my answer upon this:
When mounted, meaning that this is the first time that the ChatRoom is rendered, it will go through your useEffect and fetch data using roomId as the initial state that we setup as props.match.params.id.
Without dependencies, he is done and would never fetch again. It would do it once and that's it. However, by adding the dependency, we advise react that it would watch out for roomId changes and if they do, it should trigger the function again. It is VERY IMPORTANT that every variable inside your useEffect is added to your brackets. There is eslint for it and it is very useful. Have a look at this post. It helped me a lot.
https://overreacted.io/a-complete-guide-to-useeffect/
Let me know if it works and ask me if there is still doubts. =)
I'm making an effort to implement a loading using hooks on react.
I can do it using componentDidMount, but this applications uses Hoocks.
I create the state and the changestate, but i can not set and use it on my html.
Here is my code:
First of all i made a get request whit axios and async/await
const fetchContent = async content => {
const data = []
for await (const item of content) {
const info = await axios.get(
`url/id`
)
data.push({ componentDisplay: item.title });
}
return data
}
then i call it whit usseEffect
const ContentGroups = ({ content , ads}) => {
const [contentResult, setResult] = useState([])
const [contentLoading, changeCondition] = useState(true)
const change = () => {
changeCondition(false)
}
useEffect(
() => {
fetchContent(content).then(data => setResult(data)
change()
},
[content]
)
return (
<React.Fragment>
{ contentLoading ? <Loading /> : <Conteiner> } // always show me the container, although contentLoading innitial state is true..
</div>
</React.Fragment>
)
}