ReactJs (native) - Transition variable from one value to another incrementally - javascript

Is there a way to make a transitional change of value with increments over time in React native (or any method in JS that I don't know of / can't find?
I could of course create some sort of loop with interval or setTimeout, but I was wondering if there is something like the Animated API with interpolation that could be used to just change a value when there is not actually any view that is being animated?
In my case I want to change the screen brightness of the users device smoothly, right now it goes from 0-1 instantly, which is not what I want.
I will provide code in case anyone would like to see that.
This process takes place in a useEffect() that reacts when the user is opening a scannable code on their screen.
const [brightness, setBrightness] = useState(null);
...
...
useEffect(() => {
(async () => {
const currentBrightness =
await DeviceBrightness.getBrightnessLevel();
setBrightness(currentBrightness);
})();
if (isOpened) {
DeviceBrightness.setBrightnessLevel(1);
}
if (!isOpened && brightness) {
DeviceBrightness.setBrightnessLevel(brightness);
}
}, [isOpened]);

Related

SolidJS: Using createEffect to observe signal changes

I have started a new journey with SolidJS after being in the Angularverse since Angular 2 :)
I've been following the Effects tutorial on their official website but I'm not able to make it work the way I want.
I thought createEffect would keep track and run every time the count signal changed, or so I understood from here:
The effect automatically subscribes to any signal that is read during
the function's execution and reruns when any of them change.
Unfortunately, clicking the button doesn't seem to have any effect. So, what am I missing?
import { render } from "solid-js/web";
import { createSignal, createEffect } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
const doubleCount = () => setCount(count() * 2);
createEffect(() => {
console.log("The count is now", count());
});
return <button onClick={doubleCount}>Click Me</button>;
}
render(() => <Counter />, document.getElementById('app'));
It is a feature !
What is happening is that 0 * 2 = 0 so it doesn't trigger any effect because the value of count didn't change.
Try
const [count, setCount] = createSignal(1);
to see a console.log each time the value change with your click
Your code is correct and runs as expected. Two possible reason why you don't see change:
If tere is no log, it means you don't have #app element on the page.
If there is only one log but no subsequent ones, that is because 0*2 is 0, value does not change, so it does not trigger re-run.
https://playground.solidjs.com/anonymous/7ec9c592-6c30-43a0-97a7-5f26917b4334

State not updating inside recursion

export default function App() {
const [actionId, setActionId] = useState("");
const startTest = async () => {
const newActionId = actionId + 1;
setActionId(newActionId);
const request = {
actionId: newActionId
}
console.log({ request });
// const response = await api.runTests(request)
await new Promise((resolve) => setTimeout(resolve, 4000));
startTest();
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={startTest}>Start</button>
</div>
);
}
request actionId is always 1, though I changed it every 4 seconds.
I know setState is async, but it's weird that the state is not updated after 4 seconds.
Theory
A rendered React component contains functions that belong to that rendering. If these functions reference stale variables, this will affect the result. Search for stale closures to find out more about this. It is particularly easy to end up with stale closures when dealing with the traditional Javascript functions setTimeout and setInterval in React.
So what's going on?
So on your first render, a particular instance of startTest exists. When you click the button, THAT instance of startTest runs. This instance of startTest contains a closure of actionId (by the way, why do you set actionId to "" and then do addition to this? Would be more expected to start with 0 or do addition by + "1"). When the state is set, React rerenders with actionId set to "1" and with a new version of startTest containing a closure where actionId is "1". However, this function would only be triggered if you click the button anew. Instead, what happens is that the timeout from the first render triggers a new call to the old startTest from the first render. This timeout does not know that the component has rerendered and that there is a new version of startTest some place else with an updated closure of actionId. When the function is retriggered, it calculates the same value as the first time for newActionId and so we are trapped in a loop which only uses that very first instance of startTest containing a stale closure.
How to solve it?
If you want to use timeouts and intervals in React, you gotta do it the React way. There are probably packages out there for this but if you want, you can change your example in a small way to make it work.
Instead of calculating the value of newActionId, you can supply a function to setActionId that takes the previous value as input:
setActionId(oldActionId => oldActionId + 1)
Now, since the previous value is always passed as input, the stale closure does not affect the outcome of the function and your example will work. However, I'm not sure about the design anyway because if a user hits the button again, you will have multiple timeouts running simultaneously which will cause a havoc in the long run. Nonetheless, if you want to make sure it works as expected, you could try it that way. If you could guarantee that the function would only be executed once, by restricting the button to only be pressed once, it would be a better design.
Good luck!

How to setState in a onTick event that ticks every millisecond

I'm trying to set a state in an onTick event for a clock.
<Viewer>
<Clock
startTime={start.clone()}
stopTime={stop.clone()}
currentTime={start.clone()}
multiplier={50}
onTick={_.throttle(handleValue, 1000)} // this thing ticks every millisecond
/>
<Entity
ref={ref} // here is the ref to get the value I want to set state with
position={positionProperty}
tracked
selected
model={{ uri: model, minimumPixelSize: 100, maximumScale: 100.0 }}
availability={
new TimeIntervalCollection([
new TimeInterval({ start: start, stop: stop }),
])
}
/>
</Viewer>
Here is the handleValue function.
const handleValue = (clock) => {
//setting the state here ( I want to display the chaning the value over time)
setHeadingValue(ref.current.cesiumElement._properties._heading.getValue(clock.currentTime));
}
};
The problem is it looks like it tries to re-render over and over which freezes the app.
Due to the nature of setState, this behavior makes sense. But I feel like there is an answer that's escaping me.
May I have some insight as to what I could do? I'm out of ideas.
I'm using Resium ( a react library) and right now I'm setting the value using .getElementByID() and appending to the dom.. which defeats using react in the first place...
Here is a code sandbox: https://codesandbox.io/s/resium-cesium-context-forked-bpjuw?file=/src/ViewerComponent.js
Some elements are not showing because we need a token, but that does not affect the functionality I'm looking for. Just open the console of the code sandbox and go to the ViewerComponent.js
thank you for your help
As I see, the problem here not in the library, but in a way of managing calculation and visualization.
As few people already mentioned, for UI, user don't need more than 60fps, but for process sometimes we need more.
So the solution is to separate processing from visualization.
To be more generic, here is an example in pure JavaScript:
// Here it may be some component
const clockView = document.getElementById('speedyClock')
let state = null
// This function mimicking dispatch on state update
function setState(val) {
state = val
clockView.innerHTML = state
}
const FPS = 30
// Any state out of visualization scope
// Not the React state!!!
let history = []
let nextUiUpdate = Date.now()+(1000/FPS)
/// Super speedy process
setInterval(function() {
history.push(Math.random())
const now = Date.now()
// Update according to visual frame rate
if(now >= nextUiUpdate) {
// Prepare visual updates
setState(JSON.stringify({count: history.length, history}, null, 2))
history = []
nextUiUpdate = Date.now()+(1000/FPS)
}
}, 1)
<pre id="speedyClock"></pre>
I would suggest using requestAnimationFrame, instead of setInterval as their is no point rerendering the React tree more than once every screen refresh.
By using a combination of shouldComponentUpdate and setState you can effectively setState and prevent unnecessary renders.
This is my codesandbox url to show it.
You can access your wanted value as often as you want, but we need to prevent 1 render per millisecond with shouldComponentUpdate function.

Best way to keep track of progress bar type state when component unmounts and remounts?

I was rebuilding/refactoring an old web game of mine built with vanilla JS into React + Redux. There is a specific type of button I created which looks like this:
Some buttons have a large cooldown of minutes.
I have successfully rebuilt the button component with hooks but realized a big issue. There are "tabs" in the game which when clicked, would bring you to a different screen. This means that these buttons would unmount and lose their state.
The way I handled this when I was working on the vanilla JS version was to keep all buttons in an array and through the main logic loop, I would loop through all buttons and update their progress.
//some pseudocode
const gameTick = 30
const buttons = [
{
name: 'chop tree',
duration: 2000 // milliseconds,
currentDuration: 1230,
...
}, {...}
]
// gameLoop gets fired every 30milliseconds
const gameLoop = () => {
buttons.forEach(btn => {
if (btn.currentDuration < btn.duration) {
btn.currentDuration += gameTick
}
})
}
setInterval(gameLoop, gameTick)
I would then calculate the percentage and set the width of the progress bar to it.
Using React + Redux though, I can store the buttons into the state but how would I go about handling un-mounts? If a button gets unmounted while in progress, it still needs to be updating in the background so when a user goes back to the tab, the button would be further along or maybe even finished.
I don't see how I could have this information in the reducer while constantly updating the state and passed into the button component.
Another possible idea I had to handle this is to store the timestamp when I change tabs, then when I switch back to the tab with the buttons, I would calculate the time difference and add it up. The problem with this method is you would have to switch to the correct tab to complete something.
Any ideas?

Throttle function (lodash inspired) to handle resize event using React hooks only (with Sandbox)

Sorry for the long question, but I needed to make an introduction in order to make it clearer.
I was in need of some code to toggle between my Headers components <HeaderDesktop> and <MobileHeader>.
At first I was using CSS media queries to toggle between them using display: block | none;. This is less then ideal, since both components will be rendered at the same time, which is inneficient and could bring problems in advertisement displays on hidden elements.
Someone here on SO suggested that I could use window.innerWidth and use React to determine which component to render based on that. That is much better indeed. Now only 1 component gets rendered at a time. This is what I did:
// INSIDE HEADER COMPONENT
return(
<HeaderWrapper>
{window.innerWidth < 1200 ?
<HeaderMobile/>
: <HeaderDesktop/>
}
</HeaderWrapper>
);
But I needed a way to handle resize events. So I did:
// INSIDE HEADER COMPONENT
const [windowSize, setWindowSize] = useState(window.innerWidth);
function handleResize() {
setWindowSize(window.innerWidth);
}
return(
<HeaderWrapper>
{windowSize < 1200 ?
<HeaderMobile/>
: <HeaderDesktop/>
}
</HeaderWrapper>
);
Nice! That works, but now my component renders 1 trillion times a second every time a resize is happening. That's no good for performance.
So I've done my research and found out about lodash throttle and debounce methods. Both can reduce and control the number of events handled, even when hundreds are fired subsequentely.
https://css-tricks.com/debouncing-throttling-explained-examples/
But I'm not a fan of bringing entire libraries to my dependency list just to use a simple functionality like that, so I've ended up creating the following effect hook to mimic the throttle functionality on my resize event handler.
// INSIDE HEADER COMPONENT
// Ref to store if there's a resize in progress
const resizeInProgress = useRef(false);
// State to store window size
const [windowSize, setWindowSize] = useState(window.innerWidth);
useEffect(() => {
// This function trigger the resize event handler
// And updates the ref saying that there's a resize in progress
// If theres a resize in progress, it doesn't do anything
function handleResize() {
if (resizeInProgress.current === true) {
return;
}
resizeInProgress.current = true;
throttled_updateWindowSize();
}
// This function sets a timeout to update the state with the
// new window size and when it executes, it resets the
// resizeInProgress ref to false. You can execute what's the interval
// You want to handle your resize events, in this case is 1000ms
function throttled_updateWindowSize() {
setTimeout(() => {
console.log("I will be updated!");
console.log(window.innerWidth);
setWindowSize(window.innerWidth);
resizeInProgress.current = false;
}, 1000);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
You can see this in action in the following Sandbox:
https://codesandbox.io/s/v3o0nmvvl0
QUESTION 1
Can you give me any suggestions on how to improve my code for the throttled version of the resize event handler?
QUESTION 2
I'm guessing I'll be needing that functionality in other components. How can I make this easily reusable? Can I make this a custom Hook? I have never created one, so I'm still having some trouble on how to reason about them and what is the proper way to create them. Can you help me to put that into a Custom Hook?
Or would it better to create a Higher Order Component for that?
This isn't something i'd do with a hook. You can get it to work as a hook, but you're limiting yourself to only doing throttling inside components, when throttling is a more useful utility function than that, and hooks don't make it any easier or let you do anything extra.
If you don't want to import all of lodash, that's understandable, but you could implement something similar yourself. Lodash's throttle is a higher order function: you pass it in a function, and it returns you a new function that will only execute if the appropriate amount of time has passed since the last execution. The code to do that (without quite as many options and safety checks as lodash does) can be replicated like this:
const throttle = (func, delay) => {
let inProgress = false;
return (...args) => {
if (inProgress) {
return;
}
inProgress = true;
setTimeout(() => {
func(...args); // Consider moving this line before the set timeout if you want the very first one to be immediate
inProgress = false;
}, delay);
}
}
To be used like this:
useEffect(() => {
const handleResize = throttle(() => {
setWindowSize(window.innerWidth);
}, 1000);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);

Categories

Resources