State is not updating on axios call post Next js - reactjs

i'm developing an ecommerce whit Nextjs and payments with Paypal.
This is my product component
const Product = () => {
const router = useRouter();
const { id, category } = router.query;
const [product, setProduct] = useState();
const [amount, setAmount] = useState(1);
useEffect(() => {
if (category) {
const foundProduct = products[category].find(
(element) => element.id == id
);
setProduct({ ...foundProduct, amount, total: foundProduct.price * amount });
}
}, [id, amount]);
return (
<>
{!product ? (
<Spinner />
) : (
<div className="product-wrapper">
<div className="product-image">
<Image src={product.image} />
</div>
<div className="product-info">
<h3>{product.title}</h3>
<p className="product-price">
{product.currency} {product.price}
</p>
<p className="product-description">
{product.description}
</p>
<div className="product-cart-container">
<div className="product-cart-handle">
<p onClick={() => amount > 1 && setAmount(amount - 1)}>-</p>
<span>{amount}</span>
<p onClick={() => setAmount(amount + 1)}>+</p>
</div>
<BuyButtton item={product} amount={amount} />
</div>
<div className="product-general">
<p>General information</p>
</div>
</div>
</div>
)}
</>
);
};
and this is my BuyButton component
const BuyButtton = ({ item }) => {
useEffect(() => {
console.log(item);
}, [item]);
return (
<div>
<PayPalScriptProvider
options={{
"client-id":"",
}}
>
<PayPalButtons
createOrder={async () => {
try {
const res = await axios({
url: "http://localhost:3000/api/payment",
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
data: JSON.stringify(item),
});
return res.data.id;
} catch (error) {
console.log(error);
}
}}
onApprove={(data, actions) => {
console.log(data);
actions.order.capture();
}}
style={{ layout: "horizontal", color: "blue" }}
/>
</PayPalScriptProvider>
</div>
);
};
So when i pass this props item to my BuyButton component works fine, the amount and total value updates correctly, the problem is when i do the axios call, it looks like the component stores the initial value of the item prop, so amount and value never changes, its always amount:1, value:item.value. Any ideas? Thanks
I tried storing the item prop in a state but it didin't work as i expected

The solution that solved my problem was adding forceReRender prop to PayPalButtons like this forceReRender={[item]}, so it re-render the button and gets the new amount value

In the Product component, you are passing item & amount to BuyButton
<BuyButtton item={product} amount={amount} />.
You need to add the amount to the BuyButton component as well.
const BuyButtton = ({ item, amount }) => {
and pass the amount in the request body in the axios call
data: JSON.stringify(item),
// need to add the amount data

Related

Showing Data in React Native with useParams

I've the following question, how can I display the data in react-native with useparams? I've the following code snippet in my Details.jsx
import React from 'react'
import {useParams} from 'react-router-dom'
const Details = () => {
const userData = JSON.parse(localStorage.getItem('user'))
const token = userData ? userData.accessToken : null
const params = useParams()
const [load, setLoad] = React.useState(false)
const getAccountDetails = React.useCallback(async () => {
setLoad(true)
await fetch (`http://localhost:4000/api/account/findonecard/${params.idCard}`, {
headers: {
'Authorization': `Bearer ${token}`
}
}).then(res => res.json()).then(response=>{
console.log(response)
//My response drops the data I need
})
}, [])
React.useEffect(() => {
getAccountDetails()
}, [getAccountDetails])
return (
<>
<div>Details</div>
{
!load ?
(
<Detail card={{
username: card.fullname,
description: card.description,
mainPic: card.pic_profile,
userTags: card.usertags
}} />
)
:
(
<div style={{ position: 'relative', zIndex: 99, color: '#000000', fontFamily: 'sans-serif', textAlign: 'center', margin: '60px auto' }}>No Card to show</div>
)
}
</>
)
}
export default Details
This releases an object with my data, such as photo, description, name, etc.
Although I tried to loop through the object, I can't display the data on my Front, what am I missing?
This is my Detail.jsx
const Detail = ({ card }) => {
return (
<div className="content">
<div className="content_card-container">
<ShowMore/>
<h2 className="content_card-username">{card.username}</h2>
<div
className="content_card-image"
style={{
backgroundImage:
`url(${card.cardImg})`
}}
/>
<div className="content_tags-slider">
<SliderComponent tags={card.userTags} />
</div>
</div>
<div className="content_user-description">
<p className="content_user-description-text">{card.description}</p>
</div>
<div className="content_choice-container">
<img src={Dislike} alt="" className="content_choice-icon" />
<img src={Like} alt="" className="content_choice-icon" />
</div>
</div>
);
};
To keep track of the account details we'll create a state.
const [accountDetails, setAccountDetails] = useState({});
In the getAccountDetails function you can set the state with the received data from the api. You're also mix 'n matching await and then, you should use one. Lets go with await for this one
const getAccountDetails = React.useCallback(async () => {
setLoad(true);
const response = await fetch(
`http://localhost:4000/api/account/findonecard/${params.idCard}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await response.json();
// console.log(data);
setAccountDetails(data);
setLoad(false); // set load to false
}, []);
When passing the card data down to the Detail component we can use the accountDetails like so
<Detail
card={{
username: accountDetails.fullname,
description: accountDetails.description,
mainPic: accountDetails.pic_profile,
userTags: accountDetails.usertags,
}}
/>
You maybe want to set the initital state of load to true

How to pass id from page to another? [duplicate]

This question already has answers here:
How to pass data from a page to another page using react router
(5 answers)
Closed 5 months ago.
I have 2 pages: (Datatable.jsx and Single.jsx).
I need to send id from Datatable.jsx to Single.jsx. After googling, i found that i can do that by using the <Link /> component, like this:
<Link
to={{
pathname: "/page",
state: data
}}
>
And then you can access the desired sent data to the second page:
render() {
const { state } = this.props.location
return (
// render logic here
)
}
I dont know how to apply this on my two pages:
Datatable.jsx:
//...
const Datatable = () => {
const [data, setData] = useState([]);
const handleDelete = (id) => {
setData(data.filter((item) => item.id !== id));
fetch(`https://api.factarni.tn/article/${id}`, {
method: "DELETE",
headers: {
Authorization:
"Bearer eyJhbGciOiJS...qw2QWltkyA",
},
}).then((response) => response.json());
};
useEffect(() => {
fetch("https://api.factarni.tn/article", {
method: "GET",
headers: {
Authorization:
"Bearer eyJhbGciOiJSUz...WltkyA",
},
})
.then((response) => response.json())
.then((json) => setData(json));
}, []);
const actionColumn = [
{
field: "action",
headerName: "Action",
width: 200,
renderCell: (params) => {
return (
<div className="cellAction">
<Link to="/users/test" style={{ textDecoration: "none" }}>
<div className="viewButton">Modifier</div>
</Link>
<div
className="deleteButton"
onClick={() => handleDelete(params.row.id)}
>
Delete
</div>
</div>
);
},
},
];
return (
<div className="datatable">
<div className="datatableTitle">
Add New Article
<Link to="/users/new" className="link">
<AddBusinessIcon className="icon" /> Add Article
</Link>
</div>
<DataGrid
className="dataGrid"
rows={data}
columns={userColumns.concat(actionColumn)}
pageSize={9}
rowsPerPageOptions={[9]}
checkboxSelection
/>
</div>
);
};
export default Datatable;
Single.jsx:
//...
const Single = ({ inputs, title }) => {
const [data, setData] = useState({
code: "",
article: "",
price: 0,
vat: 0,
status: 0,
company_id: 0,
});
const normalize = (v) => ({
code: v.code,
article: v.article,
price: Number(v.price),
vat: Number(v.vat),
status: Number(v.status),
company_id: Number(v.company_id),
});
function handle(e) {
const newdata = { ...data };
newdata[e.target.id] = e.target.value;
setData(newdata);
console.log(newdata);
}
const handleClick = async (e) => {
e.preventDefault();
const body = normalize(data);
await fetch("https://api.factarni.tn/article/create", {
method: "PUT",
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
Authorization:
"Bearer eyJhbGciOiJ...w2QWltkyA",
},
});
};
return (
<div className="New">
<Sidebar />
<div className="newContainer">
<Navbar />
<div className="top">
<h1>{title}</h1>
</div>
<div className="bottom">
<div className="right">
<form>
<div className="formInput"></div>
{inputs.map((input) => (
<div className="formInput" key={input.id}>
<label>{input.label} </label>
<input
type={input.type}
placeholder={input.placeholder}
onChange={(e) => handle(e)}
id={input.label}
name={input.label}
value={input.label}
/>
</div>
))}
<button onClick={handleClick}>Update</button>
</form>
</div>
</div>
</div>
</div>
);
};
export default Single;
In the Database.jsx:
// ... code
<Link to={{ pathname: "/users/test", state: { id: params.row.id }}} style={{ textDecoration: "none" }}>
<div className="viewButton">Modifier</div>
</Link>
// ... code
In the Single.jsx:
import { useLocation } from 'react-router-dom';
// ... later in render function
const { state } = useLocation() // state.id should have your id
Although #deaponn's answer is good, you can also use the useNavigate hook and pass the id, name or any data in the state like below, using programmatic approach rather than Link component exported from react-router library
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate('/(url on which you want to navigate)', { state: { id:1,name:encryptedId} });
On the navigated component, if you want to retrieve the passed id or name, you can use the useLocation hook as below:
import { useLocation } from "react-router-dom";
const location = useLocation();
var ttid = location.state.id;

Reat Js navigate with json object to another page

Below is my code, I am fetching the data from api and on success I am setting the the response of state in set_ProductDetails. I want to pass the response state to different component and different page with the result and bind the data. I am using "react-router-dom": "^5.2.0".
Product_info.jsx
function GetProductDetails(products) {
const history = useHistory();
useEffect(() => {
console.log("render", history.location.state);
}, []);
return (
<>
<div>
<h1>Transaction Info</h1>
</div>
</>
);
}
export default GetProductDetails
Product_query.jsx
function ProductSearch() {
const [product_id, setProduct_id] = useState();
const [product_search, set_ProductSearch] = useState({ product_id: "" });
const [product_deatils, set_ProductDetails] = useState({ product_id: "" });
const history = useHistory();
//Handle the onSubmit
function handleSubmit() {
try {
set_ProductSearch({ address: product_id });
} catch (e) {
alert(e.message);
}
}
function onAPISuccess(data) {
history.push("/product_info/GetProductDetails", { data });
//here render blank screen
}
useEffect(() => {
const fetchData = async (product_id) => {
try {
const resp = await axios.post(
config.SERVER_URL + "/api/getProductInfo/",
product_id
);
set_ProductDetails(resp.data);
onAPISuccess(data)
} catch (err) {
console.error(err);
fetchData(product_search)
.catch(console.error);
}
}, [product_search]);
return (
<>
<div class="input-group mb-3">
<input
type="text"
class="form-control"
aria-describedby="button-addon2"
id="txt_address"
name="address"
placeholder="Address/Tx hash"
onChange={(e) => setProduct_id(e.target.value)}
></input>
<div class="input-group-append" style={{ color: "white" }}>
<button
class="btn btn-outline-success"
type="button"
id="button-addon2"
onClick={() => handleSubmit()}
>
Search
</button>
</div>
</div>
</>
);
}
export default ProductSearch
Home page
export default function Home() {
return (
<>
<main>
<div
className="col-md-12"
style={{
background: "#fff",
backgroundImage: `url(${Image})`,
height: "245px",
}}
>
<Container className="container-sm">
<Row>
<Col xs lg="5" className="justify-content-md-center">
<div>
<ProductSearch></ProductSearch>
</div>
</Col>
</Row>
</Container>
</div>
</main>
<>
)
}
Do history.push("/your_path",{..object you want to send}). Then in the component where this history.push redirects, access that object by saying history.location.state (this will return the object you passed while redirecting).

React Router: Navigate back to Search results

I have dynamic routes based on search results. How do I go back and see my previously rendered search results & search term in input field versus and empty Search page?
I've started looking into useHistory/useLocation hooks, but I'm lost.
1. Search page
export default function Search() {
const [searchValue, setSearchValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [noResults, setNoResults] = useState(false);
const [data, setData] = useState([]);
const fetchData = async () => {
const res = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key={API_KEY}&query=${searchValue}`
);
const data = await res.json();
const results = data.results;
if (results.length === 0) setNoResults(true);
setData(results);
setIsLoading(false);
};
function handleSubmit(e) {
e.preventDefault();
setIsLoading(true);
fetchData();
// setSearchValue("");
}
return (
<div className="wrapper">
<form className="form" onSubmit={handleSubmit}>
<input
placeholder="Search by title, character, or genre"
className="input"
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
</form>
<div className="page">
<h1 className="pageTitle">Explore</h1>
{isLoading ? (
<h1>Loading...</h1>
) : (
<div className="results">
{!noResults ? (
data.map((movie) => (
<Result
poster_path={movie.poster_path}
alt={movie.title}
key={movie.id}
id={movie.id}
title={movie.title}
overview={movie.overview}
release_date={movie.release_date}
genre_ids={movie.genre_ids}
/>
))
) : (
<div>
<h1 className="noResults">
No results found for <em>"{searchValue}"</em>
</h1>
<h1>Please try again.</h1>
</div>
)}
</div>
)}
</div>
</div>
);
}
2. Renders Result components
export default function Result(props) {
const { poster_path: poster, alt, id } = props;
return (
<div className="result">
<Link
to={{
pathname: `/results/${id}`,
state: { ...props },
}}
>
<img
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
</Link>
</div>
);
}
3. Clicking a result brings you to a dynamic page for that result.
export default function ResultPage(props) {
const [genreNames, setGenreNames] = useState([]);
const {
poster_path: poster,
overview,
title,
alt,
release_date,
genre_ids: genres,
} = props.location.state;
const date = release_date.substr(0, release_date.indexOf("-"));
useEffect(() => {
const fetchGenres = async () => {
const res = await fetch(
"https://api.themoviedb.org/3/genre/movie/list?api_key={API_KEY}"
);
const data = await res.json();
const apiGenres = data.genres;
const filtered = [];
apiGenres.map((res) => {
if (genres.includes(res.id)) {
filtered.push(res.name);
}
return filtered;
});
setGenreNames(filtered);
};
fetchGenres();
}, [genres]);
return (
<div className="resultPage">
<img
className="posterBackground"
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
<div className="resultBackground">
<div className="resultInfo">
<h1> {title} </h1>
</div>
</div>
</div>
);
}
4. How do I go back and see my last search results?
I'm not sure how to implement useHistory/useLocation with dynamic routes. The stuff I find online mentions building a button to click and go to last page, but I don't have a button that has to be clicked. What is someone just swipes back on their trackpad?
One way you could do this would be to persist the local component state to localStorage upon updates, and when the component mounts read out from localStorage to populate/repopulate state.
Use an useEffect hook to persist the data and searchValue to localStorage, when either updates.
useEffect(() => {
localStorage.setItem('searchValue', JSON.stringify(searchValue));
}, [searchValue]);
useEffect(() => {
localStorage.setItem('searchData', JSON.stringify(data));
}, [data]);
Use an initializer function to initialize state when mounting.
const initializeSearchValue = () => {
return JSON.parse(localStorage.getItem('searchValue')) ?? '';
};
const initializeSearchData = () => {
return JSON.parse(localStorage.getItem('searchData')) ?? [];
};
const [searchValue, setSearchValue] = useState(initializeSearchValue());
const [data, setData] = useState(initializeSearchData());

(Next/react) SWR refresh from button click in child component - can I use a callback?? (function to function)

I'm making a shopping cart component with Next.js and having an issue refreshing cart data after updating it. My cart component is a function as recommended on Next.js documentation (https://nextjs.org/docs/basic-features/data-fetching#fetching-data-on-the-client-side) and I have a component inside this called products.js, containing my list of products, which each have +/- buttons to adjust their qty (post to the database).
My database updates, but I need to re-fetch the data clientside on the cart component. I've realized I can re-fetch the swr call using swrs 'mutate', but when I try to pass a callback function from my cart.js to products.js it shows up in console log, but isn't called on my button click.
I have tried cartUpdate = cartUpdate.bind(this) and also looked into hooks the last couple days and tried other things but could use some advice.
If cart.js were a class component I would bind my cartUpdate function before passing it down to product.js and this would work, but I can't seem to do the same when it's function to function vs class to function.
I've been on this a couple of days and I'm not sure If I'm trying to go against some rules I don't know about or how I can re-fetch my data while still keeping my code separated and somewhat clean.
products.js:
export default function productsection ({products, cart, cartproducts, feUpdate}){
console.log("feUpdate", feUpdate)
return (
<div>
{products.map((product, i) => (
<div className="flex-column justify-content-center mx-2">
<div className="mx-auto card w-100 p-2 my-2">
<div className="card-body ">
<h5 className="card-title text-center">{product.name}</h5>
<div className="d-flex justify-content-between">
{/* <p>Size Selected: {product.size}</p> */}
{/* <p>Total: {product.price * props.cartproducts[i].qty} USD</p> */}
<button className='btn btn-light mx-2' onClick={() => {
fetch(`/api/db/editCartProducts`,{
method: 'post',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({task: 'ADD', orderid: cart.orderid, productid: product.productid})
}).then(res => {console.log("adding: " + product.name + " from cart.", "id: " + product.productid); () => feUpdate(); console.log("passed update")})
}}
>
Add
</button>
<p className="px-2">Price: {product.price} USD EA</p>
<p className="px-2">{product.description}</p>
<p>Quantity: {cartproducts[i].qty}</p>
<button className='btn btn-light mx-2' onClick={() => {
fetch(`/api/db/editCartProducts`,{
method: 'post',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({task: 'REMOVE', orderid: cart.orderid, productid: product.productid})
}).then(res => {console.log("removing: " + product.name + " from cart.", "id: " + product.productid);() => feUpdate(); console.log("passed update")})
}}>
Remove
</button>
</div>
</div>
</div>
</div>
))}
</div>
)
}
cart.js: (main points are cartUpdate() & ProductSection which I'm passing cartUpdate into)
function Cart (props){
const fetcher = (...args) => fetch(args[0], {
method: 'post',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({[args[2]]:args[1]})
}).then(res => res.json())
let User = Cookies.get('User')
async function cartUpdate(){
console.log("mutate called");
console.log("isValidated: ", isValidating)
mutate();
console.log(cartitems);
console.log("isValidated: ", isValidating)
}
const { data: user, error} = useSWR(() => [url1, User, "User"], fetcher, {suspense: false });
console.log("User returned: ", user)
const { data: cart, error2} = useSWR(() => [url2, user.customerid, 'User'], fetcher, { suspense: false });
console.log("Cart returned: ", cart)
// const OrderId = Cookies.get('orderid')
const { data: cartitems, error3, mutate, isValidating} = useSWR(() => [url3, cart.orderid, 'orderid'], fetcher, { suspense: false });
console.log("Cart items: ", cartitems)
console.log("before productdetails call")
const { data: productdetails, error4} = useSWR(() => [url4, cartitems, 'productids'], fetcher, { suspense: false });
console.log("productdetails: ", productdetails)
let itemtotal = 0;
let costtotal = 0;
if(productdetails && cartitems){
productdetails.forEach((product, i) => {
itemtotal = itemtotal + (cartitems[i].qty);
costtotal = costtotal + (product.price * cartitems[i].qty)
})
console.log("totals: ", itemtotal, costtotal)
}
if (productdetails) {
console.log(props)
// foreach to get total price of cart and total items count.
return (
<div className="jumbotron jumbotron-fluid mt-5 d-flex flex-column justify-content-center">
<Header name={user.fname}/>
<div className={!isValidating? "card text-center" : "text-center"}>isValidating??</div>
<div className="d-flex flex-row justify-content-center">
<button onClick={() => feUpdate()}>Big Update Button</button>
<ProductSection feUpdate={cartUpdate} products={productdetails} cart={cart} cartproducts={cartitems} />
<Summery itemtotal={itemtotal} costtotal={costtotal}/>
</div>
</div>
)
}
Are you using the global mutate (yes there're 2 of them)? Then you need to pass a SWR key to it.
Global mutate:
import useSWR, { mutate } from 'swr'
...
const { data } = useSWR(key, fetcher)
mutate(key) // this will trigger a refetch
Or using bound mutate, and you don't need to pass the key:
import useSWR from 'swr'
...
const { data, mutate } = useSWR(key, fetcher)
mutate() // this will trigger a refetch
So just to close this off, I came back to it after working on other areas of the project and realized it was as simple as using a normal function call instead of arrow function e.g. .then(res => {feUpdate()}) don't know how I overlooked this or what was going on with testing before but it's working now. I guess taking a break and coming back with fresh eyes does the trick.

Resources