I'm trying to calculate the duration (difference between two moments) for example currently I'm working on a react native project in which I have to calculate the time spent on a screen/card.
So right now I'm trying to calculate time spent like this:
import moment from 'moment';
// lets say i have 3 states to note time
const [startTime, setStartTime] = useState(moment());
const [endTime, setEndTime] = useState(moment());
const [elapsedTime, setElapsedTime] = useState(Number);
// assume i have a bottomSheet, so whenever that bottom sheet goes up the timer starts and when it goes down to original position, timer stops and calculate difference/duration in minutes.
// so for the onOpenStart on onCloseEnd props of the bottomsheet, this is my function:
const start = () => {
setStartTime(moment());
}
const end = () => {
setEndTime(moment());
const et = moment.duration(endTime.diff(startTime)).asSeconds();
setElapsedTime(et);
console.log(elapsedTime) // if i try for 10 seconds it logs 34, or sometimes -6, it's just not right
}
I tried to do with new Date method and if i try for 10 seconds it logs 34, or sometimes -6, it's just not right but could not succeed...
Any help would be appreciated.
In your particular case, I don't think you even need moment library. It's super heavy in the bundle and it's just not needed.
You don't need to initialise the state with the dates. You don't care about them when the component just mounts, do you? You want to keep track of the dates when your functions start and end get triggered.
The current behaviour is that way because useState is async. That means that you're trying to to set the state and then use this value in the next line for some calculation - that just won't work.
What you can do is to use useRef instead of useState. This way you will make sure that the value that you have is the freshest one across your component.
const startTime = useRef(new Date());
const endTime = useRef(new Date());
const [elapsedTime, setElapsedTime] = useState();
const start = () => {
startTime.current = new Date();
}
const end = () => {
endTime.current = new Date();
const et = (startTime.current - endTime.current) / 1000 // get the seconds
setElapsedTime(et);
console.log(et); // You cannot log elapsedTime here, because again, useState is async
}
I don't know what you need to do with the elapsed time, you might not need the state either. For now I've left it as state.
Last thing: I really recommend looking at day.js library, as it does the same things (in 99% of cases) as moment, but is much much lighter.
useState does not update synchronously. This means that although you setEndTime and then try to read it, the chances are it has not been updated yet.
Also, setting state causes a re-render. This is unnessasary when measuring performance. You can instead use useRef to hold the timing value in a reference variable.
export default function App() {
const [visible, setVisible] = React.useState(false);
const timer = React.useRef(null)
return (
<View style={styles.container}>
<Pressable
onPress={() => {
timer.current = new Date().getTime()
setVisible(true);
}}>
<Text>Show modal</Text>
</Pressable>
<Pressable
onPress={() => {
setVisible(false);
alert(new Date().getTime() - timer.current)
}}>
<Text>Hide modal</Text>
</Pressable>
{visible ? (
<Card>
<Text>Modal content</Text>
</Card>
) : null}
</View>
);
}
Here, we are setting the visible value and storing timestamp on press. And when the user presses hide, we use the stored time to calculate the time elapsed.
Related
I'm creating a DoS-like typing effect dialogue for my website. I've got the effect running but it seems to skip the second render of the loop.
I've got an effect hook that adds each letter from a string to a new state via an index ref until the index ref equals the length of the string. Here's that (and a stackblitz of it);
const startingLine = `Lorem ipsum.`;
const [screenResponse, setScreenResponse] = useState('');
const [skipDialogue, setSkipDialogue] = useState(false);
let index = useRef(0);
useEffect(() => {
let addChar;
function tick() {
setScreenResponse((prev) => prev + startingLine[index.current]);
index.current++;
console.log(screenResponse);
}
if (index.current < startingLine.length - 1 && !skipDialogue) {
addChar = setInterval(tick, 500);
return () => clearInterval(addChar);
} else {
setScreenResponse(startingLine);
}
}, [screenResponse, skipDialogue, startingLine]);
The screenResponse is what shows on screen, except the 'o' from 'Lorem' would be missing until the effect is finished (and is still missing without the else line).
I've taken it out of React.StrictMode, and it happens in all string lengths except one, and happens no matter how long the interval takes.
I need the startingLine.length - 1 so that it doesn't drop an undefined at the end, but removing that doesn't change the missing second render.
With the console.log in the tick interval, it doesn't print 'Lo' just straight to 'Lr'. So, why is the second render being skipped and how can I fix it?
working code : https://stackblitz.com/edit/react-jed2pz?file=src/App.js
try using useState instead of ref
this is from react docs
more info about useRef :https://reactjs.org/docs/hooks-reference.html#useref
Change this lines in tick():
const char = startingLine.charAt(index.current);
setScreenResponse(prev => `${prev}${char}`);
New to React.
I am trying to create a set of 12 range sliders, in two columns of six. Each column is declared separately and each column independently declares the instance of the slider. Each range slider uses the package react-slider and has a currentValue state that is passed down as a prop along with the setCurrentValue method as the setState.
My issue is that when I display the values below each slider, they all sync up together, ie. they appear to all use the same variable. How do I get React to differentiate between each 'instance' as such? SliderValueText just returns an element with the value displayed.
The production value is a boolean that just tweaks the title slightly.
The Slider element:
// imports here
export const Slider = (props) => {
const { currentValue, setCurrentValue, title, production } = props
return (
<>
<ReactSlider
className='customSlider'
key={title+production}
thumbClassName='customSlider-thumb'
trackClassName='customSlider-track'
markClassName='customSlider-mark'
min={0}
max={100}
defaultValue={0}
value={currentValue}
onChange={(value) => setCurrentValue(value)}
/>
<br/>
<SliderValueText currentValue={currentValue} />
</>
)
}
The SliderSet element:
// imports
export const SliderSet = (props) => {
const { currentValue, setCurrentValue, production } = props
return (
<>
<Slider
currentValue={currentValue}
setCurrentValue={setCurrentValue}
title='Lorem Ipsum'
production={production}
/>
// 5 further slider declarations here, all identical but with different titles
</>
)
}
I have tried using the key prop and a map (below) and I have tried using an array as the currentValue state declaration in the App.js file but I cannot figure out how to use setCurrentValue with an array (below but further).
In this instance, titles is an array of all of the titles for each individual slider.
const num = 6
const nums = new Array(num)
const element = [...nums.keys()].map(i => <Slider
key={i+titles[i]+production}
usableKey={i}
title={titles[i]}
production={production}
setCurrentValue={setCurrentValue}
currentValue={currentValue}
/>)
return (
<div>{element}</div>
State Array
// App.js
const [currentValue, setCurrentValue] = useState([0, 0, 0, 0, 0, 0])
// No idea how the usage for this works
Any and all help is appreciated :)
You need to pass different values to each slider, and different functions to update that state.
Many ways to go about it. Here is one example.
export const App = () => {
const [sliderValue, setSliderValue] = useState(Array(12).fill(0));
return sliderValue.map((val, index) => (
<Slider
currentValue={val}
setCurrentValue={(newVal) => {
const newSliderValues = [...sliderValue];
newSliderValues[index] = newVal;
setSliderValue(newSliderValues);
}}
/>
));
};
When the state is an object (like this array) the internals of react determines if the object has updated by doing what's called a shallow comparison. It just checks if it's the same object or not, it doesn't examine the actual content of the object.
For that reason, when updating the array of slider values we first make a shallow copy of the array. Then update that shallow copy and finally set the state of the new array.
Please consider the following code: https://codepen.io/kyxey/pen/XWEWBRY
const { useRef, useState } = React;
function App() {
const inputRef = useRef(null);
const [jobs, setJobs] = useState([]);
const addJob = () => {
const newJob = inputRef.current.value;
if (newJob) {
setJobs((prevJobs) => [...prevJobs, newJob]);
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={addJob}>Add</button>
<ul>
{jobs.map((job) => {
const listItem = job + " " + Math.random();
return <li key={listItem}>{listItem}</li>;
})}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
In this example, Whenever a new item gets added to the list, the entire list re-renders. You can tell this by looking at the random number in front of each item which will change with every newly items that are added to the list.
Now I know there are some duplications of this very question which I read them all. But none of them could solve my issue completely.
My question is: How can I prevent the re-render of the entire list each time a new item is added to it, WITHOUT using memo and useMemo in any shapes and forms? Meaning that whenever a new item is added to the list, only the new item is rendered and the other items on the list remain completely untouched. Again, I'm not able to use memo or useMemo to solve this.
For example:
Current behavior is like this:
I type Test in the input
Click the Add button
A new item gets added to the list below with a random number in front of it:
• Test 0.8025874545033296
I type AnotherTest in the input
Click the Add button
A new item gets added to the list below with a random number in front of it, BUT the random number in the front of the first item is also modified:
• Test 0.4454662757698613
• AnotherTest 0.16319305763152014
Expected behavior should be like this:
I type Test in the input
Click the Add button
A new item gets added to the list below with a random number in front of it:
• Test 0.8025874545033296
I type AnotherTest in the input
Click the Add button
A new item gets added to the list below with a random number in front of it, AND the random number in the front of the first item is NOT modified. Meaning that it's NOT re-rendered:
• Test 0.8025874545033296
• AnotherTest 0.16319305763152014
UPDATE:
This question was asked from me in a coding interview. They explicitly mentioned that I'm not allowed to use memo or useMemo in the sense that these are considered cheating! Now I don't know exactly why they think in such way but I'm sure there's an specific answer in their mind that is not how React is supposed to behave.
You've said:
This question was asked from me in a coding interview. They explicitly mentioned that I'm not allowed to use memo or useMemo in the sense that these are considered cheating! Now I don't know exactly why they think in such way but I'm sure there's an specific answer in their mind that is not how React is supposed to behave.
A reasonable answer to that — quite possibly the one they were expecting — is something along the lines of: "You can do that, but it would just be reinventing memo for no good reason, and by doing something so non-standard and unusual, it would make the code hard to understand and maintain for the next person." If they actually wanted to see a solution without using those things, I would suggest you cross them off the list of places you might consider working if you have any choice (I respect the fact you may not have a choice; I remember that vividly early in my career). That's a terrible interview question if they really wanted anything other than pushback (arguably even if they were looking for pushback), which can be indicative of a bad place to work.
But again, technically, you can it with a ref by storing the rendered li elements in it (perhaps in a Map). To me, that's much more "cheating" than doing it with memo as you should, but... Here's an example:
const { useRef, useState } = React;
// *** A means of having unique keys for jobs
let nextJobId = 1;
function App() {
const inputRef = useRef(null);
const [jobs, setJobs] = useState([]);
const renderedJobs = useRef(new Map());
const jobLiElements = renderedJobs.current;
const addJob = () => {
// *** Make the jobs objects, not just strings, so the
// same string can be used by more than one job and
// so we can give the job a unique ID.
const newJob = {
id: nextJobId++,
value: inputRef.current.value,
};
if (newJob) {
setJobs((prevJobs) => [...prevJobs, newJob]);
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={addJob}>Add</button>
<ul>
{jobs.map((job) => {
// *** Reuse li elements you already have if you have them,
// adding new ones if you don't
let li = jobLiElements.get(job);
if (!li) {
const listItem = job.value + " " + Math.random();
console.log(`Rendering li for ${job.value}`);
// *** You can't use `listItem` as the key, since there's
// _some_ chance of the same `li` having the same text and
// random number. Use the job object instead.
li = <li key={job.id}>{listItem}</li>;
jobLiElements.set(job, li);
}
return li;
})}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
But even if I went so far as to show them that, I'd also show them a solution using memo and putting the random value in the state of the job, talking about the strengths of doing it that way (not least that it's the normal, expected way to do this):
const { useRef, useState } = React;
const JobItem = React.memo(({ job: { value, rand } }) => {
const text = `${value} ${rand}`;
console.log(`Rendering JobItem for "${text}"`);
return <li>{text}</li>;
});
// *** A means of having unique keys for jobs
let nextJobId = 1;
function App() {
const inputRef = useRef(null);
const [jobs, setJobs] = useState([]);
const addJob = () => {
// *** Make the jobs objects, not just strings, so the
// same string can be used by more than one job, and so
// we can assign it a unique ID to use as a key.
// *** Assign the random number once, as part of the job.
const newJob = {
id: nextJobId++,
value: inputRef.current.value,
rand: Math.random(),
};
if (newJob) {
setJobs((prevJobs) => [...prevJobs, newJob]);
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={addJob}>Add</button>
<ul>
{/* *** Now just use JobItem*/}
{jobs.map((job) => (
<JobItem key={job.id} job={job} />
))}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
I use a timer and I want to ask you should I wrap this with useMemo and callback ?
without:
const [time, setTime] = useState();
const calculateInitialDuration = (endDate: string, today: Date): CountdownResult => {
const futureDate = new Date(endDate)
const days = differenceInDays(futureDate, today)
const hours = differenceInHours(futureDate, today) % 24
const minutes = differenceInMinutes(futureDate, today) % 60
const seconds = differenceInSeconds(futureDate, today) % 60
return { days, hours, minutes, seconds }
}
useEffect(() => {
const timer = setInterval(() =>
setTime(calculateInitialDuration(expire_date, new Date())), 1000);
return () => clearInterval(timer);
})
With memos
const calculateInitialDuration = useCallback(() => {
const days = calcDay;
const hours = calcHours;
const minutes = calcMinute;
const seconds = calcSeconds;
return { days, hours, minutes, seconds };
}, [time]);
const calcDay = useMemo(() => (differenceInDays(new Date(expire_date), new Date())), [time]);
const calcHours = useMemo(() => (differenceInHours(new Date(expire_date), new Date()) % 24), [time]);
const calcMinute = useMemo(() => (differenceInMinutes(new Date(expire_date), new Date()) % 60), [time]);
const calcSeconds = useMemo(() => (differenceInSeconds(new Date(expire_date), new Date()) % 60), [time]);
so is that necessary to wrap all calculations with memo or should I use the simple without useMemo ?
the second problem it does not lose 1 second, sometimes it looses 2 seconds
It's not a good idea to try to achieve an exact time different in the browser. setInterval/setTimeout can only guarantee you a minimal time when your function will be called.
As for memoization: the usual recommendation to use it - use it for heavy calculation OR if you pass some variable to another component as a prop.
For example:
const memoized = useMemo(() => {....}, [])
return <SomeComponent prop={memoized}/>
Unless you are in one of the above situations and have visible problems with performance, using unnecessary memoization may end up being worse for performance.
If you're trying to calculate a date difference, wouldn't you want it to be from the specific instance that the method was called?
I.E. Instead of using new Date() for every call, wouldn't it make more sense to have Date now = new Date(); and then pass in now for each call?
At the very least, that would remove the loss in seconds, would it not?
EDIT: Realizing this question is in relation to react, which I am unfortunately unfamiliar with. If this answer is somehow correct, then awesome. Otherwise, sorry for wasting your time.
Still pretty new to react, so sorry if this question seems very simple.
I'm currently having issues where I have a component that I am attempting to add in each time the add button is clicked (which works) but when I press the delete button, it deletes all of the components in the array except the first one which cannot seem to be deleted. I am using .splice() to attempt to remove the last element in the array (-1) and setting the new state to reflect that but everything I do doesn't seem to fix anything. Any input would be greatly appreciated!
function App() {
const [inputList, setInputList] = useState([]);
const [disabled, setDisabled] = useState(false);
const onAddBtnClick = event => {
if (inputList.length < 5){
setInputList(inputList.concat(<Autocomplete items={universities} />));
}else{
setDisabled(true);
}
};
const onDeleteBtnClick = event => {
setInputList(inputList.splice(-1, 1));
if (inputList.length < 5){
setDisabled(false);
}
};
return (
<Fragment>
<div className="autocompleter">
<Button onClick={onAddBtnClick} disabled={disabled} size="lg" block>Add Education (Maximum 5)</Button>
<Button onClick={onDeleteBtnClick} size="lg" block>Delete Education</Button>
{inputList}
</div>
</Fragment>
);
}
If you want to delete the last element use Array.splice(0,array.length-1). This will remove your last element. Hope this helps. In your case use this block of code.
setInputList(prev=>prev.splice(0,prev.length-1));
Your problem is that splice doesn't return a new object, it returns the deleted elements. So you are setting the wrong new state.
Take a look here, you could use slice instead