Cart value resets on browser refresh - reactjs

In ECommerce React project, I've created cart when clicked, changes to 'In Cart' and is then disabled which shows the product is in cart and can't be clicked back, but, when browser is refreshed Cart value resets back.
Following is the code reference
Product.js
export default class Product extends Component {
render() {
const {id, title, img, price, inCart} = this.props.product;
const dataValue = JSON.parse(localStorage.getItem('myCart'))
return (
<ProductWrapper className="col-9 mx-auto col-sm-6 col-lg-3 my-3 " >
<div className="card" >
<ProductConsumer>
{(value) => (
<div className="img-container p-3" >
<img style={imageSize} src={img} alt="product"
className="card-img-top center img-fluid img-responsive"/>
<button className="cart-btn" disabled={inCart?true:false}
onClick={() => {value.addToCart(id)}}>
{console.log('DATA VALUE', dataValue)}
{ inCart ? (
<p className="text-capitalize mb-0" disabled>
{" "}
In Cart</p>
) : (
<i className="fas fa-shopping-cart"/>
)}
</button>
</div>)}
</ProductConsumer>
</div>
</ProductWrapper>
);
}
}
ProductList.js (Mapping list of products)
export default class ProductList extends Component {
render() {
return (
<React.Fragment>
<ProductConsumer>
{value => {
return value.products.map((product, key) => {
return <Product key={product.id} product={product} />;
});
}}
</ProductConsumer>
</React.Fragment>
);
}
}
App.js
class App extends Component {
render() {
return (
<React.Fragment>
<Route render={({location}) => (
<Switch location={location}>
<Route exact path="/" component={ProductList}/>
</Switch>
)} />
</React.Fragment>
);
}
}
export default App;
I've tried with localStorage but no effect. What can be done to make the cart value store in localStorage, so that when refreshed 'In Cart' remains. Any appropriate solution?
Following is the codesandbox link: https://codesandbox.io/s/tdgwm

<button
className="cart-btn"
disabled={inCart ? true : false}
onClick={() => {
value.addToCart(id);
localStorage.setItem("added", "In Cart");
console.log(localStorage.getItem("added"));
}}
>
{localStorage === null ? (
<i className="fas fa-shopping-cart" />
) : (
<p className="text-capitalize mb-0" disabled>
{" "}
{localStorage.getItem("added")}
</p>
)}
</button>
I took a quick look at this. The above code will get the localStorage item set and render the in cart value. However, I will leave it to you to apply it to the individual product and update the conditionality for the icon/in cart piece. I'll be on for a little bit longer if you have any questions. But basically you need to see if localstorage is set prior to rendering.

It is because you don't check the local storage inCart value in the Product.js
I have added another condition like below and check it within inCart in the conditions
...
...
const localInCart = dataValue
&& dataValue.find(i => i.id === id)
&& dataValue.find(i => i.id === id).inCart;
...
...
...
(inCart || localInCart) ? ...)
you can see the result on codesandbox; https://codesandbox.io/s/mobile-store-ufxgp?fontsize=14&hidenavigation=1&theme=dark

Related

I can't get array.filter to work with useState

I am trying to delete an item from a shopping cart and I am using the filter hook to accomplish this. I have looked at the documentation for this and at the answers here on stack overflow. unfortunately no luck.
this is my code for the entire component. the function is of course "deleteItemFromBasket" and it is being called at the onclick on the delete button:
function CheckoutProduct({id, title, price, description, rating, category, image }) {
const [basket, addToBasket] = useAppContext();
const deleteItemFromBasket = (id) => {
addToBasket(basket.filter((task) => task.id !== id));
};
return (
<div>
{basket.map((element) => {
if (element === id) {
return (
<div className='grid grid-cols-5 border-b pb-4'>
{/* far left */}
<Image src={image} height={200} width={200} objectFit='contain' />
{/* middle */}
<div className="col-span-3 mx-5">
<p>{title}</p>
<p className='text-xs my-2 line-clamp-3'>{description}</p>
<button onClick={deleteItemFromBasket} className='button'>delete</button>
<h1>items ID in basket: {basket}</h1>
<h1>length of array: {basket.length}</h1>
</div>
{/* right */}
<div>
<p>${price}</p>
</div>
</div>
)
}
})}
</div>
)
}
here is the code of the context provider
import React, { createContext, useContext, useState } from 'react';
const AppContext = createContext();
export function AppWrapper({ children }) {
var [basket, addToBasket]= useState([]);
return (
<AppContext.Provider value={[basket, addToBasket]}>
{children}
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}
looks like basket is an array of ids, not an array of objects. Assuming that is the case, then you need to change your delete function to
const deleteItemFromBasket = (id) => {
addToBasket(basket.filter((element) => element !== id));
};
This is assuming that addToBasket is actually just setting the basket, not just additive.
I eventually got it to work and it took a combination of the two answers so thank you everyone for the help!
notice below that the function "deleteItemFromBasket" is the same as joe's post but it to get it to work I needed to add the function in the onClick like zehan's answer. I don't know why this works so if anyone has an explanation as to why I'll save the answer spot for it! thanks friends
import React from 'react';
import Image from 'next/image'
import { useAppContext } from '../state'
function CheckoutProduct({id, title, price, description, rating, category, image }) {
const [basket, addToBasket] = useAppContext();
const deleteItemFromBasket = (item) => {
addToBasket((current) => current.filter((element) => element !== item));
};
return (
<div>
{basket.map((element) => {
if (element === id) {
return (
<div className='grid grid-cols-5 border-b pb-4'>
{/* far left */}
<Image src={image} height={200} width={200} objectFit='contain' />
{/* middle */}
<div className="col-span-3 mx-5">
<p>{title}</p>
<p className='text-xs my-2 line-clamp-3'>{description}</p>
{/* <button onClick={deleteItemFromBasket(element)} >delete item</button> */}
<button onClick={ ()=> deleteItemFromBasket(id)} className='button'>delete</button>
<h1>items ID in basket: {basket}</h1>
<h1>length of array: {basket.length}</h1>
</div>
{/* right */}
<div>
<p>${price}</p>
</div>
</div>
)
}
})}
</div>
)
}

React.js close all the previous containers at the same time

I got the container with children coming from props.[Container][1]
[1]: https://i.stack.imgur.com/3Y7Qm.png . When i click the arrow button it shows the content of the container. [Content][1]
[1]: https://i.stack.imgur.com/A8eZH.png . When i open the content container i want other containers to close . For now i can only close them with clicking the arrow button again.[Open Content][1]
[1]: https://i.stack.imgur.com/REh57.png .Here is my code `
import { useState } from "react";
export default function Question(props) {
const [clicked, setClicked] = useState(false);
function clickedElement() {
return setClicked(!clicked);
}
return (
<div className="question-cont">
<div className="question-cont-inner">
<h3>{props.head}</h3>
<button onClick={() => clickedElement()}>
{clicked ? (
<img src={props.img2} />
) : (
<img src={props.img} />
)}{" "}
</button>
</div>
{clicked ? <p>{props.description}</p> : ""}
</div>
);
}
Here is the my parent component
import Question from "../components/Question";
import questions from "../components/Questions";
export default function Sorular() {
const questionList = questions.map((question) => {
return (
<Question
key={question.id}
id={question.id}
head={question.head}
description={question.description}
img={question.img}
img2={question.img2}
/>
);
});
return (
<div className="sorular-container">
<div className="sorular-top">
<div className="sorular-top-back-img">
<a href="/">
<img
src="./images/right-arrow-colorful.png"
id="right-arrow-img"
/>
</a>
</div>
<div className="sorular-top-head">
<img src="./images/conversation.png" />
<h4>Sıkça Sorulan Sorular</h4>
</div>
</div>
<div className="sorular-bottom">{questionList}</div>
</div>
);
}
`
You need to remove your const [clicked, setClicked] = useState(false); state variable from the component itself and move it into parent:
In parent add this at the beggining and modify questionList:
const [clickedElementId, setClickedElementId] = useState(null);
const questionList = questions.map((question) => {
return (
<Question
key={question.id}
id={question.id}
head={question.head}
description={question.description}
img={question.img}
img2={question.img2}
isOpened={question.id === clickedElementId}
onClickedElement={() => setClickedElementId(
question.id === clickedElementId ? null : question.id
)}
/>
);
});
And in the Question.jsx, swap button for the following:
<button onClick={() => props.onClickedElement()}>
{props.isOpened ? (
<img src={props.img2} />
) : (
<img src={props.img} />
)}{" "}
</button>
// and later:
{props.isOpened ? <p>{props.description}</p> : ""}
This works by your app holding id of only one, currently open question, and swap it based on clicked element.
Note that questionId should be unique amongst all Question components, but you probably use .map to render them so you should use the same variable as you are passing into Question's key prop while rendering.

React useState: How to put ternary operator within a ternary operator?

I'm mapping an array of items. Some items must display videos and others, images. I made 2 functions for each display and I'm using useState to toggle them.
export default function App() {
//SIMPLE USESTATE TO TOGGLE WINDOW
const [open, setOpen] = useState(false);
//THE ARRAY (1 contains an image and the other a video)
const itemsArray = [
{
name: "Item1",
imageUrl: "some image url"
},
{
name: "Item2",
videoUrl: "some video url"
}
];
//RENDER THIS IF ITEM IS IMAGE
const ifImage = (i) => {
return (
<div onClick={() => setOpen(!open)}>
<img src={i} alt="cat" />
</div>
);
};
//RENDER THIS IF ITEM IS VIDEO
const ifVideo = (v) => {
return (
<div className="window-on-top" onClick={() => setOpen(!open)}>
<iframe>Some Video</iframe>
</div>
);
};
return (
<div className="App">
<h3>One button shows a cat photo and the other a cat video</h3>
{itemsArray.map((item) => {
return (
<div key={item.name}>
<button className="niceBtn" onClick={() => setOpen(!open)}>
{item.name}
</button>
{/* NESTING CONDITIONALS OR SOMETHING TO MAKE THIS WORK */}
{open ? {
{item.imageUrl ? ifImage(item.imageUrl): null}
||
{item.videoUrl ? ifVideo(item.videoUrl): null}
} : null}
</div>
);
})}
</div>
);
}
I'm obviously wrong... Need some help understanding how to solve this...
Here is a Sandbox with the code to watch it properly.
SANDBOX
I'd put the conditions inside the subfunctions instead, which should make it easy to understand what's going on.
const tryImage = item => (
!item.imageUrl ? null : (
<div onClick={() => setOpen(!open)}>
<img src={item.imageUrl} alt="cat" />
</div>
));
const tryVideo = item => (
!item.videoUrl ? null : (
<div onClick={() => setOpen(!open)}>
<img src={item.videoUrl} alt="cat" />
</div>
));
return (
<div className="App">
<h3>One button shows a cat photo and the other a cat video</h3>
{itemsArray.map((item) => {
return (
<div key={item.name}>
<button className="niceBtn" onClick={() => setOpen(!open)}>
{item.name}
</button>
{open && ([
tryImage(item),
tryVideo(item),
])}
</div>
);
})}
</div>
);
Not sure, but you also probably want a separate open state for each item in the array, rather than a single state for the whole app.

Remove previous content when new one is rendered through a condition (React)

I have a navbar that uses eventKeys to switch between the buttons
const CustomNav = ({ active, onSelect, ...props }) => {
return (
<Nav
{...props}
activeKey={active}
onSelect={onSelect}
style={{ marginBottom: "15px" }}>
<Nav.Item eventKey='all'>All</Nav.Item>
<Nav.Item eventKey='movies'>Movies</Nav.Item>
<Nav.Item eventKey='shows'>Shows</Nav.Item>
<Nav.Item eventKey='people'>People</Nav.Item>
</Nav>
);
};
I did this:
const Content = () => {
if (this.state.active === "all") {
return (
<div>
{trending.results &&
trending.results.map((i) => (
<React.Fragment key={i.id}>
<p>{i.title}</p>
</React.Fragment>
))}
</div>
);
} else if (this.state.active === "movies") {
return (
<div>
{trendingMovies.results &&
trendingMovies.results.map((i) => (
<React.Fragment key={i.id}>
<p>{i.title}</p>
</React.Fragment>
))}
</div>
);
}
};
Called it here:
return (
<div className='Home'>
<FlexboxGrid justify='center'>
<Panel bordered header='Trending today!'>
<CustomNav
className='customNav'
appearance='subtle'
active={active}
onSelect={this.handleType}
/>
<Content />
<Pagination
{...this.state}
style={{ marginTop: "15px" }}
maxButtons={5}
size='sm'
pages={totalPages}
activePage={this.state.activePage}
onSelect={this.handlePage}
/>
</Panel>
</FlexboxGrid>
</div>
);
}
}
To display the correct data for each tab, but when I'm on the movies tab it shows all the data from the first "all" tab + data on the "movies" tab. I wanna show each data individually corresponding to the correct tab which is controlled by "this.state.active". Tried a switch statement too and that did not work
you are using the arrow syntax
const Content = () => { ... }
and also using this.state variable in your code !!!
if you want to use this.state, then you want to use the class syntax, like
class Content extends React.Component { ... }
but don't mix the two styles.
what you are probably wanting to do is to send the active variable as a prop
try:
const Content = ({active}) => {
if (active === 'all') {
return (...)
} else if (active === 'movies') {
return (...)
}
return null
}
and where you are calling the component you send the active value in as a prop
<Content active={active} />
Note also that you are using the variables trending and trendingMovies and it is unclear where those come from, you may need to send those via props also.
Now you can also leave the if..else logic outside of your Content component like so
const Content = ({myTrending}) => {
return (
<div>
{myTrending.results &&
myTrending.results.map((i) => (
<React.Fragment key={i.id}>
<p>{i.title}</p>
</React.Fragment>
))}
</div>
);
}
and then where you call that component you have
<Content
myTrending={active === 'all' ? trending : trendingMovies}
/>
You need to pass active and other variables as props to the Content component, since it doesn't access them otherwise:
const Content = ({active, trending=[], trendingMovies=[]}) => {
if (active === "all") {
return (
<div>
{trending.results.map((i) => (
<React.Fragment key={i.id}>
<p>{i.title}</p>
</React.Fragment>
))}
</div>
);
} else if (active === "movies") {
return (
<div>
{trendingMovies.results.map((i) => (
<React.Fragment key={i.id}>
<p>{i.title}</p>
</React.Fragment>
))}
</div>
);
}
};
return (
<div className='Home'>
<FlexboxGrid justify='center'>
<Panel bordered header='Trending today!'>
<CustomNav
className='customNav'
appearance='subtle'
active={active}
onSelect={this.handleType}
/>
<Content active={this.state.active} trending={this.state.trending} trendingMovies={this.state.trendingMovies} />
<Pagination
{...this.state}
style={{ marginTop: "15px" }}
maxButtons={5}
size='sm'
pages={totalPages}
activePage={this.state.activePage}
onSelect={this.handlePage}
/>
</Panel>
</FlexboxGrid>
</div>
);
}
}

Nextjs - getInitialProps make reload and delete store

I built a site with nextjs (with server express & API) and Reactjs.
I would like to create dynamic paginations because there is far too much result for statically generated, So I added server endpoint /publiations/page/:id, I put a getInitialsProps for keep the id in query
But actually, when I click on my main page /publications where my store is not empty to go to the next page (publications/page/1), the page reloads and the store is empty. How I can keep my store when I change route?
And here my publications/page/[id].js
const PublicationsPage = ({id}) => {
return (
<>
<MainMenu/>
<Search/>
<div className="flex">
<Sidebar fallback={<Loader/>}/>
<Cards type={'publications'} idPage={id} />
</div>
</>
)
}
PublicationsPage.getInitialProps = async function({ query: { id } }) {
return {
id: id
};
};
export default withAuthSync(PublicationsPage);
The cards components where i use the data of store :
components/Cards.js
const Cards = ({ idPage, cards, type }) => {
console.log(cards)
return (
<div className="cards">
<div className="content-filter-search">
<div className="content-newsearchresult">
<div className="global-name">Result: {cards.cards.length} articles found</div>
<div className="content-button-blue">
<Link href="/explorer">
<a>
<div className="button-blue">New search</div>
</a>
</Link>
</div>
</div>
<div className="content-filter">
{filters[idPage].map((item) => {
return <InputFilter key={item.id} data={item} callback={keepItems} />;
})}
</div>
</div>
<div className="wrapper-card">
<div className="cards-container">
{
!cards.loading ? cards.cards.slice(idPage * 9, idPage * 9 + 9).map((card) => (
<Card key={card.PMCID} data={card} />
)) : <Spinner color="black" size="100px" thickness={3} gap={5} speed="fast" />
}
</div>
</div>
<div className="text">
<Link href={'/publications/page/1'}><a>Next page</a></Link>
</div>
{
!cards.loading ? (
<div className="center">
<Pagination type={type} page={parseInt(idPage)} totalElement={cards.cards.length} numberPerPage={9} />
</div>
) : null
}
</div>
);
};
const mapStateToProps = state => ({
cards: state.cards,
})
export default connect(mapStateToProps)(Cards);
I use the same code for the route /publications and /publications/page/:id just I add the getInitialProps to keep the id of page. And I use connect redux in my Cards component
I have no error in the console just my store is reset because the page reload. I don't understand how I can make pagination with my store where is my data if when I change page the store is empty
Thanks

Resources