20
loading...
This website collects cookies to deliver better user experience
RecoilRoot
(remember to import it from recoil, I won’t include all the imports here).ReactDOM.render(
<React.StrictMode>
<RecoilRoot>
<App />
</RecoilRoot>
</React.StrictMode>,
document.getElementById("root")
);
const cartState = atom({
key: "cartState",
default: [],
});
const [cart, setCart] = useRecoilState(cartState);
const products = [
{ name: "Toothbrush", price: 10 },
{ name: "Smart TV", price: 800 },
{ name: "Laptop", price: 600 },
{ name: "Chocolate", price: 12 },
{ name: "Apple juice", price: 5 },
];
const ShopProducts = () => {
const setCart = useSetRecoilState(cartState);
const addToCart = (product) =>
setCart((cart) =>
cart.find((item) => item.name === product.name)
? cart
: [...cart, product]
);
return (
<>
PRODUCTS:
<div>
{products.map((product) => (
<p key={product.name}>
{product.name} (${product.price})
<button onClick={() => addToCart(product)}>Add</button>
</p>
))}
</div>
</>
);
};
useSetRecoilState
hook in the setCart
function to achieve this.const App = () => (
<div>
<ShopProducts />
</div>
);
const Cart = () => {
const [cart, setCart] = useRecoilState(cartState);
const removeFromCart = (product) =>
setCart((cart) => cart.filter((item) => item.name !== product.name));
return (
<>
CART:
<div>
{!cart.length
? "Empty"
: cart.map((product) => (
<p key={product.name}>
{product.name} (${product.price})
<button onClick={() => removeFromCart(product)}>Remove</button>
</p>
))}
</div>
</>
);
};
const App = () => (
<div>
<ShopProducts />
<Cart />
</div>
);
const cartDetailsState = selector({
key: "cartDetailsState",
get: ({ get }) => {
const cart = get(cartState);
const total = cart.reduce((prev, cur) => prev + cur.price, 0);
return {
total,
};
},
});
const CartDetails = () => {
const { total } = useRecoilValue(cartDetailsState);
return <p>TOTAL: ${total}</p>;
};
const App = () => (
<div>
<ShopProducts />
<Cart />
<CartDetails />
</div>
);
CartDetails
selector logic to include the new state:const shopConfigState = atom({
key: "shopState",
default: {
discount: 0,
},
});
const cartDetailsState = selector({
key: "cartDetailsState",
get: ({ get }) => {
const { discount } = get(shopConfigState);
const cart = get(cartState);
const total = cart.reduce((prev, cur) => prev + cur.price, 0);
const discountAmount = total * discount;
return {
total,
discountAmount,
};
},
});
const CartDetails = ({ total, discount, discountAmount, setCart }) => (
<div>
<p>
DISCOUNT: {discount * 100}% (${discountAmount})
</p>
<p>TOTAL: {total}</p>
<p>TOTAL AFTER DISCOUNT: {total - discountAmount}</p>
<button onClick={() => setCart([])}>Clear cart</button>
</div>
);
const ShopConfig = () => {
const [shopState, setShopState] = useRecoilState(shopConfigState);
const updateDiscount = ({ target: { value } }) => {
setShopState({ ...shopState, discount: value });
};
return (
<div>
Discount:
<select value={shopState.discount} onChange={updateDiscount}>
<option value={0}>None</option>
<option value={0.05}>5%</option>
<option value={0.1}>10%</option>
<option value={0.15}>15%</option>
</select>
</div>
);
};
const App = () => (
<div>
<ShopConfig />
<ShopProducts />
<Cart />
<CartDetails />
</div>
);
const getProductsFromDB = async () =>
new Promise((resolve) => {
setTimeout(() => resolve(products), 2500);
});
const productsQuery = selector({
key: "Products",
get: async () => getProductsFromDB(),
});
getProductsFromDB
is a function that waits 2.5 second (to simulate a network latency, or database response time) to simply resolve a promise with the product list that we’ve created at the beginning. The important part here is the selector with an async getter function – it simply calls the aforementioned function.const ShopProducts = () => {
const shopProducts = useRecoilValue(productsQuery);
const setCart = useSetRecoilState(cartState);
const addToCart = (product) =>
setCart((cart) =>
cart.find((item) => item.name === product.name)
? cart
: [...cart, product]
);
return (
<>
PRODUCTS:
<div>
{shopProducts.map((product) => (
<p key={product.name}>
{product.name} (${product.price})
<button onClick={() => addToCart(product)}>Add</button>
</p>
))}
</div>
</>
);
};
const App = () => (
<div>
<ShopConfig />
<React.Suspense fallback={<div>Loading products...</div>}>
<ShopProducts />
</React.Suspense>
<Cart />
<CartDetails />
</div>
);
const ShopProducts = () => {
const setCart = useSetRecoilState(cartState);
const shopProducts = useRecoilValueLoadable(productsQuery);
const addToCart = (product) =>
setCart((cart) =>
cart.find((item) => item.name === product.name)
? cart
: [...cart, product]
);
switch (shopProducts.state) {
case "loading":
return <div>Loading products...</div>;
case "hasError":
throw shopProducts.contents;
case "hasValue":
return (
<>
PRODUCTS:
<div>
{shopProducts.contents.map((product) => (
<p key={product.name}>
{product.name} (${product.price})
<button onClick={() => addToCart(product)}>Add</button>
</p>
))}
</div>
</>
);
}
};
useRecoilValueLoadable
, we get access to an object containing the current state of an async selector’s promise and, when it’s resolved, either to contents or error. This works exactly the same as in case of using Suspense.