I'm new to React Js, so I can't find a solution to my problem by myself, please help me.
I'm working on a website with a blog page, blogs should be displayed dynamically on the page. When page loads I want it to have 4 blogs, and underneath there will be button, so when the user clicks it, React should render and display the rest of the blogs.
My code so far looks like this:
import { blogs} from "./blogs";
import { Blog} from "./Blog";
function BlogList() {
const cardComponent = blogs.slice(0,6).map((blog, i) => {
return (
<Blog
key={i}
id={blogs[i].id}
img={blogs[i].img.src}
date={blogs[i].date}
title={blogs[i].title}
img2={blogs[i].img2.src}
logoTitle={blogs[i].logoTitle}
text={blogs[i].text}
/>
);
});
return (
<div>{cardComponent}</div>
)
}`````
**This code lets me display 6 blogs when the page is loaded, what I want to do is add "Load More" button under these already loaded 6 blogs, when the user clicks the button it should render and display another 4 blogs from "blogs", and again have Load More button.** Any help will be greatly appreciated,
Thank you.
Your code shows a fixed amount of blogs (6). Instead of hardcoding the amount of visible blogs, you need to store it in a variable that you can change later. We will use useState for this. You also need to change the amount of posts based on a button press, so a button and an action is also needed.
function BlogList() {
// Starting number of visible blogs
const [visibleBlogs, setVisibleBlogs] = useState(6)
// Set the visible blogs to the current amount + 4
// eg. if there are 10 visible post, clicking again will show 14.
const handleClick = () => {
setVisibleBlogs(prevVisibleBlogs => prevVisibleBlogs + 4)
}
const cardComponent = blogs.slice(0, visibleBlogs).map((blog, i) => {
return (
<Blog
key={i}
id={blogs[i].id}
img={blogs[i].img.src}
date={blogs[i].date}
title={blogs[i].title}
img2={blogs[i].img2.src}
logoTitle={blogs[i].logoTitle}
text={blogs[i].text}
/>
);
});
return (
<div>
{cardComponent}
<button type="button" onClick={handleClick}>
See more
</button>
</div>
)
}
I hope it helps.
You can do it this way:
function BlogList() {
const [maxRange, setMaxRange] = useState(6);
const loadMore = useCallback(() => {
setMaxRange(prevRange => prevRange + 4);
},[])
const cardComponent = blogs.slice(0, maxRange).map((blog, i) => {
return (
<Blog
key={i}
id={blogs[i].id}
img={blogs[i].img.src}
date={blogs[i].date}
title={blogs[i].title}
img2={blogs[i].img2.src}
logoTitle={blogs[i].logoTitle}
text={blogs[i].text}
/>
);
});
return (
<div>
{cardComponent}
<button onClick={loadMore}>Load More</button>
</div>
)
}
So you can just maintain the maximum number of currently displayed Blogs in state and increment it when the button gets clicked.
I used useCallback so that a new function doesn't get created when the component re-renders.
Related
I developed a Simple React Application that read an external API and now I'm trying to develop a Like Button from each item. I read a lot about localStorage and persistence, but I don't know where I'm doing wrong. Could someone help me?
1-First, the component where I put item as props. This item bring me the name of each character
<LikeButtonTest items={item.name} />
2-Then, inside component:
import React, { useState, useEffect } from 'react';
import './style.css';
const LikeButtonTest = ({items}) => {
const [isLike, setIsLike] = useState(
JSON.parse(localStorage.getItem('data', items))
);
useEffect(() => {
localStorage.setItem('data', JSON.stringify(items));
}, [isLike]);
const toggleLike = () => {
setIsLike(!isLike);
}
return(
<div>
<button
onClick={toggleLike}
className={"bt-like like-button " + (isLike ? "liked" : "")
}>
</button>
</div>
);
};
export default LikeButtonTest;
My thoughts are:
First, I receive 'items' as props
Then, I create a localStorage called 'data' and set in a variable 'isLike'
So, I make a button where I add a class that checks if is liked or not and I created a toggle that changes the state
The problem is: I need to store the names in an array after click. For now, my app is generating this:
App item view
localStorage with name of character
You're approach is almost there. The ideal case here is to define your like function in the parent component of the like button and pass the function to the button. See the example below.
const ITEMS = ['item1', 'item2']
const WrapperComponent = () => {
const likes = JSON.parse(localStorage.getItem('likes'))
const handleLike = item => {
// you have the item name here, do whatever you want with it.
const existingLikes = likes
localStorage.setItem('likes', JSON.stringify(existingLikes.push(item)))
}
return (<>
{ITEMS.map(item => <ItemComponent item={item} onLike={handleLike} liked={likes.includes(item)} />)}
</>)
}
const ItemComponent = ({ item, onLike, liked }) => {
return (
<button
onClick={() => onLike(item)}
className={liked ? 'liked' : 'not-liked'}
}>
{item}
</button>
)
}
Hope that helps!
note: not tested, but pretty standard stuff
const curTodos = useRef({});
const handleClickOpen = (o) => {
console.log(o);
curTodos.current = o;
setOpen(true);
};
const allTodos = todos.map((o) => {
console.log("re-render");
return (
<>
<div key={o.id} className="row">
<span>{o.name}</span>
<span>{o.id}</span>
<span>{o.email}</span>
<span>{o.task}</span>
<Button onClick={() => handleClickOpen(o)} variant="outlined">
Edit Todo
</Button>
</div>
</>
);
});
https://codesandbox.io/s/sweet-platform-du3i8x?file=/src/App.js:1593-1664
I made a different component for my modal
When I click on edit todo I want the todo form modal to contain the name and task that the row is on. Currently it just shows up as an empty input
That is,
currently:
I want:
curTodos is a reference to todo object
When I click on edit todos I want the default value to be set to the one on the rows.
Since its already rendered this wont work it just shows up as empty input.
useState(default) value runs only once on mount. Since you're using a component that does not unmount in this view, you can include an effect to update the form state.
// in FormModal
useEffect(() => {
setName(o.name)
setTask(o.task)
}, [o]);
I have a React app that displays a list of cards displaying assignment info for an educational app. Each card contains MUI tabs so that teachers can navigate through students' work. Simplified code below:
teacherClass.jsx
function TeacherClass(props) {
const [classData, setClassData] = React.useState(props.classData);
const [assignments, setAssignments] = React.useState([]);
//assignments are found from Firebase elsewhere in this file. Omitted for simplicity.
const [tabValue, setTabValue] = React.useState(0);
const handleTabChange = (event, newValue) => {
setTabValue(newValue);
}
const AssignmentCard = (assignment) => (
<Card>
<Tabs value={tabValue} onChange={handleTabChange} >
{classData.students ? classData.students.map((stu) =>
<Tab label={stu.name} value={stu} />) : null }
</Tabs>
</Card>
)
return (
<div>
{ assignments ? assignments.map((assignment) => AssignmentCard(assignment)) : null }
</div>
);
}
export default TeacherClass;
This works just fine, however the problem is that when I change tabs, it changes the tab for EVERY card, not just the one that I selected. I thought I could fix this by having my tabValue useState as an array or something, but instead decided to create a new component so that each card could have its own useState:
teacherAssignmentCard.jsx
function TeacherAssignmentCard(props) {
const classData = props.classData;
const assignment = props.assignmentData;
const [tabValue, setTabValue] = React.useState(classData.students[0]);
const AssignmentCard = () => {Same code as 'AssignmentCard' in 'teacherClass.jsx',
but with no argument as 'assignment' comes from props}
return (
<AssignmentCard />
);
and in teacherClass.jsx I of course changed the return:
return (
<div>
{ assignments ? assignments.map((assignment) =>
<TeacherAssignmentCard classData={classData} assignmentData={assignment} />) : null}
</div>
);
I thought this was a fairly elegant solution and it worked perfectly. Each tab click only changes the tab for one assignment. However, what was very strange was that I lost the nice smooth MUI animation where the line underneath the tab slides over to the new tab. Instead it just repositions instantly.
I have checked and rewritten my code a half dozen times and made sure it's exactly the same. Any ideas what could be causing this weird behaviour or how to fix it?? Thank you.
I am having trouble with my react quiz app. Here follows the description:
This is from App.js file:
...
const [createQuiz, setCreateQuiz] = useState(false);
...
useEffect(()=> {
const reRender = () => {
setCreateQuiz(true)
}
window.onload=function(){
document.getElementById("myBtn").addEventListener("click", reRender);
}
// return document.getElementById("myBtn").removeEventListener("click", reRender);
}, [createQuiz])
return (
<QuizContextProvider>
{
(createQuiz) ? (
<div>Form</div>
) : (
<div>
<Modal/>
<Question question={questions[questionNumber]} next={goToTheNext} />
</div>
)
}
{console.log(createQuiz)}
</QuizContextProvider>
);
}
As can be seen it is a conditional rendering: a Modal window asks a user whether they want to take the existing quiz or create their own and when the user clicks "Create your own " button, the app should re-render over again, this time the useEffect() (in App.js) sets the value of createQuiz to true. the code excerpt below is from <Modal /> component:
return (
<div className='benclosing' style={{display:displayValue}}>
<div className='modal'>
<h1>Welcome!</h1>
<p>Do you want to take an existing quiz or create your own?</p>
<button onClick={hideWindow} >Existing quiz</button>
<button id='myBtn'>Create your own</button>
</div>
</div>
)
}
Everthing works fine as expected, except for 1: whenever reload icon is clicked, my page re-renders over-again and the user is again asked if they want to take the existing quiz. I want that refreshing affect nothing. I am stuck with this problem. How can I achieve the desired result?
I also tried this:
const reRender = () => {
setCreateQuiz(true)
}
useEffect(()=> {
reRender()
//return setCreateQuiz(false)
}, [createQuiz])
It didn't work as expected. I described what it caused in my 2nd comment to Red Baron, please have a look.
The proper way to achieve what you want is to create an event handler inside your App component that will set createQuiz to true when the Create your own button gets clicked inside the Modal component.
function App() {
const [createQuiz, setCreateQuiz] = React.useState(false);
const handleShowQuizForm = () => {
setCreateQuiz(true);
};
return (
<div>
{createQuiz ? (
<div>Form</div>
) : (
<>
<Modal showQuizForm={handleShowQuizForm} />
</>
)}
</div>
);
}
function Modal(props) {
return (
<div>
<button type="button" onClick={props.showQuizForm}>
Create your own
</button>
</div>
);
}
Here's an example:
CodeSandbox
There's no need for the useEffect hook here and the window.onload event implies to me that you'd want to set createQuiz to true then "refresh" your page and expect createQuiz to now be true - it won't work like that.
Additionally, the way you're using the useEffect hook could be problematic - you should try to stay away from updating a piece of state inside of a useEffect hook that's also part of the dependency array:
React.useEffect(() => {
const reRender = () => {
setCreateQuiz(true);
}
// although not an issue here, but if this hook was
// rewritten and looked like the following, it would
// case an infinite re-render and eventually crash your app
setCreateQuiz(!createQuiz);
}, [createQuiz]);
I am new to web development and is trying to learn react and redux.
I am following this tutorial https://www.youtube.com/playlist?list=PLC3y8-rFHvwheJHvseC3I0HuYI2f46oAK
As I'm trying to extend what I learned,
I'm trying to list all the users (clickable),
once clicked will display (expand/collapse) all the post of the selected user (clickable again),
once post is clicked, will display (expand/collapse) all the comment on that selected post
APIs to use:
users: https://jsonplaceholder.typicode.com/users
posts: https://jsonplaceholder.typicode.com/posts?userId={user.id}
comments: https://jsonplaceholder.typicode.com/comments?postId={post.id}
Right now, I was able to list all the users and able to do the expand/collapse,
and also able to display the post of the user however, I am experiencing below problem:
If I click on user[0] it will expand and display the post of user[0] (OK).
then if I click user[1], will expand and display the post of user[1] (OK)
however upon click of user[1] it also changes the post listed on user[0] to list the post of user[1] as well (NOT OK)
here is my UserContainer.js
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { fetchUsers, updateUser } from "../redux";
import PostsContainer from "./PostsContainer";
function UsersContainer({ userData, fetchUsers, updateUser }) {
useEffect(() => {
fetchUsers();
}, []);
const handleClick = event => {
//console.log(userData.users)
const indx = userData.users.findIndex(obj => obj.id == event.target.value);
//console.log(indx)
userData.users[indx].collapse = !userData.users[indx].collapse;
//console.log(userData.users[indx].collapse + " " + indx);
updateUser(userData);
};
return userData.loading ? (
<h2>loading</h2>
) : userData.error ? (
<h2>{userData.error}</h2>
) : (
<div>
<h2>User List</h2>
<div className="list-group">
{userData.users.map(user => (
<div>
<button
type="button"
className="list-group-item list-group-item-action"
key={user.id}
onClick={handleClick}
value={user.id}
>
{user.name}
</button>
{/* for update to change SampleContainer component to POST component */}
{!user.collapse && (
//<SampleContainer id={user.id} name={user.name} />
<PostsContainer id={user.id} />
)}
</div>
))}
</div>
</div>
);
}
const mapStateToProps = state => {
return {
userData: state.user
};
};
const mapDispatchToProps = dispatch => {
return {
fetchUsers: () => dispatch(fetchUsers()),
updateUser: users => dispatch(updateUser(users))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UsersContainer);
I don't know why stackoverflow finds my post have code that doesn't properly formatted therefore I wasn't able to put the PostContainer component.
here is the codesandbox link for complete reference of the code:
https://codesandbox.io/s/react-redux-testing-mi6ms
You are storing the posts of that particular selected user at an instance, so change the state posts of postsReducer to object to store the posts of multiple users
Please find the code sandbox here
EDIT
If you want to prevent the loading indicator for other users then, you need to store the array of ids that are currently being loaded, and remove id once the data is loaded, for that you need to update the way you are dealing with loading state of reducer from boolean to array
Please find the updated sandbox here