Cannot display items by pasing value from different component - reactjs

So i wanna display my cart item in the cart list, as i have set the initial state of cart to storeProducts, 8 of my products should be rendered as i'm using the map function. I haven't made the ui of my cartItem.js yet, but instead, i should have 8 lines of text as "this is a cart item" from my cartItem.js. Please help me to find out what's wrong with my codes! Thank you so much!
context.js:
class ProductProvider extends React.Component {
state = {
products: storeProducts,
detailProduct: detailProduct,
cart: storeProducts,
modalOpen: false,
modalProduct: detailProduct
};
getItem = (id) => {
const product = this.state.products.find((item) => item.id === id);
return product;
};
addToCart = (id) => {
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return (
{ products: tempProducts, cart: [...this.state.cart, product] },
() => console.log(this.state)
);
});
};
openModal = (id) => {
const product = this.getItem(id);
this.setState(() => {
return { modalProduct: product, openModal: true };
});
};
closeModal = (id) => {
this.setState(() => {
return { modalOpen: false };
});
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
addToCart: this.addToCart,
openModal: this.openModal,
closeModal: this.closeModal
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
CartItem.js:
import React from "react";
function CartItem(item) {
return <div>this is a cart item</div>;
}
export default CartItem;
CartList.js:
import React from "react";
import CartItem from "./CartItem"
export default function CartList (props) {
const {cart} = props
return (
<div>
{cart.map((item) => (
<CartItem key={item.id} item={item} />
))}
</div>
)
}
Sandbox link:https://codesandbox.io/s/cart-code-addict-buz0u?file=/src/App.js

In your Sandbox link, EmptyCart component can not found, so the app is error.
And your should add ProductConsumer too.
'PropTypes' is defined but never used. (no-unused-vars)
eslint
'useParams' is defined but never used. (no-unused-vars)
eslint
'Modal' is defined but never used. (no-unused-vars)
eslint
Missing radix parameter. (radix)
eslint
'ProductConsumer' is not defined. (react/jsx-no-undef)
eslint

Related

react.js event handler is not returning updated data

I am new to react.js. I am following the video on youtube for learning react.js. I am working on simple event handling and stuck in some issue. I want to check/uncheck the checkbox when user performs onclick function on the checkbox. but somehow the returning array is not updated and checkbox is not actionable. I am pasting my code below:
App.js
import React from 'react'
import Header from './components/Header'
import Todolist from './components/Todolist'
import todosData from './data/todosData'
class App extends React.Component {
constructor(){
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id){
this.setState(prevState => {
console.log(prevState.todos)
const updatedTodos = prevState.todos.map(todo => {
if(todo.id === id){
todo.completed = !todo.completed
}
return todo
})
console.log(updatedTodos)
return{
todos : updatedTodos
}
})
}
render(){
const todoItems = this.state.todos.map(item => <Todolist key={item.id} item={item} handleChange={this.handleChange} />)
return (
<div>
<Header />
{todoItems}
</div>
)
}
}
export default App;
TodoList.js
import React from 'react'
function Todolist(props){
return(
<div className='todo-item'>
<input type='checkbox'
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<span>{props.item.text}</span>
</div>
)
}
export default Todolist
You are trying to mutate the original item/object of the array at this line!
todo.completed = !todo.completed
You can try to create a new object using Object.assign or using spread. Both are fine but the spread way is preferable.
Using Object.assign
handleChange(id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
// Object.assign creates a new object!
const changedTodo = Object.assign({}, todo, {
completed: todo.id === id ? !todo.completed : todo.completed
});
return changedTodo;
});
return {
todos: updatedTodos
};
});
}
Using spread ...
handleChange(id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
const changedTodo = {
...todo,
completed: todo.id === id ? !todo.completed : todo.completed
};
return changedTodo;
});
return {
todos: updatedTodos
};
});
}
As stated here, setState runs twice in strict mode. The state updater function gets called twice with the same input. Which should be fine and produce the same output. But the problem here is you have reused the todo object instead of creating a new one. Which in turn causes the completed flag to be flipped twice.
You can change your updater function to create a new todo object every time:
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed,
};
}
return todo;
});
return {
todos: updatedTodos,
};
});

How to store inCart value in localStorage?

In my React ECommerce project, I have created Add to cart icon, when clicked disables, the icon is replaced with 'In Cart' text showing that the product is available in cart,but, the problem is when the browser is refreshed the 'In Cart' text disappears and cart icon is back. How to store it in localStorage so that the value remains even when refreshed. Following is the code for reference.
ProductList.js
<ProductConsumer>
{value => {
return value.products.map((product, key) => {
return <Product key={product.id} product={product} />;
});
}}
</ProductConsumer>
Product.js
export default function Product(props) {
// Taken from ProductList.js File
const {id, title, img, price, inCart} = props.product;
<ProductConsumer>
{(value) => (
<button className="cart-btn" disabled={inCart?true:false}
onClick={() => {value.addToCart(id)}}>
{ inCart ? (
<p className="text-capitalize mb-0" disabled>
{" "}
In Cart</p>
) : (
<i className="fas fa-shopping-cart"/>
)}
</button>
)}
</ProductConsumer>
}
context.js (addToCart(id) is defined)
const ProductContext = React.createContext();
class ProductProvider extends Component {
addToCart = (id) => {
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
// How to store this value 'product.inCart' in...
// ...localStorage and make it true until the product is
// removed
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return {
products: tempProducts,
cart: [...this.state.cart, product]
};
},
() => {
this.addTotal();
localStorage.setItem('myCart', JSON.stringify(this.state.cart));
});
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
As seen above when the product is in cart, cart icon gets disabled, I want to make inCart be true (even when the browser is refreshed) until and unless the product is removed from cart. Watch out for sandbox link: https://codesandbox.io/s/mobile-store-tdgwm
Above File ProductList.js is added and ProductConsumer is defined from context.js
In your setProducts function in context.js
setProducts = () => {
let tempProducts = [];
let activeProducts = JSON.parse(localStorage.getItem("myCart"));
storeProducts.forEach(item => {
let singleItem = { ...item };
if(activeProducts){
activeProducts.forEach(i => {
if (singleItem.id === i.id) {
singleItem = i;
}
});
}
tempProducts = [...tempProducts, singleItem];
});
this.setState(() => {
return { products: tempProducts };
});
};
Working codeSandbox - https://codesandbox.io/s/mobile-store-325x9

Firebase/React/Redux Component has weird updating behavior, state should be ok

I am having a chat web app which is connected to firebase.
When I refresh the page the lastMessage is loaded (as the gif shows), however, for some reason, if the component is otherwise mounted the lastMessage sometimes flickers and disappears afterwards like it is overridden. When I hover over it, and hence update the component, the lastMessage is there.
This is a weird behavior and I spent now days trying different things.
I would be very grateful if someone could take a look as I am really stuck here.
The db setup is that on firestore the chat collection has a sub-collection messages.
App.js
// render property doesn't re-mount the MainContainer on navigation
const MainRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => (
<MainContainer>
<Component {...props} />
</MainContainer>
)}
/>
);
render() {
return (
...
<MainRoute
path="/chats/one_to_one"
exact
component={OneToOneChatContainer}
/>
// on refresh the firebase user info is retrieved again
class MainContainer extends Component {
componentDidMount() {
const { user, getUserInfo, firebaseAuthRefresh } = this.props;
const { isAuthenticated } = user;
if (isAuthenticated) {
getUserInfo(user.id);
firebaseAuthRefresh();
} else {
history.push("/sign_in");
}
}
render() {
return (
<div>
<Navigation {...this.props} />
<Main {...this.props} />
</div>
);
}
}
Action
// if I set a timeout around fetchResidentsForChat this delay will make the lastMessage appear...so I must have screwed up the state / updating somewhere.
const firebaseAuthRefresh = () => dispatch => {
firebaseApp.auth().onAuthStateChanged(user => {
if (user) {
localStorage.setItem("firebaseUid", user.uid);
dispatch(setFirebaseAuthUser({uid: user.uid, email: user.email}))
dispatch(fetchAllFirebaseData(user.projectId));
}
});
};
export const fetchAllFirebaseData = projectId => dispatch => {
const userId = localStorage.getItem("firebaseId");
if (userId) {
dispatch(fetchOneToOneChat(userId));
}
if (projectId) {
// setTimeout(() => {
dispatch(fetchResidentsForChat(projectId));
// }, 100);
...
export const fetchOneToOneChat = userId => dispatch => {
dispatch(requestOneToOneChat());
database
.collection("chat")
.where("userId", "==", userId)
.orderBy("updated_at", "desc")
.onSnapshot(querySnapshot => {
let oneToOne = [];
querySnapshot.forEach(doc => {
let messages = [];
doc.ref
.collection("messages")
.orderBy("created_at")
.onSnapshot(snapshot => {
snapshot.forEach(message => {
messages.push({ id: message.id, ...message.data() });
});
});
oneToOne.push(Object.assign({}, doc.data(), { messages: messages }));
});
dispatch(fetchOneToOneSuccess(oneToOne));
});
};
Reducer
const initialState = {
residents: [],
oneToOne: []
};
function firebaseChat(state = initialState, action) {
switch (action.type) {
case FETCH_RESIDENT_SUCCESS:
return {
...state,
residents: action.payload,
isLoading: false
};
case FETCH_ONE_TO_ONE_CHAT_SUCCESS:
return {
...state,
oneToOne: action.payload,
isLoading: false
};
...
Main.js
// ...
render() {
return (...
<div>{React.cloneElement(children, this.props)}</div>
)
}
OneToOne Chat Container
// without firebaseAuthRefresh I don't get any chat displayed. Actually I thought having it inside MainContainer would be sufficient and subscribe here only to the chat data with fetchOneToOneChat.
// Maybe someone has a better idea or point me in another direction.
class OneToOneChatContainer extends Component {
componentDidMount() {
const { firebaseAuthRefresh, firebaseData, fetchOneToOneChat } = this.props;
const { user } = firebaseData;
firebaseAuthRefresh();
fetchOneToOneChat(user.id || localStorage.getItem("firebaseId"));
}
render() {
return (
<OneToOneChat {...this.props} />
);
}
}
export default class OneToOneChat extends Component {
render() {
<MessageNavigation
firebaseChat={firebaseChat}
firebaseData={firebaseData}
residents={firebaseChat.residents}
onClick={this.selectUser}
selectedUserId={selectedUser && selectedUser.residentId}
/>
}
}
export default class MessageNavigation extends Component {
render() {
const {
onClick,
selectedUserId,
firebaseChat,
firebaseData
} = this.props;
<RenderResidentsChatNavigation
searchChat={this.searchChat}
residents={residents}
onClick={onClick}
firebaseData={firebaseData}
firebaseChat={firebaseChat}
selectedUserId={selectedUserId}
/>
}
}
const RenderResidentsChatNavigation = ({
residents,
searchChat,
selectedUserId,
onClick,
firebaseData,
firebaseChat
}) => (
<div>
{firebaseChat.oneToOne.map(chat => {
const user = residents.find(
resident => chat.residentId === resident.residentId
);
const selected = selectedUserId == chat.residentId;
if (!!user) {
return (
<MessageNavigationItem
id={chat.residentId}
key={chat.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
{residents.map(user => {
const selected = selectedUserId == user.residentId;
const chat = firebaseChat.oneToOne.find(
chat => chat.residentId === user.residentId
);
if (_isEmpty(chat)) {
return (
<MessageNavigationItem
id={user.residentId}
key={user.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
</div>
}
}
And lastly the item where the lastMessage is actually displayed
export default class MessageNavigationItem extends Component {
render() {
const { hovered } = this.state;
const { user, selected, chat, isGroupChat, group, id } = this.props;
const { messages } = chat;
const item = isGroupChat ? group : user;
const lastMessage = _last(messages);
return (
<div>
{`${user.firstName} (${user.unit})`}
{lastMessage && lastMessage.content}
</div>
)
}
In the end it was an async setup issue.
In the action 'messages' are a sub-collection of the collection 'chats'.
To retrieve them it is an async operation.
When I returned a Promise for the messages of each chat and awaited for it before I run the success dispatch function, the messages are shown as expected.

React pagination on scroll

i have to implement chat system in react i m doing it first time and i m stuck.i have to pass page no to backend api to get new data every time.and i have to pass page no to api on scroll. i m using
[1]: https://www.npmjs.com/package/react-infinite-scroller
i m getting total data count and 9 data array per page from api.scroll upto which total count is available and when user scroll to top is should load more.i have tried lots of module but failed to implement pagination on scroll.using react-infinite-scroll module i m getting page no but its not working as i want.Please suggest me right way of doing it
here is my component code
const mapStateToProps = state => ({
users: state.sidebarUser.users,
total:state.sidebarUser.total,
routing: state.routing,
converSationId: state.getConversationId.data
});
const mapDispatchToProps = dispatch => ({
loadUserList: (page={}) => (dispatch(getSideBarUser(page))),
getConversationId: (userId) =>
dispatch(getConversationId(userId)),
loadUserContent: id => dispatch(UserChatList(id))
});
class SidebarContainer extends Component {
constructor(props) {
super(props);
this.state={
isLoading:false,
sidebar:[],
page:0,
hasMore: true,
}
this.getPosts=this.getPosts.bind(this);
}
componentDidMount() {
const {
location: { search }
} = this.props.routing;
let userId = new URLSearchParams(search).get("id");
this.props.loadUserList({page:1});
this.setState({page:this.state.page+1});
this.props.getConversationId(userId);
}
getPosts(page) {
console.log("pgae---->",page)
console.log("this.props--->",this.props.users)
this.props.loadUserList({page:page});
}
render() {
const { users } = this.props;
const {hasMore,sidebar} =this.state;
return (
<div className="chatting-user-list-section" ref={(ref) => this.scrollParentRef = ref} >
<InfiniteScroll
initialLoad={false}
pageStart={0}
loadMore={this.getPosts.bind(this)}
hasMore={hasMore}
getScrollParent={() => this.scrollParentRef}
threshold={520}
loader={<div className="loader">Loading ...</div>}>
<SidebarComponent users={users} listClicked={this.listClicked} />
</InfiniteScroll>
</div>)
}
}
export const Sidebar = connect(
mapStateToProps,
mapDispatchToProps
)(SidebarContainer)
and here is my Reducer
import { SIDEBAR_USERS_SUCCESS, SIDEBAR_USERS_FAILURE } from './ActionTypes';
const initialState = {
users: [],
total: 0
}
export const sidebarUser = (state = initialState, { type, payload }) => {
switch (type) {
case SIDEBAR_USERS_SUCCESS: {
return { ...state, ...payload };
}
case SIDEBAR_USERS_FAILURE: {
return { ...state, error: payload }
}
default:
return state;
}
};

React, Redux Deleting an item on click deletes all items

I need fresh eyes on this. As I am slowly learning React and Redux i have run into a roadblock again.
/actions/items.js
export const DELETE_ITEM = "DELETE_ITEM"
export function deleteItem(id) {
return {
type: DELETE_ITEM,
id
}
}
/components/Item.jsx
export default class Item extends React.Component {
renderDelete = () => {
return <button onClick={this.props.onDelete}>x</button>
};
renderItem = () => {
const onDelete = this.props.onDelete
return (
<div onClick={this.edit}>
<span>{this.props.text}</span> {onDelete ? this.renderDelete() : null}
</div>
)
}
/components/Items.jsx
export default class Items extends React.Component {
handleOnDelete = (id) => {
this.props.dispatch(actions.deleteItem(id))
}
render() {
const {items, onEdit, onDelete } = this.props
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
id={item.id}
text={item.text}
onEdit={this.handleOnEdit}
onDelete={this.handleOnDelete.bind(null, item.id)}
/>
</li>
)}</ul>
);
}
}
export default connect(
state => ({
items: state.items
})
)(Items)
/reducers/items.js
case types.DELETE_ITEM:
const filteredItems = state.filter((item) => {
item.id !== action.id
});
return filteredItems
I'm not sure why clicking on x button to delete an item deletes all of them. Thanks in advance for the help
You do not return value in filter in your reducers.
Your should add return:
const filteredItems = state.filter((item) => {
return item.id !== action.id;
});
Or use short version, without brackets:
const filteredItems = state.filter((item) => item.id !== action.id);

Resources