React context not updating on certain places - javascript

On button click I'am adding product information to local storage and setting it to context.
I'm using context in two places - header and products page, syntax is almost the same. There is a console.log in header and whenever i'am interacting with context it's instantly firing, confirming that context is working fine. However, when i enter products page i need to reload it one more time in order to get the data. Why is that?
Context file
import React, { useState, useEffect } from "react";
export const LikedContext = React.createContext([{}, () => {}]);
export const LikedProvider = (props) => {
const [liked, setLiked] = useState(null);
useEffect(() => {
if (typeof window !== "undefined") {
let likedData = localStorage.getItem("liked");
likedData = likedData ? JSON.parse(likedData) : "";
setLiked(likedData);
}
}, []);
return (
<LikedContext.Provider value={[liked, setLiked]}>
{props.children}
</LikedContext.Provider>
);
};
Header
export const LikedProductsIcon = () => {
const [liked, setLiked] = useContext(LikedContext);
console.log("liked header", liked);
const likedCount =
null !== liked && Object.keys(liked).length ? liked.totalProductsCount : "";
const totalPrice =
null !== liked && Object.keys(liked).length ? liked.totalProductsPrice : "";
return (
<>
<CartContainer to="/pamegti-produktai">
<CountCircle>
<HeartSvg />
{likedCount ? <span>{likedCount}</span> : ""}
</CountCircle>
</CartContainer>
</>
);
};
Liked products page
const LikedProducts = () => {
const [liked, setLiked] = useContext(LikedContext);
console.log("liked page", liked);
return (
<Layout>
<Container>
{liked ? (
liked.products.map((product, index) => (
<ProductsCard product={product} blackText />
))
) : (
<div>Pamėgtų produktų nėra.</div>
)}
</Container>
</Layout>
);
};

If you render multiple <LikedProviders>, then they will each have their own independent state. Setting state in one of them will not affect the other one. The reason they sync up when you refresh the page is that they both load from local storage and see the same value. But that only happens on load, and they will get out of sync after that.
If you want the state to be shared, then you want to just render one of them. Place it at the top of your component tree (app.js is a good spot) so that it's accessible by all the components that need it, and delete the other one.

Related

React array.length returning 0 even though array has items

I'm pulling stuff from my database using Firestore. When I log inside the function that pulls the data, the array has the data. When I log in my main component, it also has. But for some reason, .map doesn't work, and when I try array.length it returns 0. I was using a map, but then I changed it to use a function to try to get the error.
export default function Search() {
const [searchedData, setSearchedData] = useState([]);
const [loading, setLoading] = useState(true);
const [noBook, setNoBook] = useState(false);
const [showBooks, setShowBooks] = useState(false);
const link = useLocation();
useEffect(() => {
const srch = link.pathname.substring(8);
loadSearchBooks(srch);
}, [link]);
async function loadSearchBooks(srch) {
try {
const bookArray = await getSearchedBooks(srch);
bookArray ? setShowBooks(true) : setNoBook(true);
setSearchedData(bookArray);
} catch (e) {
setSearchedData(null);
} finally {
setLoading(false);
}
}
function renderBooks() {
console.log(searchedData);
const l = searchedData.length;
return l;
}
return (
<div>
<Navbar />
<div className={searchBookWrapper}>
{loading && 'Carregando'}
{showBooks && renderBooks()}
{noBook && <BookCardItem />}
</div>
</div>
);
}
When doing this, console.log(searchedData) returns the array, but const l = searchedData.length shows just a 0. When I search again, the number changes to 12 for a moment right when it's about to change. This is the previous code:
return (
<div>
<Navbar />
<div className={searchBookWrapper}>
{loading && 'Carregando'}
{showBooks &&
searchedData.map(({ afn, aln, notes, quant, title }, index) => {
return (
<BookCardItem
key={title}
firstName={afn}
lastName={aln}
notes={notes}
quant={quant}
title={title}
bookNumber={index}
/>
);
})}
{noBook && <BookCardItem />}
</div>
</div>
);
}
The same thing happened. The bookInfo appeared just for a moment when I searched again.
From the first code in this question, this is the console:
Console - one empty array, then two filled ones
if useLocation is an API call or other async function, react prefers those to be in a useEffect. If they are not, it can give inconsistent results. Try putting useLocation inside a useEffect. If you only plan on useLocation firing once (and thus loadSearchedBooks only firing once) you can even put them in the same useEffect, just don't make them rerender based on the thing they update.
useEffect(() => {
useLocation().then(link => {
const srch = link.pathname.substring(8);
loadSearchBooks(srch);
}
}, []);
Hopefully, this will fix your problem.

Reactj.s: Item deleted only after refresh | Delete Method

I'm trying to send a delete request to delete an item from an API.
The API request is fine when clicking on the button. But Item get's deleted only after refreshing the browser!
I'm not too sure if I should add any parameter to SetHamsterDeleted for it to work?
This is what my code looks like.
import React, {useState} from "react";
const Hamster = (props) => {
const [hamsterDeleted, setHamsterDeleted] = useState("")
async function deleteHamster(id) {
const response = await fetch(`/hamsters/${id}`, { method: "DELETE" });
setHamsterDeleted()
}
return (
<div>
<p className={props.hamster ? "" : "hide"}>
{hamsterDeleted}
</p>
<button onClick={() => deleteHamster(props.hamster.id)}>Delete</button>
<h2>{props.hamster.name}</h2>
<p>Ålder:{props.hamster.age}</p>
<p>Favorit mat:{props.hamster.favFood}</p>
<p>Matcher:{props.hamster.games}</p>
<img src={'./img/' + props.hamster.imgName} alt="hamster"/>
</div>
)
};
export default Hamster;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Imagine you have a parent component (say HamstersList) that returns/renders list of these Hamster components - it would be preferable to declare that deleteHamster method in it, so it could either: a) pass some prop like hidden into every Hamster or b) refetch list of all Hamsters from the API after one got "deleted" c) remove "deleted" hamster from an array that was stored locally in that parent List component.
But since you are trying to archive this inside of Hamster itself, few changes might help you:
change state line to const [hamsterDeleted, setHamsterDeleted] = useState(false)
call setHamsterDeleted(true) inside of deleteHamster method after awaited fetch.
a small tweak of "conditional rendering" inside of return, to actually render nothing when current Hamster has hamsterDeleted set to true:
return hamsterDeleted ? null : (<div>*all your hamster's content here*</div>)
What do you want to do in the case the hamster is deleted? If you don't want to return anything, you can just return null.
I'm not too sure if I should add any parameter to SetHamsterDeleted for it to work?
Yes, I'd make this a boolean instead. Here's an example:
import React, { useState } from "react";
const Hamster = (props) => {
const [hamsterDeleted, setHamsterDeleted] = useState(false);
async function deleteHamster(id) {
const response = await fetch(`/hamsters/${id}`, { method: "DELETE" });
setHamsterDeleted(true);
}
if (hamsterDeleted) return null;
return (
<div>
<p className={props.hamster ? "" : "hide"}>
{hamsterDeleted}
</p>
<button onClick={() => deleteHamster(props.hamster.id)}>Delete</button>
<h2>{props.hamster.name}</h2>
<p>Ålder:{props.hamster.age}</p>
<p>Favorit mat:{props.hamster.favFood}</p>
<p>Matcher:{props.hamster.games}</p>
<img src={'./img/' + props.hamster.imgName} alt="hamster"/>
</div>
);
};
HOWEVER! Having each individual hamster keep track of its deleted state doesn't sound right (of course I don't know all your requirements but it seems odd). I'm guessing that you've got a parent component which is fetching all the hamsters - that would be a better place to keep track of what has been deleted, and what hasn't. That way, if the hamster is deleted, you could just not render that hamster. Something more like this:
const Hamsters = () => {
const [hamsers, setHamsters] = useState([]);
// Load the hamsters when the component loads
useEffect(() => {
const loadHamsters = async () => {
const { data } = await fetch(`/hamsters`, { method: "GET" });
setHamsters(data);
}
loadHamsters();
}, []);
// Shared handler to delete a hamster
const handleDelete = async (id) => {
await fetch(`/hamsters/${id}`, { method: "DELETE" });
setHamsters(prev => prev.filter(h => h.id !== id));
}
return (
<>
{hamsters.map(hamster => (
<Hamster
key={hamster.id}
hamster={hamster}
onDelete={handleDelete}
/>
))}
</>
);
}
Now you can just make the Hamster component a presentational component that only cares about rendering a hamster, eg:
const Hamster = ({ hamster, onDelete }) => {
const handleDelete = () => onDelete(hamster.id);
return (
<div>
<button onClick={handleDelete}>Delete</button>
<h2>{hamster.name}</h2>
<p>Ålder:{hamster.age}</p>
<p>Favorit mat:{hamster.favFood}</p>
<p>Matcher:{hamster.games}</p>
<img src={'./img/' + hamster.imgName} alt="hamster"/>
</div>
);
};

Way to render a new component onClick in react js

Am trying to render a new component onclick a button in react js. Am using functional components and I can't handle it. Eg: am in the UserManagement component and on a button click I need to render another component named employee management.
You can conditionally render your component.
Example :
EmployeeManagement.js
const EmployeeManagement = () => {
....
return (
<div>
EmployeeManagement
</div>
);
}
UserManagement.js
const UserManagement = () => {
const [hasRender, setRender] = useState(false);
const onShow = React.useCallback(() => setRender(true), []);
return (
<>
<button onClick={onShow}>Show Employee Management</button>
{hasRender && <EmployeeManagement />}
</>
)
}
One way to do this would be to add a local state in UserManagement,
that holds a boolean value indication whether the component should be hidden or shown.
Then you will have something like:
function UserManagement() {
const [compIsShown, setCompIsShown] = useState(false);
return (
// Whatever else you're rendering.
<button onClick={() => setCompIsShown(true)}>...</button>
{compIsShown && <OtherComp />}
)
}
What will happen is that compIsShown will initialize as false,
so this condition compIsShown && <OtherComp /> will prevent it from rendering.
Then, when you click the button, the state will set, causing a re-render, except now the condition will be true, so <OtherComp> will be rendered.
There are other ways to go about this.
Depends mostly on the use-case.
use a visible state & toggle it in onClick:
const [visible, setVisible] = useState(false)
onClick = () => {setVisible(true)}
then render it like this:
{visible && <EmployeeManagement onClick={onClick} />}

How to check if the element is present using react?

How can I check if an element is present in the DOM or not, using React?
I have a popup that is displayed throughout the application when items are 0 and the user clicks a button. It's created by a context provider that is wrapped around the App component.
There is an add button that gets displayed in some page "/items".
const root = () => {
<PopupContextProvider>
<App/>
</PopupContextProvider>
}
export const PopupContextProvider = ({ children }: any) => {
return (
<popupContext.Provider value={context}>
{children}
{(condition1 || condition2) && (
<Popup onHide={dismiss} />
)}
</popupContext.Provider>
);
}
function App() {
return (
<Route path="/items">
<Drawer/>
/>
//other routes
);
}
function Drawer() {
return (
<ButtonElement/> //this is a styled div component and i want to check if this element is
//present in dom at the sametime when popup is there in dom
);
}
What I want to do?
I want to check if the ButtonElement is there in the DOM at the same time as the popup.
The ways that I have thought:
add an id to button element and check if it is present using document.getelementbyid (last option for me)
using ref, but I'm not sure how to do it
I want to use a ref to button element, but I don't know how to pass it to context.
What would be the best way to do this?
Use the useRef hook and pass it down to the child component. It'll be undefined
Assuming you're defining const popupContext = React.createContext(undefined); somewhere, roughly this should work:
const PopupContextProvider = ({ children }: any) => {
const popupRef = useRef(null);
return (
<popupContext.Provider value={popupRef}>
{children}
{(condition1 || condition2) && (
<Popup onHide={dismiss} ref={popupRef}/>
)}
</popupContext.Provider>
);
}
const Drawer = () => {
return (
<popupContext.Consumer>
{value => (value !== undefined)
? <ButtonElement popup={true}/>
: <ButtonElement/>
}
</popupContext.Consumer>
);
}
More info about context usage here: https://reactjs.org/docs/context.html#reactcreatecontext

How do you push a UI update to a javascript frontend?

So I'm building a simple react app that fetches a bunch of images and displays them as cards.
The intention is to show an info message until all the images have loaded, then removing the notice again.
const App = () => {
const [cardInfo, setCardInfo] = useContext(CardInfoContext)
useEffect(() => {
fetchData(setCardInfo)
}, [])
useEffect(() => {
const app = document.querySelector('.app')
for(const child of app.children){
app.removeChild(child)
}
const loadingNotice = document.createElement('h1')
loadingNotice.innerHTML = "Fetching data ..."
app.appendChild(loadingNotice) //<-- this never shows up
cardInfo.forEach( info => {
const img = document.createElement('img')
img.src = info.image
app.appendChild(img)
})
app.removeChild(loadingNotice)
}, [cardInfo])
return (
<>
<div className="app">
<h1>Fetching data...</h1>
</div>
</>
)};
What instead happens is the app stays blank until all the images are loaded, then shows all the images at once -- but never the loading notice.
Can I somehow "push" the loading indicator change to the UI independent of the rest of the rendering?
Another thing I tried was
const App = () => {
const [cardInfo, setCardInfo] = useContext(CardInfoContext)
useEffect(() => {
fetchData(setCardInfo)
}, [])
useEffect(() => {
const app = document.querySelector('.app')
if(!cardInfo) return
const loadingNotice = app.querySelector(".loadingNotice")
loadingNotice.style.display = 'block' //<-- this never shows up
cardInfo.forEach( info => {
const img = document.createElement('img')
img.src = info.image
app.appendChild(img)
})
loadingNotice.style.display = 'none'
}, [cardInfo])
return (
<>
<div className="app">
<h1 className="loadingNotice">Fetching data...</h1>
</div>
</>
)}
Which would be incorrect because I do need to remove all the images, at least, but even that only displayed the loading notice for a fraction of a second, then the component goes blank until all the images can be displayed.
useEffect observes when cardInfo is changed, not when the render the comes after fired. You can use useLayoutEffect instead.
...but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
BTW, I wouldn't combine direct DOM manipulation with React to avoid issues like this (among other reasons)
Something like
const App = () => {
const [isLoadgin, setIsLoading] = useState(true)
const [cardInfo, setCardInfo] = useContext(CardInfoContext)
useEffect(() => {
fetchData(result => {
setCardInfo(result);
setIsLoading(false);
})
}, [])
return (
<>
<div className="app">
{isLoading && <h1 className="loadingNotice">Fetching data...</h1>}
{
cardInfo.map(card => <img src={card.image} />)
}
</div>
</>
)}
You need conditional rendering instead of all that DOM manipulation you are trying in the useEffect.
...
return(
<>
<div className="app">
{ !cardInfo ? <h1 className="loadingNotice">Fetching data...</h1> : <Cards info={cardInfo} /> }
</div>
</>
)
Note: I am assuming you have something like <Cards> component that displays cards details.

Categories