Post Component re-renders even other instance of Post Component is called - reactjs

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

Related

React component "html" changes are not displayed

I'm working on someone else example code and I made some changes for the sake of testing.
One thing I stumbled upon was that I made some minimal changes on the html part of a component and the changes are not beeing displayed.
import React from 'react'
const Choices = ({ handleNewActivity, handleAddActivity, name }) => {
return (
<div>
<button id="primary-btn" onClick={() => handleNewActivity()}>Noooooo thanks...</button>
<button id="success-btn" onClick={() => handleAddActivity(name)}>Sounds fun!</button>
</div>
)
}
export default Choices
Like in this component I changed the phrase "No thanks..." to "Noooooo thanks..."
and the app continues to display as "No thanks..."
image of component rendered
Maybe you guys can help me figure it out why this is happening.
If anyone would like to take a look at the code you can find it on:
https://github.com/mondadori89/deploying-fullstack-with-heroku-sample
const handleAddActivity = newActivity => {
activityService
.addActivity({
activity: newActivity,
})
.then(() => {
setActivities([...activities, {activity: newActivity}])
})
activityService
.getNewActivity()
.then(data => {
setNewActivity(data.activity)
})
}
This is your handleAddActivity.
it is required a parameter newActivity. but you are not using on Noooooo thanks... button
<button id="primary-btn" onClick={() => handleNewActivity()}>Noooooo thanks...</button>
Please add a parameter or make another activity to handle the Nooooo thanks ... event.

Like Button with Local Storage in ReactJS

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

Paypal button cannot read new React state. How to work with dynamic values and paypal in React?

I'm currently working on the checkout page of an application where a user can purchase up to three items at one of three prices chosen by the user (this is mostly being done as an experiment). When the user chooses a price by clicking a button this triggers a setState and a new price is stored to the state. When doing console.log I see the new state has been set, but upon checkout it appears the state resets to its initial value. I can't tell why and have no idea where to begin on this one. I imagine on initial render paypal is keeping the initial state it was passed and needs to be rerendered when the new state is set, but not sure how to go about this or even if this is the problem. Any help or guidance is appreciated.
I'm using the #paypal/react-paypal-js library for this paypal implementation, but am welcome to alternative suggestions.
Here is the code I'm using but cut down relevant sections:
import React, {useState, useRef, useEffect} from 'react';
import { PayPalButtons, usePayPalScriptReducer } from "#paypal/react-paypal-js";
import PriceButton from './PriceButton.jsx';
import NumberItemButton from './NumberItemButton';
import {priceOptions, amountItems} from './PriceOptions';
const PaymentPage = () => {
const [isLoading, setIsLoading] = useState(false);
const [payAmount, setPayAmount] = useState('5.00');
const [itemAmount, setItemAmount] = useState('1');
const payPalOptions = { //Note: This is used in the higher level component PayPalScriptProvider
"client-id": `${process.env.REACT_APP_PAYPAL_CLIENT_ID}`,
currency: "USD",
intent: "capture",
};
const createOrder = (data, actions) => { //This will show the initial state when triggered
return actions.order.create({
purchase_units : [
{
amount: {
value: payAmount //This stays at the initial State of '5.00' despite the newState being set
}
}
]
})
};
const onApprove = (data, actions) => {
return actions.order.capture().then(function(orderData) {
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
let transaction = orderData.purchase_units[0].payments.captures[0];
alert('Transaction '+ transaction.status + ': ' + transaction.id + '\n\nSee console for all available details');
}
)};
const onError = (error) => {
console.log(error)
}
console.log(payAmount) //Note: This will show the new State
return (
<div>
<h1>Purchase</h1>
<label> Choose number of items
<div>
{amountItems.map((item, index) => {
return <NumberItemButton key={index} setItemAmount={setItemAmount} amount={item.amount} />
})}
</div>
</label>
<label> Pick a price
<div>
{priceOptions.map((item, index) => {
return <PriceButton key={index} itemAmount={itemAmount} setPayAmount={setPayAmount} price={item.price} />
})}
</div>
</label>
<PayPalButtons
createOrder={(data, actions) => createOrder(data, actions)}
onApprove={(data, actions) => onApprove(data, actions)}
onError={onError}
/>
</div>
);
}
export default PaymentPage;
I'll also add the price button component incase the issue is there
const PriceButton = ({itemAmount, setPayAmount, price}) => { //itemAmount is the amount customer buys, price is the value passed through on the mapping function
const multPrice = (itemAmount * price).toFixed(2);
const withTaxPrice = (parseInt(multPrice) + .5).toFixed(2).toString();
return (
<button onClick={() => setPayAmount(withTaxPrice)}>${multPrice}</button>
)
}
export default PriceButton;
Appreciate any help!
I came back to this with a fresh pair of eyes and found the solution (though I'm not sure if it's the best one).
The issue is when the Paypal button renders it pulls in the initial state that is passed through, but it needs to be rerendered when a new state is passed.
My solution to this was to pass a forceReRender={[payAmount]} within the PaypalButtons component. This rerenders the Paypal button upon update to the price state and allows me to pass an updated value.
Hope this helps others!
I found a better solution. Just use useRef and access the ref.current value!

ReactJS - Alternative to passing useState between files

Background
I have a file that presents my main page Dash.js
This presents some data from an API on a "card", from two other files List.js and ListLoading.js
I have an additional "card" which I can trigger open with default useState value of 1, and the onClick works to close, as you will see in the dash.js file.
Current Code
//Dash.js
function Dash(props) {
//control additional card
const [openCard, setopenCard] = React.useState(0);
const closeCard = () => {
setopenCard(0);
}
//set API repo
const apiUrl = (`http://example.com/api/`);
axios.get(apiUrl, {
withCredentials: true }).then((res) =>{
setAppState({ loading: false, repos: res.data.emails });
});
return (
{(openCard>0 &&
<Card>
<Cardheader onClick={() => closeCard()}>
Click here to close
</Cardheader>
<Cardbody>
Some data here
</Cardbody>
</Card>
)
|| null
}
<Card>
<ListLoading isLoading={appState.loading} repost={appState.repos} />
<Card>
);
}
//List.js
const List = (props) => {
const { repos } = props;
if (!repos || repos.length === 0) return <p>No data available</p>;
for (var key in repos) {
return (
{repos.map((repo) => {
return (
<p className='repo-text max-width' >ID:{repo.id}{" "}Value:{repo.value} </p>
);}
)}
);}
};
export default List;
//ListLoading.js
function WithListLoading(Component) {
return function WihLoadingComponent({ isLoading, ...props }) {
if (!isLoading) return <Component {...props} />;
return (
<p style={{ textAlign: 'center', fontSize: '30px' }}>
Fetching data may take some time, please wait
</p>
);
};
}
export default WithListLoading;
Desired Outcome
I want to set the the value for openCard.useState() to the repos.id.
e.g. onClick={() => openCard({repos.id})}
The complication of this is that I need to retrieve that code from List.js and pass it to the useState for the openCard, which is in Dash.js.
I am still fairly new to react so this is proving a little tricky to work out how to do.
What I've tried
I have looked into useContext, but either it has confused me or I am right to think this would not work for what I am trying to do.
I have looked into redux, however this seems like that may be overkill for this solution.
I have tried a series of passing the different constants via import/export however I now understand that useState is not designed to work this way and should really be used within the function/class where it is contained.
So any thoughts to remedy would be greatly appreciated!
So, just to restate what I understood your issue to be:
You have a parent component that renders a list of objects and can render a detail card of one of the object.
You want to have a single item in your list of objects be able to tell the parent "please open card 123".
Now to look at the options you considered:
Redux I agree Redux is overkill for this. Redux is usually only necessary if you need complex, possibly async reading and writing to a single shared datasource across the whole scope of your application. For a little UI interaction like this, it is definitely not worth setting up Redux.
React Context Context relies on a Provider component, which you wrap some chunk of your app in. Any component below that Provider can then use useContext to reach into the memory of that Provider. You can store anything in there that you could store in a component, from a single state variable up to a more complex useReducer setup. So, in a way, this basically does what you were hoping to do with static variables passing the state around. This is the right solution if you were going to be using this state value across a wide variety of components.
Props are probably the right way to go here - since you have a parent who wants to get messages from a child directly you can give the child a callback function. This is the same as the onClick function you can give a button, except here you can pass your list a onShowCard function.
In your Dash:
<ListLoading
isLoading={appState.loading} repost={appState.repos}
onShowCard={(cardId) => setopenCard(cardId)} />
At the end of the List:
{repos.map((repo) => {
return (
<button key={repo.id} className='repo-text max-width' onClick={() => { props.onShowCard(repo.id) }>
ID:{repo.id}{" "}Value:{repo.value}
</button>
);}
)}
You can pass on the function to update state to ListLoading component which will be forwarded to List component assuming it is wrapped by thee HOC WithListLoading.
Inside List you can then attach and onClick on the element to pass on the id of the clicked element
function Dash(props) {
//control additional card
const [openCard, setopenCard] = React.useState(0);
const closeCard = () => {
setopenCard(0);
}
//set API repo
const apiUrl = (`http://example.com/api/`);
axios.get(apiUrl, {
withCredentials: true
}).then((res) =>{
setAppState({ loading: false, repos: res.data.emails });
});
const handleOpen = id => {
setopenCard(id);
}
return (
{(openCard>0 &&
<Card>
<Cardheader onClick={() => closeCard()}>
Click here to close
</Cardheader>
<Cardbody>
Some data here
</Cardbody>
</Card>
)
|| null
}
<Card>
<ListLoading isLoading={appState.loading} repost={appState.repos} handleOpen={handleOpen} />
<Card>
);
}
const List = (props) => {
const { repos, handleOpen } = props;
if (!repos || repos.length === 0) return <p>No data available</p>;
for (var key in repos) {
return (
{repos.map((repo) => {
return (
<p className='repo-text max-width' onClick={() => props.handleOpen(repo.id)} >ID:{repo.id}{" "}Value:{repo.value} </p>
);}
)}
);}
};
export default List;

Load Components Dynamically React Js with load more button

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.

Resources