After changing a UseState Hook React component is not re-rendering - javascript

I read many topics around these problem but none of them helped to find an answer.
I am using a useEffect to fetch an array of objects "nftRewards". And it's working fine cause I can see them on the console. But the part of the render that depends on this hook is not re-render once it was updated. And I don't understand why.
const [campaignRewardsJson, setCampaignRewardsJson] = useState<nftReward[]>([]);
const { data: rewards } = useContractRead({
address: campaignsId,
abi: campaignABI,
functionName: 'getRewards',
});
async function getRewardObject() {
var array: nftReward[] = [];
rewards.map(async (reward, index) => {
const json = fetch(reward[2]);
const data = await json.then((res) => res.json());
const nft: nftReward = {
title: data.name,
description: data.description,
imageURL: data.image,
price: reward[0],
quantity: reward[1],
};
array.push(nft);
});
return array;
}
type nftReward = {
title: string;
description: string;
imageURL: string;
price: number;
quantity: number;
};
useEffect(() => {
if (rewards) {
const fetchData = async () => {
var data = await getRewardObject();
setCampaignRewardsJson(data);
};
// call the function
fetchData().catch(console.error);
}
}, [rewards]);
And At the end of my render , I have this but It's never render by react:
{campaignRewardsJson.map((reward, index) => (
<Card direction={{ base: 'column', sm: 'row' }} overflow="hidden" variant="outline">
<Image objectFit="cover" maxW={{ base: '100%', sm: '200px' }} src={reward.imageURL} alt="Caffe Latte" />
<Stack>
<CardBody>
<Heading size="md">{reward.title}</Heading>
<Text py="2">{reward.description}</Text>
</CardBody>
<CardFooter>
<Button variant="solid" colorScheme="blue">
Mint NFT
</Button>
</CardFooter>
</Stack>
</Card>
))}

async function getRewardObject() {
var array: nftReward[] = [];
await Promise.allSettled(
rewards.map(async (reward, index) => {
const json = fetch(reward[2]);
const data = await json.then((res) => res.json());
const nft: nftReward = {
title: data.name,
description: data.description,
imageURL: data.image,
price: reward[0],
quantity: reward[1],
};
array.push(nft);
}));
return array;
}

If rewards is state in this component and type of that is object or array.
When you pass array or object to dependency, every render snapshot will compare with previous version. You may try keyword useEffect dependency array and object comparison. for more information.

your useEffect hook has rewards as its dependencies. and it will only run if rewards is true or it's defined and not null(truthy). you need to check that condition.

Related

Javascript Object Bracket Notation Not Working

Thanks all in advance.
Going by the screenshot:
enter image description here
I wish to use a dynamic variable (filter) in line 33 but it keeps throwing error. If I use the name properties of the object, Filter_M (eg: "ALL", it works fine. If I use the the values too by using the functions (eg:
(order) => !order.completed
) directly, it works fine.
How can I resolve this?
Note: I am following similar procedure as used in MDN article. See:
Back to the Filter Buttons
A portion of my code is shown below while links to react components are shown at the bottom:
import React from "react";
import FilterButton from "./components/filterButton";
import MyOrder from "./components/MyOrder";
// const ORDER = [
// { cname: 'ki', item: 'liveChicken', quantity: 0, id: "order-0", completed: true },
// { cname: 'fu', item: 'egg', quantity: 0, id: "order-1", completed: false },
// { cname: 'nu', item: 'chicken', quantity: 0, id: "order-2", completed: false}
// ]
// const root = ReactDOM.createRoot(document.getElementById('root'));
// root.render(
// <React.StrictMode>
// <App2 orders={ORDER} />
// </React.StrictMode>
// );
const FILTER_M = {
ALL: () => true,
Active: (order) => !order.completed,
Completed: (order) => order.completed
};
const FILTER_NAMES = Object.keys(FILTER_M);
function App(props) {
const chickenPrice = 5000;
const [filter, setFilter] = useState('All');
const [orders, setOrders] = useState(props.orders)
const orderList = orders
.filter(FILTER_M[filter])
.map((order) => (
<MyOrder
item={order.item}
quantity={order.quantity}
cname={order.cname}
id={order.id}
completed={order.completed}
key={order.id}
/>))
const filterList = FILTER_NAMES.map((cname) => (
<FilterButton
key={cname}
cname={cname}
isPressed={cname === filter}
setFilter={setFilter}
/>
));
}
Code Sandbox
I am expecting to display dynamically, the tasks/orders either active, completed, with all orders shown by default.
I have tried hard-coding it by using the property name or the values and it all worked out fine. But using the named variable in object (FILTER_MAP) bracket notation which is the only way of accessing named variable as name property of an object.
Based on the MDN article you provided this code should work, I can't see the complete picture of your code but anyway this code below is working :
const DATA = [
{ id: 'todo-0', name: 'Eat', completed: true },
{ id: 'todo-1', name: 'Sleep', completed: false },
{ id: 'todo-2', name: 'Repeat', completed: false },
];
const filterBy= {
All: (order) => true,
Active: (order) => !order.completed,
Completed: (order) => order.completed,
};
const DisplayList = () => {
const [filter, setFilter] = useState('All');
const [orders, setOrders] = useState(DATA);
console.log(filterBy[filter]);
const orderList = orders.filter(filterBy[filter]).map(order => (
<li key={order.id} id={order.id}>
<p>{order.name}</p>
<p>{`${order.completed}`}</p>
</li>
));
return (
<>
<button onClick={() => setFilter('Active')}>
view active orders ⬇
</button>
{orderList}
</>
);
};
export default function App() {
return (
<DisplayList />
);
}
a working sample in code sandbox
The problem is here :
const FILTER_MAP = {
ALL: () => true, // HERE (ALL) is all CAPS
Active: (order) => !order.completed,
Completed: (order) => order.completed
};
and your are trying to access (All) a property that doesn't exist in FILTER_MAP
const [filter, setFilter] = useState('All'); // not the same as (ALL) in FILTER_MAP
and that is a good example of why you should use type script, because TS would have helped you find this typo.

react-beautiful-dnd: Prevent flicker when drag and drop a lists

I'm using this react-beautiful-dnd library to be able to reorder lists. However, even though I'm able to drag and drop and re-order, there is a flicker when I try to reorder lists.
You can see in the video:
enter image description here
Here is my code:
I added sorting to the array by order before mapping.
const Board = () => {
const [currentId, setCurrentId] = useState(null);
const lists = useSelector((state) => state.lists);
const dispatch = useDispatch();
const classes = useStyles();
useEffect(() => {
dispatch(getLists());
}, [currentId, dispatch]);
const onDragEnd = (result) => {
const { destination, source, draggableId, type } = result;
if(!destination) return;
const droppableIdStart = source.droppableId;
const droppableIdEnd = destination.droppableId;
const droppableIndexStart = source.index;
const droppableIndexEnd = destination.index;
const newState = [...lists];
// drag lists
if(type === 'list') {
const dragList = newState.splice(droppableIndexStart, 1);
newState.splice(droppableIndexEnd, 0, ...dragList);
// update order list to be index
newState.forEach((list, index) => {
list.order = index;
dispatch(updateList(list._id , { ...list }));
});
}
return newState;
}
// Arranging lists by order
const newArrange = (a,b) => {
return (a.order - b.order);
}
lists.sort(newArrange);
return (
<>
<DragDropContext onDragEnd={onDragEnd} >
<div>
<h1>Board</h1>
<Droppable droppableId="all-lists" direction="horizontal" type="list">
{ provided => (
<div className={classes.listContainer} {...provided.droppableProps} ref={provided.innerRef} >
{ lists.map((list, index) =>
(user?.result?.googleId === list?.creator || user?.result?._id === list?.creator) ?
<List key={list._id} title={list.title} cards={list.cards} currentId={list._id} index={index} /> :
null
)}
{addListFlag && (
<InputItem
value={listData.title}
btnText={"Add list"}
type={"list"}
placeholder={"Enter a list title..."}
changedHandler={handleChange}
closeHandler={closeHandlerBtn}
addItem={submitHandler}
/>
)}
{!addListFlag && (
<AddBtn btnText={"Add another list"} type={"list"} handleClick={handleAddition} />
)}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
</DragDropContext>
</>
)
}
export default Board;
Sample of data:
{
_id: "6163cdd306d27b",
title: "a",
name: "first one",
order: "0",
cards:[
{
id: "0",
text: "a1",
_id: {_id: "61d0ca2c20d27e"}
},
{
id: "1",
text: "a2",
_id: {_id: "616541ec90630"}
},
{
id: "2",
text: "a3",
_id: {_id: "61606e609"}
}
]
}
Thank :)
It has probably todo with your getLists() call in the useEffect hook.
Is there an async function behind it? Do you get your lists from a server? If so, I suppose that useEffect is firing twice (once on drag end and once when it gets back the data from the backend or whatever getLists is doing), which leads to the flickering.
It would probably help to know what exactly getLists is.

How to select and save my components ids in an array?

I have a landing Page with an array that is passed to my TemplateList component. And my TemplateList component is made up by TemplateCard components.
Each card on my TemplateList can be selected, and if more than 2 of those cards are selected, the button on the page activates onOnboardingComplete, and it continues to the next page.
What I would like to do, is to be able to grab each card's unique Id that I selected on the page And once I select one or more, convert those ids into an array (called: selectedTemplatesIds) that I can pass to onOnboardingComplete (the button).
Using the id as an identifier, and based on that we are getting an array of selected cards and passing it with onOnboardingComplete.
I hear I can use Object.keys, but never done this before. How can I do this?
Summary:
1. I want to be able to grab my card unique ids when selected and ignore them when unselected
2. Once I select one or more, convert those ids into an array (selectedTemplatesIds) in order to pass them to onOnboardingComplete (the button in TemplateList).
Here is the array I use in my LandingPage
templates = [
{
title: "Grocery List",
description: "Description of what this things does so the reader can have info of",
imgURL: "https://res.cloudinary.com/deruwllkv/image/upload/v1625753993/groceries.png",
id: 0,
},
{
title: "Shopping Space",
description: "blablabalblabla",
imgURL: "https://res.cloudinary.com/deruwllkv/image/upload/v1625753766/shopping.png",
id: 1,
},
{
title: "Travel Planning",
description: "blablabalblabla",
imgURL: "https://res.cloudinary.com/deruwllkv/image/upload/v1625753885/travel.png",
id: 2,
},
{
title: "Travel",
description: "blablabalblabla",
imgURL: "https://res.cloudinary.com/deruwllkv/image/upload/v1625753993/groceries.png",
id: 3,
},
];
My Templateslist component:
export type Template = {
title: string;
description: string;
imgURL: string;
id?: number;
};
type Props = {
templates: Template[];
onOnboardingComplete: Function;
};
const TemplateList = ({ templates, onOnboardingComplete }: Props) => {
const { aspectRatio, vmin } = useWindowResponsiveValues();
const [noOfSelectedCards, setNoOfSelectedCards] = useState(0);
const handleSelect = () => setNoOfSelectedCards(noOfSelectedCards + 1);
const handleDeselect = () => setNoOfSelectedCards(noOfSelectedCards - 1);
let buttonText;
if (noOfSelectedCards === 1) {
buttonText = "Select at least 1 more option";
} else if (noOfSelectedCards >= 2) {
buttonText = "Create my initial cuadds!";
} else {
buttonText = "Select at least 2 options";
}
return (
<>
<div className={styles.scrollContainer}>
{templates.map((item) => (
<TemplateCard
title={item.title}
description={item.description}
img={item.imgURL}
classNameToAdd={styles.cardContainer}
key={item.id}
onSelectCard={handleSelect}
onDeselectCard={handleDeselect}
/>
))}
</div>
<MenuButton
onClick={onOnboardingComplete}
style={actionButton}
className={
noOfSelectedCards >= 2 ? `${styles.actionBlue}` : `${styles.actionNormal}`
}
>
{buttonText}
</MenuButton>
</>
);
};
export default TemplateList;
And my TemplateCards:
type Props = {
title: string;
description: string;
img: string;
classNameToAdd?: string;
classNameOnSelected?: string;
onSelectCard: any;
onDeselectCard: any;
};
const TemplateCard = ({
title,
description,
img,
classNameToAdd,
classNameOnSelected,
onSelectCard,
onDeselectCard,
}: Props) => {
const { aspectRatio, vmin } = useWindowResponsiveValues();
let className = `${styles.card} ${classNameToAdd}`;
const [selected, setSelected] = useState(false);
const handleClick = () => {
setSelected(!selected);
if (selected) {
onDeselectCard();
} else {
onSelectCard();
}
};
if (selected) {
className += `${styles.card} ${classNameToAdd} ${classNameOnSelected}`;
}
return (
<div style={card} className={className} onClick={handleClick}>
<img style={imageSize} src={img}></img>
<div style={cardTitle}>
{title}
{selected ? <BlueCheckIcon style={blueCheck} className={styles.blueCheck} /> : null}
</div>
<div style={descriptionCard}>{description}</div>
</div>
);
};
TemplateCard.defaultProps = {
classNameOnSelected: styles.selected,
};
export default TemplateCard;
Add:
const selectedTemplatesIds = [];
Change handlers:
const handleSelect = (id) => {
selectedTemplatesIds.push(id);
setNoOfSelectedCards(noOfSelectedCards + 1);
};
const handleDeselect = (id) => {
selectedTemplatesIds.splice(selectedTemplatesIds.indexOf(id),1);
setNoOfSelectedCards(noOfSelectedCards - 1);
}
Change:
<TemplateCard
title={item.title}
description={item.description}
img={item.imgURL}
classNameToAdd={styles.cardContainer}
key={item.id}
onSelectCard={() => {
handleSelect(item.id)
}}
onDeselectCard={() => {
handleDeselect(item.id)
}}
/>
Rather than just storing the noOfSelectedCards. Just store the array of ids:
const [selectedIds, setSelectedIds] = useState([]);
Send in the id into the handleSelect function and push the id into the array.
<TemplateCard
title={item.title}
description={item.description}
img={item.imgURL}
classNameToAdd={styles.cardContainer}
key={item.id}
onSelectCard={() => handleSelect(item.id)}
onDeselectCard={handleDeselect}
/>
handleSelect(id) {
setSelectedIds(prev => [...prev, id])
}
And you can do a similar thing to remove the id when deselecting a card.

Material Table not updating table data after mutation

When a user adds additional information, a mutation is made to the database adding the new info, then the local state is updated, adding the new information to the lead.
My mutation and state seem to get updated fine, the issue seems to be that the state of the Material Table component does not match its 'data' prop. I can see in the React Dev tools that the state was updated in the parent component and is being passes down, the table just seems to be using stale data until I manually refresh the page.
I will attach images of the React Devtools as well as some code snippets. Any help would be much appreciated.
Devtools Material Table data prop:
Devtools Material Table State
Material Table Parent Component:
const Leads = () => {
const [leadState, setLeadState] = useState({});
const [userLeadsLoaded, setUserLeadsLoaded] = React.useState(false);
const [userLeads, setUserLeads] = React.useState([]);
const { isAuthenticated, user, loading } = useAuth()
const [
createLead,
{ data,
// loading: mutationLoading,
error: mutationError },
] = useMutation(GQL_MUTATION_CREATE_LEAD);
const params = { id: isAuthenticated ? user.id : null };
const {
loading: apolloLoading,
error: apolloError,
data: apolloData,
} = useQuery(GQL_QUERY_ALL_LEADS, {
variables: params,
});
useEffect(() => {
if (apolloData) {
if (!userLeadsLoaded) {
const { leads } = apolloData;
const editable = leads.map(o => ({ ...o }));
setUserLeads(editable);
setUserLeadsLoaded(true);
};
}
}, [apolloData])
if (apolloLoading) {
return (
<>
<CircularProgress variant="indeterminate" />
</>
);
};
if (apolloError) {
console.log(apolloError)
//TODO: Do something with the error, ie default user?
return (
<div>
<div>Oh no, there was a problem. Try refreshing the app.</div>
<pre>{apolloError.message}</pre>
</div>
);
};
return (
<>
<Layout leadState={leadState} setLeads={setUserLeads} leads={userLeads} setLeadState={setLeadState} createLead={createLead}>
{apolloLoading ? (<CircularProgress variant="indeterminate" />) : (<LeadsTable leads={userLeads} setLeads={setUserLeads} />)}
</Layout>
</>
)
}
export default Leads
Handle Submit function for adding additional information:
const handleSubmit = async (event) => {
event.preventDefault();
const updatedLead = {
id: leadState.id,
first_name: leadState.firstName,
last_name: leadState.lastName,
email_one: leadState.email,
address_one: leadState.addressOne,
address_two: leadState.addressTwo,
city: leadState.city,
state_abbr: leadState.state,
zip: leadState.zipCode,
phone_cell: leadState.phone,
suffix: suffix,
address_verified: true
}
const { data } = await updateLead({
variables: updatedLead,
refetchQueries: [{ query: GQL_QUERY_GET_USERS_LEADS, variables: { id: user.id } }]
})
const newLeads = updateIndexById(leads, data.updateLead)
console.log('New leads before setLeads: ', newLeads)
setLeads(newLeads)
// setSelectedRow(data.updateLead)
handleClose()
};
Material Table Component:
const columnDetails = [
{ title: 'First Name', field: 'first_name' },
{ title: 'Last Name', field: 'last_name' },
{ title: 'Phone Cell', field: 'phone_cell' },
{ title: 'Email', field: 'email_one' },
{ title: 'Stage', field: 'stage', lookup: { New: 'New', Working: 'Working', Converted: 'Converted' } },
{ title: 'Active', field: 'active', lookup: { Active: 'Active' } },
];
const LeadsTable = ({ leads, setLeads }) => {
const classes = useStyles();
const { user } = useAuth();
const [isLeadDrawerOpen, setIsLeadDrawerOpen] = React.useState(false);
const [selectedRow, setSelectedRow] = React.useState({});
const columns = React.useMemo(() => columnDetails);
const handleClose = () => {
setIsLeadDrawerOpen(!isLeadDrawerOpen);
}
console.log('All leads from leads table render: ', leads)
return (
<>
<MaterialTable
title='Leads'
columns={columns}
data={leads}
icons={tableIcons}
options={{
exportButton: false,
hover: true,
pageSize: 10,
pageSizeOptions: [10, 20, 30, 50, 100],
}}
onRowClick={(event, row) => {
console.log('Selected Row:', row)
setSelectedRow(row);
setIsLeadDrawerOpen(true);
}}
style={{
padding: 20,
}}
/>
<Drawer
variant="temporary"
open={isLeadDrawerOpen}
anchor="right"
onClose={handleClose}
className={classes.drawer}
>
<LeadDrawer onCancel={handleClose} lead={selectedRow} setLeads={setLeads} setSelectedRow={setSelectedRow} leads={leads} />
</Drawer>
</>
);
};
export default LeadsTable;
Try creating an object that contains refetchQueries and awaitRefetchQueries: true. Pass that object to useMutation hook as a 2nd parameter. See example below:
const [
createLead,
{ data,
loading: mutationLoading,
error: mutationError },
] = useMutation(GQL_MUTATION_CREATE_LEAD, {
refetchQueries: [{ query: GQL_QUERY_GET_USERS_LEADS, variables: { id: user.id } }],
awaitRefetchQueries: true,
});
Manually updating cache. Example blow is adding a new todo. In your case you can find and update the record before writing the query.
const updateCache = (cache, {data}) => {
// Fetch the todos from the cache
const existingTodos = cache.readQuery({
query: GET_MY_TODOS
});
// Add the new todo to the cache (or find and update an existing record here)
const newTodo = data.insert_todos.returning[0];
cache.writeQuery({
query: GET_MY_TODOS,
data: {todos: [newTodo, ...existingTodos.todos]}
});
};
const [addTodo] = useMutation(ADD_TODO, {update: updateCache});

onClick function not working using context-api

The handleDetail() function for clicking a product and pulling up the product detail page isn't working. My addToCart function is working properly so I don't know what I'm missing.
context.js:
state = {
products: [],
productDataDetail: productDataDetail,
};
getItem = (id) => {
const product = this.state.products.find((item) => item.id === id);
return product;
};
handleDetails = id => {
const product = this.getItem(id);
this.setState(() => {
return { productDataDetail: product };
});
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
handleDetails: this.handleDetails,
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
product.js:
<ProductConsumer>
{(value) => (
<Card className={classes.root}>
<CardActionArea onClick={() => value.handleDetails(id)}>
)}
</ProductConsumer>
Product Data:
export const productDataDetail = {
id: 0,
name: "Desk",
img: desk,
store: "Local Furniture Shop 1",
price: 9.99,
desc:
"This sturdy desk is built to outlast years of coffee and hard work. You get a generous work surface and a clever solution to keep cords in place underneath.",
inCart: false,
count: 0,
total: 0,
};
bind it:
value={{
...this.state,
handleDetails: this.handleDetails.bind(this)
}}
Otherwise, when you call value.handleDetails, the function is bound to a different context (value) than the one you intended (the component's context)

Categories