React useState can't be set in useEffect - javascript

I am learning react and trying set object from queryStr for later reuse,
can't set searchFilter in useEffect
this line prinst null:
console.log(searchFilter.transactionId)//prints null
interface TransactionSearchFilter {
transactionId?: number;
}
const TransactionList: any = (props: any) => {
const queryStr = location.search.substring(1);
const [searchFilter, setSearchFilter] = React.useState<TransactionSearchFilter>({
transactionId: null,
});
const parseQueryParams = () => {
const queryObj = QueryString.parse(queryStr);
console.log(queryObj.transactionId)//prints 10
setSearchFilter({ ...searchFilter, ...queryObj });
console.log(searchFilter.transactionId)//prints null
};
React.useEffect(() => {
parseQueryParams();
}, []);
return (<div>Hello World</div>);
}; export default TransactionList;

Related

How can I make the data inside of my Cart become persistent?

I have a NextJS application that is using the ShopifyBuy SDK. I have been successfully able to implement a solution where I am able to fetch the products from Store and display them to the User. The user is also able to go to a product page and add the product to the cart.
However, when the user refreshes the page, the cart is reset, and the data does not persist. The code is below:
context/cart.js:
import { createContext, useContext, useEffect, useReducer } from "react";
import client from "../lib/client";
import Cookies from "js-cookie";
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = "SET_CART";
const initalState = {
lineItems: [],
totalPrice: 0,
webUrl: "",
id: "",
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const savedState = Cookies.get("cartState");
const [state, dispatch] = useReducer(reducer, savedState || initalState);
useEffect(() => {
Cookies.set("cartState", state, { expires: 7 });
}, [state]);
useEffect(() => {
getCart();
}, []);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
const getCart = async () => {
try {
const cart = await client.checkout.create();
setCart(cart);
} catch (err) {
console.log(err);
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={{ state }}>
{children}
</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
products/[handle].tsx:
import React, { useState, useEffect } from "react";
import client from "../../lib/client";
import { useCartDispatch, useCartState } from "../../context/cart";
import Link from "next/link";
import cookie from "js-cookie";
export const getStaticPaths = async () => {
const res = await client.product.fetchAll();
const paths = res.map((product: any) => {
return {
params: { handle: product.handle.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context: any) => {
const handle = context.params.handle;
const res = await client.product.fetchByHandle(handle);
const product = JSON.stringify(res);
return {
props: {
product,
},
};
};
function Product({ product }: any) {
const { state } = useCartState();
const { setCart } = useCartDispatch();
const addToCart = async () => {
const checkoutId = state.id;
const lineItemsToAdd = [
{
variantId: product.variants[0].id,
quantity: 1,
},
];
const res = await client.checkout.addLineItems(checkoutId, lineItemsToAdd);
setCart(res);
};
product = JSON.parse(product);
return (
<div>
<div className=" flex-col text-2xl font-bold m-8 flex items-center justify-center ">
<h1>{product.title}</h1>
<button onClick={addToCart}>Add to Cart</button>
<Link href="/cart">Checkout</Link>
</div>
</div>
);
}
export default Product;
pages/cart/index.tsx:
import React, { useEffect } from "react";
import { useCartState, useCartDispatch } from "../../context/cart";
import client from "../../lib/client";
function Cart() {
const { state } = useCartState();
return (
<div>
<h1>Cart</h1>
{state.lineItems &&
state.lineItems.map((item: any) => {
return (
<div key={item.id}>
<h2>{item.title}</h2>
<p>{item.variant.title}</p>
<p>{item.quantity}</p>
</div>
);
})}
</div>
);
}
export default Cart;
I have tried using a library called js-cookie and also localStorage. I'm not sure where the problem lies or if the solutions that I've tried are wrong.
P.S.: I'm fairly new to NextJS and Typescript so go easy on the syntax. This code is for a personal project. Thanks in advance!
Answering this because I ended up coming up with a solution that works for me, at least.
Here it is:
const getCart = async () => {
try {
const checkoutId = Cookies.get("checkoutId");
let cart;
if (checkoutId) {
cart = await client.checkout.fetch(checkoutId);
} else {
cart = await client.checkout.create();
Cookies.set("checkoutId", cart.id);
}
setCart(cart);
} catch (err) {
console.log(err);
}
};
From my understanding, what this does is the following:
Check the cookies to see if one exists called "checkoutId"
If it exists, fetch the cart using that checkoutId
Otherwise, create a new cart and create a cookie using the cart.id that is returned in the response
Then, inside my individual Product page ([handle].tsx), I'm doing the following:
const addToCart = async () => {
const checkoutId = state.id;
const lineItemsToAdd = [
{
variantId: product.variants[0].id,
quantity: 1,
},
];
const res = await client.checkout.addLineItems(checkoutId, lineItemsToAdd);
console.log(res);
if (cookie.get("checkoutId") === undefined) {
cookie.set("checkoutId", res.id);
}
setCart(res);
};
Using cookies to store your object cart, as far as I know, is not a good idea. You could use localStorage, like so:
import { createContext, useContext, useEffect, useReducer } from "react";
import client from "../lib/client";
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = "SET_CART";
const initalState =
typeof localStorage !== "undefined" && localStorage.getItem("cartState")
? JSON.parse(localStorage.getItem("cartState"))
: {
lineItems: [],
totalPrice: 0,
webUrl: "",
id: "",
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initalState);
useEffect(() => {
localStorage.set("cartState", JSON.stringify(state));
}, [state]);
useEffect(() => {
getCart();
}, []);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
const getCart = async () => {
try {
const cart = await client.checkout.create();
setCart(cart);
} catch (err) {
console.log(err);
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={{ state }}>{children}</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);

Auto unsubscribe from custom store

I am developing app using svelte and I use custom stores.
I know that svelte automatically handles unsubscribe for us if we use $
<h1>The count is {$count}</h1>
but, as I have to filter my array in script, how can I use this advantage?
from
const filteredMenu = menu.filter("header");
to
$: filteredMenu = menu.filter("header");
?. or maybe I have to manually unsubscribe on unMount hook?
I am including my code
// /store/menu.ts
import { writable } from "svelte/store";
import { myCustomFetch } from "#/utils/fetch";
import type { NavbarType } from "#/types/store/menu";
const createMenu = () => {
const { subscribe, set } = writable(null);
let menuData: Array<NavbarType> = null;
return {
subscribe,
fetch: async (): Promise<void> => {
const { data, success } = await myCustomFetch("/api/menu/");
menuData = success ? data : null;
},
sort(arr: Array<NavbarType>, key: string = "ordering") {
return arr.sort((a: NavbarType, b: NavbarType) => a[key] - b[key]);
},
filter(position: string, shouldSort: boolean = true) {
const filtered = menuData.filter((item: NavbarType) =>
["both", position].includes(item.position)
);
return shouldSort ? this.sort(filtered) : filtered;
},
reset: () => set(null),
};
};
export const menu = createMenu();
// Navbar.svelte
<sript>
const filteredMenu = menu.filter("header");
</script>
{#each filteredMenu as item, index (index)}
<a
href={item.url}
target={item.is_external ? "_blank" : null}
class:link-selected={activeIndex == index}>{item.title}
</a>
{/each}

Refactoring a class component to Functional, ReferenceError

I am trying to refactor a class component, but in the class one, there is a state with map and I tried changing it to Functional and used useState but it keeps giving me this error
ReferenceError: Cannot access 'rules' before initialization
it happens when I'm trying to refactor the State of rules(which I'm not sure how), with map, to useState. Is it even the correct way of assigning state for map and how can I fix it?
the class component :
import Rule from "./Rule";
class Game extends Component {
state = {
dices: Array(this.props.nOfDices).fill(1),
locked: Array(this.props.nOfDices).fill(false),
rotation: Array(this.props.nOfDices).fill(0),
rollsRemaining: 3,
isRolling: false,
rules: this.props.rules.map( r => ({...r})),
score: 0,
bestScore: window.localStorage.getItem("bestScore") || "0"
};
componentDidMount() {
this.roll();
};
my refactored functional component :
const Game = ({ nOfDices }) => {
const [isRolling, setisRolling] = useState(false);
const [score, setScore] = useState(0);
const [rollsRemaining, setRollsRemaining] = useState(3);
const [dices, setDices] = useState([Array(nOfDices).fill(1)]);
const [rules, setRules] = useState(rules.map(r => ({ ...r })));
const [bestScore, setBestScore] = useState(window.localStorage.getItem("bestScore") || "0");
const [locked, setLocked] = useState([Array(nOfDices).fill(false)]);
const [rotation, setRotation] = useState([Array(nOfDices).fill(0)]);
useEffect(() => {
roll();
//eslint-disable-next-line
}, []);
You are currently setting rules to a map of itself...
const [rules, setRules] = useState(rules.map(r => ({ ...r })));
should it be coming from props as it is in the original?
state = {
// ...
rules: this.props.rules.map( r => ({...r})),
// ...
}
If so you'll need to also destructure it out of props in the parameter declaration. (Here renaming it to avoid collision with the the state name Game = ({rules: _rules, nOfDices}) => ...)
Something like...
const Game = ({ rules: _rules, nOfDices }) => {
const [isRolling, setisRolling] = useState(false);
const [score, setScore] = useState(0);
const [rollsRemaining, setRollsRemaining] = useState(3);
const [bestScore, setBestScore] = useState(window.localStorage.getItem('bestScore') || '0');
// nOfDices
const [dices, setDices] = useState([Array(nOfDices).fill(1)]);
const [locked, setLocked] = useState([Array(nOfDices).fill(false)]);
const [rotation, setRotation] = useState([Array(nOfDices).fill(0)]);
// rules
const [rules, setRules] = useState(_rules.map((r) => ({ ...r })));
// update state if `nOfDices` changes in props
useEffect(() => {
setDices([Array(nOfDices).fill(1)]);
setLocked([Array(nOfDices).fill(false)]);
setRotation([Array(nOfDices).fill(0)]);
}, [nOfDices]);
// update state if `_rules` changes in props
useEffect(() => {
setRules(_rules.map((r) => ({ ...r })));
}, [_rules]);
useEffect(() => {
roll();
//eslint-disable-next-line
}, []);

Null useState issue when state is not null in useEffect

some trouble with state in TypeScript React.
A Child component passes a ’terminal’ object to the Parent through a passed function named returnTerminal(). This terminal object is then stored as a useState _object. useEffect() says that _object is not null eventually, but callback() continuously maintains that _object is null, and I’m not sure why.
Parent Component
const Parent: React.FunctionComponent<ParentProps> = () => {
const [_object, set_object] = useState<Terminal>(null);
const handleGetTerminal = (terminal: Terminal) => {
set_object(terminal);
};
useEffect(() => {
if (_object !== null) {
_object.writeln("_object is not null"); // This prints just fine
}
}, [_object]);
return (
<Child
returnTerminal={(term) => handleGetTerminal(term)}
callback={() => {
console.log(_object === null); // This returns true - the object is null for some reason???
}}
/>
);
};
Child Component
const Child: React.FunctionComponent<ChildProps> = (props) => {
const { callback, returnTerminal } = props;
// The ref where the terminal will be stored and returned
const ref = useRef<HTMLDivElement>(null);
// The xterm.js terminal object
const terminal = new Terminal();
useLayoutEffect(() => {
// Calls whenever terminal is typed into
if (callback) terminal.onData(callback);
// Mount terminal to the DOM
if (ref.current) terminal.open(ref.current);
// Pass the terminal to the parent object
returnTerminal(terminal);
}, []);
return <div ref={ref}></div>;
};
export default Child;
The callback() always returns that _object is null, no matter how long I wait.
The answer ended up being the use of useImperativeHandle and forwardRef. Here’s the completed code.
Parent
import Child from "./child";
interface ParentProps {
name?: string;
}
const Parent: React.FunctionComponent<ParentProps> = () => {
type ChildHandle = ElementRef<typeof Child>;
const childRef = useRef<Child>(null);
let _object: Terminal; // same Terminal type that Child uses
useEffect(() => {
if (childRef.current) {
_object = childRef.current.get(); // imperitive handle function
_object.writeln(“_object loaded”);
}
}, []);
return (
<Child
ref={childRef}
callback={() => {
terminal.writeln("_object is not null”); // THIS finally works
}}
/>
);
};
Child
interface ChildProps {
callback?(data: string): void;
}
type ChildHandle = {
get: () => Terminal;
};
const Child: ForwardRefRenderFunction<ChildHandle, ChildProps> = (
props,
forRef
) => {
const { callback } = props;
const terminal = new Terminal();
// The ref where the terminal will be stored and returned
const ref = useRef<HTMLDivElement>(null);
useImperativeHandle(forRef, () => ({
get() {
return terminal;
},
}));
useLayoutEffect(() => {
// Calls whenever terminal is typed into
if (callback) terminal.onData(callback);
// Mount terminal to the DOM
if (ref.current) terminal.open(ref.current);
}, []);
return <div ref={ref}></div>;
};
export default forwardRef(Child);

What does the subscribers array do in the HOC withUser?

Just as the title says, I don't understand in this code what does the subscribers.
import React from "react";
const stateFromStore = sessionStorage.getItem('user');
let state = stateFromStore ? JSON.parse(stateFromStore) : null;
const subscribers = [];
const unsubscribe = subscriber => {
const index = subscribers.findIndex(subscriber);
index >= 0 && subscribers.splice(index, 1);
};
const subscribe = subscriber => {
subscribers.push(subscriber);
return () => unsubscribe(subscriber);
};
export const withUser = Component => {
return class WithUser extends React.Component {
componentDidMount() {
this.unsubscribe = subscribe(this.forceUpdate.bind(this));
}
render() {
const newProps = { ...this.props, user: state };
return <Component {...newProps} />;
}
componentWillUnmount() {
this.unsubscribe();
}
};
};
export const update = newState => {
state = newState;
sessionStorage.setItem('user', state ? JSON.stringify(state) : null);
subscribers.forEach(subscriber => subscriber());
};
I get the part about sessionStorate but I don't understand the use of the subscribers array.
This is part of an example about how to use passport with react https://github.com/HackedByChinese/passport-examples/tree/master/example-simple-react/client/src

Categories