I have a React Native Component which takes an image URI as a prop. I'm passing the prop from another js file:
<SlideComponent
imageUri={listOfAssets[counter].uri}
/>
As you can see, the passing of the image URI is dynamic (maybe it's not the smartest solution, but it works kinda). The problem is that when the counter goes up and should technically provide a new image URI, the image won't update within the "SlideComponent".
This is how I update the counter variable:
const keepImage = async () => {
counter = counter + 1;
};
Is there a way to rerender the Component everytime the counter goes up?
As #Robin Zigmond and #PhantomSpooks pointed out, I changed
const keepImage = async () => {
counter = counter + 1;
};
to
const keepImage = async () => {
setCounter(counter + 1);
};
and now it works like a charm :)
Related
Problem Summary
I am working on a React app with Node.js, Express.js, MogoDB backend.
I have a progress bar component. The number (i.e. percentage) that I pass to the progress bar comes from a function. This function calculates the percentage of the progress bar and then updates the state variable "progress".
The problem is that I seem to have an infinite re-rendering error. However, I cannot tell where it originates from and how to approach it.
Problem Description
Progress Component
Consider the following react component. This component is a progress bar. The component takes a float done and then shows a progress bar with a bar that takes up done% of the progress bar space
const Progress = ({done}) => {
return (
<div class="progress">
<div class="progress-done"
style={{
opacity: 1,
width: `${done}%`
}}>
{done}%
</div>
</div>
)
}
Use of Progress Bar in App.js
I attempted to implement the progress bar like below:
const App = () => {
const [progress, setProgress] = useState(0)
const [groupOfTasks, setGroupOfTasks] = useState([])
// assume that there’s code here that sets groupOfTasks using data from a database
const countDone = (groupOfTasks) => {
var numOfTasks = groupOfTasks.length
var numOfTasksSatisfied = 0
groupOfTasks.map((task, index) => {
if(task.completed == True)
{
numOfTasksSatisfied++
}
}
setProgress(numOfTasksSatisfied/numOfTasks)
}
return (
{countDone(groupOfTakss))
<Progress done={progress}/>
)
}
but I got this error:
How would you recommend I approach this problem? Any help is very appreciated
It's because you are using the countDone inside the return (where the visual render should be done)
You should use the useEffect hook, with the groupOfTasks as dependency.
So whenever the groupOfTasks is updated, you can have the progress updated at the same time
Just after you countDone method
useEffect(()=>{
countDone(groupOfTasks)
},[groupOfTasks])
By the way if you want to understand a bit more about the useEffect here a great article !
The error comes due to the line {countDone(groupOfTrasks)} inside the return statement. This is what happens:
You -> Render App -> Calculate count -> The countDone function sets a state, so it will rebuild the App -> Render App -> loop...
You can come over this problem by using the hook useEffect. The hook works like this: it takes a block of code and then an array.
If the array is empty -> the code will be executed only once when component is rendered.
If the array has variables -> the code will be executed every time one of those variables changes.
This is called dependencies array. In your case, set groupOfTasks inside the array, and it means: "Every time groupOfTasks changes, execute this code".
The function will be:
const App = () => {
const [progress, setProgress] = useState(0)
const [groupOfTasks, setGroupOfTasks] = useState([])
// assume that there’s code here that sets groupOfTasks using data from a database
const countDone = (groupOfTasks) => {
var numOfTasks = groupOfTasks.length
var numOfTasksSatisfied = 0
groupOfTasks.map((task, index) => {
if(task.completed == True)
{
numOfTasksSatisfied++
}
}
setProgress(numOfTasksSatisfied/numOfTasks)
}
useEffect(()=>{
countDone(groupOfTasks)
},[groupOfTasks])
return (
<Progress done={progress}/>
)
}
I'm trying to make a page that gets picture from a server and once all pictures are downloaded display them, but for some reason the page doesn't re-render when I update the state.
I've seen the other answers to this question that you have to pass a fresh array to the setImages function and not an updated version of the previous array, I'm doing that but it still doesn't work.
(the interesting thing is that if I put a console.log in an useEffect it does log the text when the array is re-rendered, but the page does not show the updated information)
If anyone can help out would be greatly appreciated!
Here is my code.
export function Profile() {
const user = JSON.parse(window.localStorage.getItem("user"));
const [imgs, setImages] = useState([]);
const [num, setNum] = useState(0);
const [finish, setFinish] = useState(false);
const getImages = async () => {
if (finish) return;
let imgarr = [];
let temp = num;
let filename = "";
let local = false;
while(temp < num+30) {
fetch("/get-my-images?id=" + user.id + "&logged=" + user.loggonToken + "&num=" + temp)
.then(response => {
if(response.status !== 200) {
setFinish(true);
temp = num+30;
local = true;
}
filename = response.headers.get("File-Name");
return response.blob()
})
.then(function(imageBlob) {
if(local) return;
const imageObjectURL = URL.createObjectURL(imageBlob);
imgarr[temp - num] = <img name={filename} alt="shot" className="img" src={imageObjectURL} key={temp} />
temp++;
});
}
setNum(temp)
setImages(prev => [...prev, ...imgarr]);
}
async function handleClick() {
await getImages();
}
return (
<div>
<div className="img-container">
{imgs.map(i => {
return (
i.props.name && <div className="img-card">
<div className="img-tag-container" onClick={(e) => handleView(i.props.name)}>{i}</div>
<div className="img-info">
<h3 className="title" onClick={() => handleView(i.props.name)}>{i.props.name.substr(i.props.name.lastIndexOf("\\")+1)}<span>{i.props.isFlagged ? "Flagged" : ""}</span></h3>
</div>
</div>
)
})}
</div>
<div className="btn-container"><button className="load-btn" disabled={finish} onClick={handleClick}>{imgs.length === 0 ? "Load Images" : "Load More"}</button></div>
</div>
)
}
I think your method of creating the new array is correct. You are passing an updater callback to the useState() updater function which returns a concatenation of the previous images and the new images, which should return a fresh array.
When using collection-based state variables, I highly recommend setting the key property of rendered children. Have you tried assigning a unique key to <div className="img-card">?. It appears that i.props.name is unique enough to work as a key.
Keys are how React associates individual items in a collection to their corresponding rendered DOM elements. They are especially important if you modify that collection. Whenever there's an issue with rendering collections, I always make sure the keys are valid and unique. Even if adding a key doesn't fix your issue, I would still highly recommend keeping it for performance reasons.
It is related to Array characteristics of javascript.
And the reason of the console log is related with console log print moment.
So it should be shown later updated for you.
There are several approaches.
const getImages = async () => {
... ...
setNum(temp)
const newImage = [...prev, ...imgarr];
setImages(prev => newImage);
}
const getImages = async () => {
... ...
setNum(temp)
setImages(prev => JOSN.parse(JSON.object([...prev, ...imgarr]);
}
const getImages = async () => {
... ...
setNum(temp)
setImages(prev => [...prev, ...imgarr].slice(0));
}
Maybe it could work.
Hope it will be helpful for you.
Ok the problem for me was the server was not sending a proper filename header so it was always null so the condition i.props.name was never true... lol sorry for the confusion.
So the moral of this story is, always make sure that it's not something else in your code that causes the bad behavior before starting to look for other solutions...
I've gone through multiple useRef/useEffect instructions but I just can't seem to make it work here.
The code workflow here is: Remix/React, get data from database, display data, turn data into a ticker that can be updated
If anyone could point out any glaring errors they see in this code as to why the useEffect hook isn't firing, or why the useRef hook can never find the {listRef} within the <ul>, I would love to know.
import { Links, redirect, useLoaderData, Outlet } from 'remix'
import { db } from '~/utils/db.server'
import { getUser } from '~/utils/session.server'
import { ReactSortable } from "react-sortablejs"
import { useState, useRef, useEffect } from 'react'
import tickerStylesUrl from '~/styles/tickerDisplay.css'
export const links = () => [{ rel: 'stylesheet', href: tickerStylesUrl }]
export const loader = async ({ request, params }) => {
const user = await getUser(request)
const ticker = await db.ticker.findUnique({
where: { id: params.tickerId },
include: {
headlines: true,
},
})
if (!ticker) throw new Error('Ticker not found')
const data = { ticker, user }
return data
}
export const action = async ({ request, params }) => {
}
// The ticker function displays the items without styling, so it finds the database perfectly and can get the data
function displayTicker() {
const { ticker, user } = useLoaderData()
const headlines = ticker.headlines
const tickerParentStyle = {
width: "1920px",
height: "1080px",
position: "relative",
backgroundColor: "black"
}
const tickerStyle = {
position: "absolute",
padding: "0",
bottom: "0",
color: `${ticker.fontColor}`,
backgroundColor: `${ticker.backgroundColor}`,
fontFamily: `${ticker.font}`,
fontSize: "2em",
}
const tickerHeadlineStyle = {
margin: "auto",
height: "50%",
}
console.log("Headlines: " + headlines)
// So begins the found ticker code I had hoped to integrate
// Source: https://www.w3docs.com/tools/code-editor/2123
function scrollTicker() {
const marquee = listRef.current.querySelectorAll('.tickerHeadlines');
let speed = 4;
let lastScrollPos = 0;
let timer;
marquee.forEach(function (el) {
const container = el.querySelector('.headlineItem');
const content = el.querySelector('.headlineItem > *');
//Get total width
const elWidth = content.offsetWidth;
//Duplicate content
let clone = content.cloneNode(true);
container.appendChild(clone);
let progress = 1;
function loop() {
progress = progress - speed;
if (progress <= elWidth * -1) {
progress = 0;
}
container.style.transform = 'translateX(' + progress + 'px)';
container.style.transform += 'skewX(' + speed * 0.4 + 'deg)';
window.requestAnimationFrame(loop);
}
loop();
});
window.addEventListener('scroll', function () {
const maxScrollValue = 12;
const newScrollPos = window.scrollY;
let scrollValue = newScrollPos - lastScrollPos;
if (scrollValue > maxScrollValue) scrollValue = maxScrollValue;
else if (scrollValue < -maxScrollValue) scrollValue = -maxScrollValue;
speed = scrollValue;
clearTimeout(timer);
timer = setTimeout(handleSpeedClear, 10);
});
function handleSpeedClear() {
speed = 4;
}
}
const listRef = useRef()
console.log("listRef: " + JSON.stringify(listRef))
// This console appears everytime, but is always empty, presumably because DOM has just rendered
useEffect(() => {
console.log("useEffect fired")
// This console NEVER fires, sadly. I thought this would happen ONCE rendered
}, [listRef]);
return (
<>
<Links />
<div style={tickerParentStyle}>
<div style={tickerStyle}>
<div key={ticker.id} style={tickerHeadlineStyle} class="tickerWrapper">
// HERE IS THE TARGET UL
<ul className="tickerHeadlines" ref={listRef} style={{ margin: "10px 0 10px 0" }} >
{/* Hoping to map through the ticker items here, and have them displayed in a list, which would then be manipulated by the useRef/useEffect hook */}
{headlines.map((headline) => (
<>
<li class="headlineItem" key={headline.id}>
<span>
{headline.content} {ticker.seperator}
</span>
</li>
</>
))}
{scrollTicker()}
</ul>
</div>
</div>
</div>
</>
)
}
export default displayTicker
As always, any help is appreciated.
useRef is a hook that is used to access DOM elements, manipulating the DOM directly in a React application breaks the whole point of declarative programming. It is not at all advised to manipulate DOM directly using any dom objects and methods such as document. Coming to the useEffect hook, the useEffect hook runs conditionally depending on what's supplied in the dependency array, if none, the hook runs only once after the component finishes mounting. So you should be careful regarding what needs to be passed to the useEffect dependency array. Considering your case, when you pass listRef, the useEffect runs only when there is a change in the object and not it's properties, because objects are non-primitive, any changes in the property is not treated as a change in the object, and its merely an object property mutation that doesn't cause re-render. To steer clear, you should be sure of, when exactly you want it to be invoked, as you mentioned, you'd want it to run right after the data has rendered, you could instead use headlines in your dependency array.
Change the dependency array to include headlines.
useEffect(() => {
console.log("useEffect fired")
// This console NEVER fires, sadly. I thought this would happen ONCE rendered
}, [headlines]);
Alternatively, you could also leave it empty, making it run only once after the component has mounted.
useEffect(() => {
console.log("useEffect fired")
// This console NEVER fires, sadly. I thought this would happen ONCE rendered
}, []);
A caveat, the former snippet would run every time there's a change in headlines, and the latter would run only once no matter what changes.
So, depending on your use case, you might want to choose the one that best suits your needs.
There are a couple of things to code make code better:
initiate ref with 'null' value
call your 'scrollTicker' function inside useEffect Hook.
always remove listeners when component demount. Follow https://reactjs.org/docs/hooks-reference.html#useeffect for more details
you can use useEffect hook like this:
useEffect(() => {
// use your ref here.
return () => {
// Remove linteners
};
});
Trying to create an auto-clicker/idle game. So far the entire application works except for this loop. After the loop begins, if I update the counter, different values update in intervals. So my counter will display those different values, going back and forth between them depending on how many times I've tried to mess with the counter while its looping.
I've tried using while loops, if statements, and for loops. And for each of those loops I've tried both setInterval() and setTimeout(). They either lead to the problem above, or the browser crashing.
Here's a video of the issue:
Youtube Link
Here's the relevant code I've got currently:
const Counter = () => {
const [counter, setCounter] = useState(1);
const [minions, setMinions] = useState(0);
let minionCost = minions * 10 + 6;
let autoMinions = () => {
if (minions > 0) {
setTimeout(() => {
setCounter(minions + counter);
}, 1000);
} else {
return null;
}
};
const onClickMinion = () => {
if (counter < minionCost) {
console.log(`you don't have ${minionCost} to spend`);
} else {
setCounter(counter - minionCost);
setMinions(minions + 1);
}
};
autoMinions();
};
If you're computing state based off of a previous state, you should use functional updates.
Try passing setCounter a function that receives the previous state instead of using counter directly (do this with any of your useState hooks that depend on previous state):
setCounter(prevCounter => prevCounter + minions)
So I'm currently trying to learn react and as practice I was just trying to build a hacker news site using the hacker new API. But, I ran into a issue. For some reason it is currently infinitely looping. I debugged it and found that it has something to do with the useEffect() hook. I tried the solution in this post but it didn't seam to work(I think I probably did it wrong though).
My Code:
const [maindata, setmaindata] = useState("");
useEffect(() => {
axios
.get("https://hacker-news.firebaseio.com/v0/user/jl.json?print=pretty")
.then((repo) => {
const output = [];
// repo.data["submitted"].length
for (let x = 0; x < 30; x++) {
axios
.get(
"https://hacker-news.firebaseio.com/v0/item/" +
repo.data["submitted"][x] +
".json?print=pretty"
)
.then((titledata) => {
//console.log(titledata.data["text"]);
output.push(titledata.data["text"]);
});
}
});
setmaindata(output);
});
I also tried replacing:
}
});
setmaindata(output);
});
With:
}
});
}, [output});
But that didn't seem to work
If you don't pass the dependency array to useEffect, useEffect runs on every render.
By pass empty array([]) as dependency, useEffect runs once when component is mounted.
Ex:
useEffect(() => {
... // your code
}, [])
You should add dependency to useEffect because if you don't add any dependency, this method infinitely runs.
Just implement at the end of method [].
In addition, take care with setMainData because you call it outside of axis request.
Final code could be this:
const [maindata, setmaindata] = useState("");
useEffect(() => {
axios
.get("https://hacker-news.firebaseio.com/v0/user/jl.json?print=pretty")
.then((repo) => {
const output = [];
// repo.data["submitted"].length
for (let x = 0; x < 30; x++) {
axios
.get(
"https://hacker-news.firebaseio.com/v0/item/" +
repo.data["submitted"][x] +
".json?print=pretty"
)
.then((titledata) => {
//console.log(titledata.data["text"]);
output.push(titledata.data["text"]);
// here you have output array with the push action that you did in previous line
setmaindata(output);
});
}
});
}, [dependency]);
Change dependency with your variable that you want to use when this value changes this useEffect will be called