I am new to React and am trying to create my own scrollbar. I am using a local JSON API to simulate getting data which is then listing the data as 'cards'. I built a couple of other components to organize and handle the information, it looks like this:
Scrollbar (Buttons to Navigate)
|-->CardList (Handles iterating over the cards)
|-->Cards (Template for displaying an individual card)
The issue I am having is that when I trigger the Button event handleNext it will successfully update offset & limit and pass these to CardList. However, it does not reiterate over the array and output the next 5 items. Instead it removes all the Cards from CardList and I am left with an empty screen.
Scrollbar.js:
const { data isPending, error} = useFetch ('http://localhost:8000/data');
const [limit, setLimit] = useState(5);
const [offset, setOffset] = useState(0);
const handleNext = () => {
setOffset(offset + limit);
console.log("Offset: " +offset);
}
return (
<div className="scrollbar">
{error && <div>{error}</div>}
{isPending && <div>Loading...</div>}
{data && <CardList data={data} offset={offset} limit={limit}/> }
<Button onClick={handleNext}/>
</div>
);
}
export default Scrollbar;
CardList.js:
const CardList = ({data , offset, limit}) => {
return (
<div className="card-list" >
{data.slice(offset,limit).map((data) => (
<Card data={data} key={data.id}/>
))}
</div>
);
}
export default CardList;
The problem is when handleNext is triggered offset is going to be equal to 5 while limit keeps the value of 5 too, then when u do slice(offset,limit) is going to be replaced by slice(5,5) that returns an []. What u want is increasing the limit too if u increase the offset, for example:
const handleNext = () => {
setOffset(offset + limit);
setLimit(limit + limit)
}
Related
I was wondering how can emulate Tinder's swipe cards mechanism by getting my cards representing each user's information to be fixed in a single position overlapping one another completely so that the only visible one is the one on top until you swipe it away.
I have used the react-tinder-card library to implement the swiping mechanism, however I don't understand how to get these cards to stay on top of one another so the next one becomes visible when swiped.
Currently, they are positioned in a 2d tower/column and when the top is swiped, after about 3 seconds, the below cards all move up by one:
OnSwipe:
Swiped:
Code:
export const SwipeCard = () => {
//array of compatible users fetched for a user.
const [users, setUsers] = useState([]);
const [lastDirection, setLastDirection] = useState()
const [isLoading, setLoading] = React.useState(true);
useEffect(() => {
getUsers().then(() => {
setLoading(false);
});
}, []);
const swiped = (direction, nameToDelete) => {
console.log('removing: ' + nameToDelete)
setLastDirection(direction)
}
const outOfFrame = (firstName) => {
console.log(firstName + ' left the screen!')
}
const getUsers = async () => {
//Currently hardcoded value until authorisation implemented.
const userId = 7;
const response = await UserService.getUsers(userId)
.then(response => response.json())
.then(data => {
for(let i = 0; i < data.length; i++){
users[i] = data[i];
}
});
}
if (isLoading) {
return (
<div/>
)
}
return (
<div>
<div id='tinderCards'>
{lastDirection ? <h2 className='text'>You swiped {lastDirection}</h2> : <h2 className='text' />}
{users.map((user) =>
<TinderCard className='swipeCard' key={user.firstName} onSwipe={(dir) => swiped(dir, user.firstName)} onCardLeftScreen={() => outOfFrame(user.firstName)}>
<div className='card'>
<img id='profileImg' src={config.URL + '/users/' + user.userID + '/image/download'} />
<h2>{user.firstName} {user.lastName}</h2>
</div>
</TinderCard>
)}
</div>
</div>
)
}
The closest I have been able to come was by setting the parent to position = relative, and then the children cards to position = absolute, but then they all overlap one another, making it unreadable, and also they are stacked in reverse order, so Nike Tyson was on the top when he should've been at the bottom.
Thanks for any help!
I think the easiest way is to use an animation library like framer-motion for this. I've seen this article that you can maybe follow.
So I'm building a drum-pad type of app, and almost everything is working, except this.
Edit: Put the whole thing on codesandbox, if anyone wants to have a look: codesandbox.io/s/sleepy-darwin-jc9b5?file=/src/App.js
const [index, setIndex] = useState(0);
const [text, setText] = useState("");
const [theSound] = useSound(drumBank[index].url)
function playThis(num) {
setIndex(num)
}
useEffect(()=>{
if (index !== null) {
setText(drumBank[index].id);
theSound(index);
console.log(index)
}
}, [index])
When I press a button, the index changes to the value associated with the button and then the useEffect hook plays the sound from an array at that index. However, when I press the same button more than once, it only plays once, because useState doesn't re-render the app when the index is changed to the same value.
I want to be able to press the same button multiple times and get the app to re-render, and therefore useEffect to run every time I press the button. Can anyone help me how to do this?
Here's what I could come up with from your sandbox.
According to the docs each useSound is just a single sound, so when trying to update an index into a soundbank to use via React state the sound played will always be at least one render cycle delayed. I suggest creating a new custom hook to encapsulate your 9 drum sounds.
useDrumBank consumes the drumbank array and instantiates the 9 drum sounds into an array.
const useDrumBank = (drumbank) => {
const [drum0] = useSound(drumbank[0].url);
const [drum1] = useSound(drumbank[1].url);
const [drum2] = useSound(drumbank[2].url);
const [drum3] = useSound(drumbank[3].url);
const [drum4] = useSound(drumbank[4].url);
const [drum5] = useSound(drumbank[5].url);
const [drum6] = useSound(drumbank[6].url);
const [drum7] = useSound(drumbank[7].url);
const [drum8] = useSound(drumbank[8].url);
return [drum0, drum1, drum2, drum3, drum4, drum5, drum6, drum7, drum8];
};
Update the component logic to pass the drumBank array to the new custom hook.
const sounds = useDrumBank(drumBank);
Here's the full code:
function App() {
useEffect(() => {
document.addEventListener("keypress", key);
return () => document.removeEventListener("keypress", key);
}, []);
const [text, setText] = useState("");
const sounds = useDrumBank(drumBank);
function playThis(index) {
drumBank[index]?.id && setText(drumBank[index].id);
sounds[index]();
}
function key(e) {
const index = drumBank.findIndex((drum) => drum.keyTrigger === e.key);
index !== -1 && playThis(index);
}
return (
<div id="drum-machine" className="drumpad-container">
<div id="display" className="drumpad-display">
<p>{text}</p>
</div>
<button className="drum-pad" id="drum-pad-1" onClick={() => playThis(0)}>
Q
</button>
<button className="drum-pad" id="drum-pad-2" onClick={() => playThis(1)}>
W
</button>
<button className="drum-pad" id="drum-pad-3" onClick={() => playThis(2)}>
E
</button>
<button className="drum-pad" id="drum-pad-4" onClick={() => playThis(3)}>
A
</button>
<button className="drum-pad" id="drum-pad-5" onClick={() => playThis(4)}>
S
</button>
<button className="drum-pad" id="drum-pad-6" onClick={() => playThis(5)}>
D
</button>
<button className="drum-pad" id="drum-pad-7" onClick={() => playThis(6)}>
Z
</button>
<button className="drum-pad" id="drum-pad-8" onClick={() => playThis(7)}>
X
</button>
<button className="drum-pad" id="drum-pad-9" onClick={() => playThis(8)}>
C
</button>
</div>
);
}
Demo
Usage Notes
No sounds immediately after load
For the user's sake, browsers don't allow websites to produce sound
until the user has interacted with them (eg. by clicking on
something). No sound will be produced until the user clicks, taps, or
triggers something.
Getting the keypresses to consistently work seems to be an issue and I don't immediately have a solution in mind, but at least at this point the button clicks work and the sounds are played synchronously.
Have you considered combining those discrete state variables (value types) into a single reference type state object?
Instead of having an effect that sets the text when the index changes you just set the entire state at the same time.
As long as you ensure this is a new object/reference then the effect will fire. The effect is then only responsible for playing the sound based on the current state.
Here's some sample code
const [state, setState] = useState({ index: 0, text: '', });
const playThis = (num) => {
setState({ index: num, text: drumBank[num].id, });
};
useEffect(() => {
theSound(state.index);
}, [state]);
Use react.js. Catching hard to understanding error. My component without pagination work well - show you all items and you can see the item by click. Pagination work fine too, but i cant click on item in item list. Actualy i can click, but displaying only first page items. If you click on item from 2-nd(3,4...n) page you get item from 1-st page.
Open CodePen with my code
export function ListOfItems() {
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(10);
const users = useSelector(state => state);
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = users.slice(indexOfFirstPost, indexOfLastPost);
const paginate = pageNumber => setCurrentPage(pageNumber);
let items = currentPosts.map(function (value, index) {
return (
<form key={index}>
<div className="input-group">
<div className="input-group-prepend">
<Link className="input-group-text" to={`${url}/${index}`}>
{value.name}
</Link>
</div>
</div>
</form>
)
});
return (
<div>
<div>{items}</div>
<Pagination postsPerPage={postsPerPage} totalUsers={users.length} paginate={paginate}/>
</div>
)
}
Recently I've built something like you.
There is a more clean way to do it.
I recommend you to separate your logic in custom hooks.
For example, you can create custom hook:
export const usePagination = (posts, defaultPage = 1, amountPerPage = 10) => {
const [currentPage, setCurrentPage] = useState(defaultPage);
const [postsPerPage] = useState(amountPerPage);
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
let currentPosts = [];
let amountOfPages = 0;
if (Array.isArray(posts)) {
currentPosts = posts.slice(indexOfFirstPost, indexOfLastPost);
amountOfPages = Math.ceil(posts.length / postsPerPage);
}
return {
setCurrentPage,
amountOfPages,
currentPosts,
};
};
And use it in any component you need. For example:
const { setCurrentPage, currentPosts, amountOfPages } = usePagination(yourArrayOfData);
And for example you can use it that way(I was using Material UI Pagination component):
<Pagination
count={amountOfPages}
onChange={(event, page) => setCurrentPage(page)}
/>
And use currentPosts for actually displaying your data.
I know, that it's not direct answer to your question, but recently I have written something like you and it worked perfectly. So I hope that my solution will help you.
I am using hooks in React Native. This is my code:
useEffect(() => {
if (var1.length > 0 ){
let sym = [];
var1.map((items) => {
let count = 0;
state.map((stateItems) => {
if(items === stateItems.name) {
count = count + 1;
}
})
if (count === 0) {
sym.push(items)
}
});
async function getAllStates (sym) {
let stateValues = [];
await Promise.all(sym.map(obj =>
axios.get(ServerURL + "/note?name=" + obj).then(response => {
stateValues.push(response.data[0]);
})
)).then(() =>{
setNewItem(stateValues);
});
}
getAllStates (sym);
}
}, [var1]);
useEffect(() => {
let stateValues = state;
for( let count = 0 ; count < newItem.length; count++ ){
stateValues.push(newItem[count]);
}
setState(stateValues);
}, [newItem]);
This runs successfully without any errors. However, when the state is displayed as below, I am not seeing the latest value added in the state. It shows all the previous values. When I refresh the whole application, I am able to see my value added.
return (
<View style={styles.container}>
<Text style = {{color:"white"}}>
{
state.map( (item, key) =>{
return(
<Text key = {key} style = {{color:"white"}}> {item.name} </Text>
)
})
}
</Text>
</View>
);
Can someone tell me why this is happening? I want to see the data render immediately after the axios call. I am using React Native.
when i force update using :stackoverflow.com/questions/53215285/... it works fine. However, i am looking for a better fix if anyone can provide?
This should do:
useEffect(() => {
var1.forEach(async (name) => {
if (state.some(item => item.name === name)) return;
const response = await axios.get(ServerURL + "/note?name=" + name);
setState(state => [...state, response.data[0]]);
});
}, [var1]);
I still see two issues in your approach:
this code may start the same ajax request multiple times before the result of the firstz ajax-request is added to state; this also means that the result for the same name may be added multiple times to state.
for each item of var1 times each item of state, this is an O(n*m) problem or in this case basically O(n²) as m is pretty fast catching up to n. You should find a better approach here.
And I'm almost certain that [var1] is wrong here as the dependency for useEffect. But you'd need to show where this value comes from to fix that, too.
I'm trying to build a re-usable component to be used for pagination.
Before making you guys read more, this is how my state looks like:
I'm calling it from a component where I'm fetching post from MongoDB.
From there I'm having troubles with useEffect to make the changes and with my Pagination component that I'm not even sure if I'm building it properly.
This is my useEffect:
const params = new URLSearchParams(window.location.search);
const page = parseInt(params.get('page')) || 1;
const limit = parseInt(params.get('limit')) || 1;
const startIndex = (page - 1) * limit;
const endIndex = page * limit;
// const currentPosts = posts.slice(endIndex, startIndex);
const currentPosts = Object.entries(posts).slice(endIndex, startIndex);
console.log(currentPosts);
useEffect(() => {
getPosts(null, null, page, limit);
getCurrentProfile();
}, [getPosts, getCurrentProfile]);
// Change page
const paginate = pageNumber => page(pageNumber);
this is the component I'm calling inside the loop that is fetching the posts.
<Pagination
limit={limit}
totalPosts={posts.data.length}
paginate={paginate}
/>
Finally this is how I'm working with the component, it is obviously still a work in progress.
import React from 'react';
const setPagination = ({ limit, totalPosts, paginate }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalPosts / limit); i++) {
pageNumbers.push(i);
}
return (
<nav>
<ul className='pagination'>
{pageNumbers.map(number => (
<li key={number} className='page-item'>
<a
onClick={() => paginate(number)}
href={`?page=${number}&limit=${limit}`}
className='page-link'
>
{number}
</a>
</li>
))}
</ul>
</nav>
);
};
export default setPagination;
Right now, I'm trying to make it work with query strings from my url:
http://localhost:3000/posts?page=1&limit=1
Can anyone guide me on the logic that I'm missing? I'm having a headache with this xD.
EDIT:Sorry I did not make it clear. This is the problem:
Should not it create more pages?