Categories
discuss

Better way to use useState hook for setting the boolean state in React

I’ve just started learning React and got to know about the useState hook. I came across two different ways for setting the state for boolean data. So are these two approaches identical and if not, which one is one should prefer?

const [isChanged, setIsChanged] = useState<boolean>(false)
  
const onClick = () => {
    setIsChanged((prevState) => !prevState)  // Approach 1
    setIsChanged(!isChanged)  // Approach 2
}

Answer

Since, as often in code, a simple example paints a thousand words, here’s a simple CodeSandbox demo to illustrate the difference, and why, if you want an update based on the value of the state at the point of update, the “updater function” (Approach 1) is best:

https://codesandbox.io/s/stack-overflow-demo-nmjiy?file=/src/App.js

And here’s the code in a self-contained snippet:

<div id="root"></div><script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/standalone@7.16.7/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">

function App() {
  const [count, setCount] = React.useState(0);

  // this uses the "good way" but it doesn't really matter here
  const incrementPlain = () => setCount((oldCount) => oldCount + 1);

  const incrementWithTimeoutBad = () =>
    setTimeout(() => setCount(count + 1), 3000);
  const incrementWithTimeoutGood = () =>
    setTimeout(() => setCount((oldCount) => oldCount + 1), 3000);

  return (
    <div>
      <div>Current count: {count}</div>
      <div>
        <button onClick={incrementPlain}>
          Increment (doesn't matter which way)
        </button>
      </div>
      <div>
        <button onClick={incrementWithTimeoutBad}>
          Increment with delay (bugged)
        </button>
        <button onClick={incrementWithTimeoutGood}>
          Increment with delay (good)
        </button>
      </div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));

</script>

Here we have a simple numeric “count” state which is displayed in the markup, together with 3 different buttons that all increment it.

The one on top just does a direct increment – I happen to have used the function form (“approach 1”) here because I prefer this style for reasons that will hopefully become clear, but as my comment says, it doesn’t actually matter here.

The two below use the two different approaches you outline in the question, and do so after a delay. I’ve done this with setTimeout here just to be simple – while this isn’t particularly realistic, similar effects are commonly seen in real apps where actions call an API endpoint (and even though one hopes that doesn’t normally take as long as 3 seconds, the same problems can always be observed with quicker requests – I’ve just slowed it down to be easier to trigger the bug).

To see the difference, try the following with each of the 2 bottom buttons:

  • click the button
  • click the button on top (to increment the count again) BEFORE the 3-second timeout is up

You should see a clear difference in behaviour:

  • with “approach 1” (button on the right, which I’m calling “good” here), the count increments a second time after the timeout is finished.
  • with “approach 2” (button on the left, which I’ve called “bugged”), there is no further increment from the value produced by the intermediate click on the top button, no matter how long you wait

(You can see this more dramatically if you click the bottom button multiple times quickly, then the top one once. And for an even more counterintuitive effect, try pressing the “bugged” bottom button one or more times, then clicking the top button more than once, all within the 3 second time interval.)

Why does this happen? Well, the “buggy” behaviour happens because the function inside the setTimeout is a closure over the outer variable count which is in the scope of the full component function. That means that when it’s called with count + 1 as the argument, it will update the count to 1 more than whatever it was at the point the function was defined. Say you do the above sequence from first loading the component where count is 0, then the more detailed sequence of what happens is:

  • the bottom button click schedules a callback to happen in 3 seconds’ time. Since count at that point is equal to 0, its argument, count + 1 is equal to 1.
  • the top button click rerenders the component, with count now equal to 1.
  • the callback set up at the first step later triggers, and sets the count to 1. This doesn’t cause any noticeable chance, because the count was already 1. (If you tried clicking the top button multiple times, so it now shows 2 or more, this will actually decrement the counter, because as I’m explaining, it will always get set to 1.)

If you know a little bit about JS closures, you might wonder why the count that is accessed in the closure is still 0. Wasn’t it previously updated to 1? No, it wasn’t, and that’s the bit that might be counterintuitive. Notice how count is declared with const? That’s right – it never actually changes. The reason the UI updates is because setCount causes React to rerender your component, which means the whole outer function corresponding to the component is called again. This sets up a whole new environment, with a new count variable. React’s internals ensure that the useState call now gives back 1 for the current count, which is therefore the value in that new “instance” of the component – but that’s irrelevant from the point of view of the function that was put in the event queue to fire after 3 seconds. As far as it’s concerned, the count variable – no longer in scope but “remembered” inside that callback as all closed-over variables are – has never changed from 0. The count that’s equal to 1 is in a different scope entirely, and forever inaccessible to that first callback.

How does the function argument form – “approach 1” – get round this? Very easily. It doesn’t hold any closure at all – the variable inside that function, which I’ve called oldCount here for the sake of both accuracy and to disambiguate from the outer count – has nothing to do with the count outside. It’s the argument to a function that React itself will call internally. When React does call the function, it always supplies the “most up-to-date” state value it has. So you don’t have to worry about “stale closures” or anything like that – you’re saying “whatever the most recent value was, update the count to be one more than that”, and React will take care of the rest.

I’ve called approach 2 “bugged” here because I think it’s reasonable to expect an increment to happen after the timeout, if you’ve clicked a button that you’ve set up to do an increment. But this isn’t always what you want. If you genuinely wanted the update to be based on the value at the point the button was first clicked, then of course you will prefer Approach 2, and Approach 1 will seem bugged. And in a sense that’s more often the case. I highly recommend reading this post by Dan Abramov – one of the core React developers – that explains a crucial difference between class components and functions, that’s based on many of the same arguments about closures that came into play here, where normally you do want the event handlers to reference values as they were at the time of render, not when they actually fire after an API request or timeout.

But that post doesn’t have anything to do with the “approach 1” form of state-updating functions, which isn’t even mentioned in the article. That’s because it’s irrelevant to the examples given – there’d be no (sensible) way to rewrite those examples to use it. But when you do want to update a state value based on its previous value – as could happen with negating a boolean value, as in the OP example, or incrementing a counter as in mine, I would argue that it’s more natural that you always want that “previous value” to be up to date. There are 2 buttons which both should increment a value, albeit in different ways – I think it’s reasonable to call it bugged if clicking both of them, depending on the timing, may only increment once in total.

But that’s of course up to each individual component or application to decide. What I hope I’ve done here is explain what the difference is, and give you a basis to choose which might be best. But I do believe that 90+% of the time, if you have the option of using the function argument (“approach 1”), it will be better, unless you know it isn’t.

Categories
discuss

Vue leaflet doesn’t work when building app

I’m currently creating a vue3 cli app that uses vue-leaflet (the vue3 compatible version)

Everything works great on my local dev environment but once my app is built the map doesn’t load, even when I resize like this thread explains well.

I tried using the leafletObject.invalidateSize() method but nothing changed.

My map is a component called using a v-if on first call (switch between a list view and the map) and a v-show once it has been initialized

  <transition name="fade" mode="out-in">
    <Map v-if="mapInit"
         v-show="(!mobileView && !listing) ||
          (mobileView && !listing && !getFiltresMobile)"
         :liste="getFilteredList"/>
  </transition>

Here is what I get on my dev version when I use npm run serve enter image description here

Here is the result on the prod version lealfet prod

Here is the relevant part of my code, I can add more if you think more code needs to be shown

<template>

  <div id="map">
    <l-map v-if="!loading" :style="mapHeight" :zoom="zoom" :center="centerValue" :options="{tap : false}" ref="map">
      <l-tile-layer :url="url"></l-tile-layer>
      <l-marker v-for="(marqueur) in mapData" :key="marqueur.id" :visible="isFiltered(marqueur)" :lat-lng="marqueur.latLng" :ref="setMarkerRefs" @click="markerClick(marqueur.latLng)">
        <l-icon :icon-url="checkActive(marqueur.latLng)" :icon-size="[30 , 37]" :popup-anchor="popupUpPosition" :icon-anchor="[15, 37]"/>
        <l-popup :options="popupoptions">
          <MarqueurPopup :contenu="marqueur"/>
        </l-popup>
      </l-marker>
    </l-map>
    <Loader v-if="loading"/>
  </div>

</template>

JS

<script>
import "leaflet/dist/leaflet.css"
import { LMap , LTileLayer , LPopup , LMarker , LIcon } from "@vue-leaflet/vue-leaflet";

export default {
  name: "Map",
  props : ['liste'],
  components: {
    Loader,
    MarqueurPopup,
    LMap,
    LTileLayer,
    LMarker,
    LPopup,
    LIcon,
  },
  data() {
    return {
      url: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',
      zoom: 13,
      mapData : [],
      markerRefs : [],
      markerRefsDef : [],
      popupoptions : {
        maxWidth : 1000,
        minWidth : 300,
        closeButton : false,
      }
    };
  },

  beforeMount() {
    this.initMap()
  },
  mounted() {
    setTimeout( () => {
      console.log(this.$refs.map.leafletObject) // returns the leaflet map objects
      this.$refs.map.leafletObject.invalidateSize(); // no error but does nothing 
    }, 1000);
  },
  computed : {
    popupUpPosition() {
      if (window.innerWidth < 768) {
        return [0, 74]
      }
      return [0, -37]
    },
    mapHeight() {
      let colonneValue = window.innerWidth / 12;
      if (colonneValue < 115) {
        colonneValue = 115
      }
      let height = window.innerHeight - colonneValue;

      return {height : height+'px'}
    },
  },
  methods : {
    async initMap() {
      this.$store.commit('changeLoading' , true);
      this.mapData = []
      this.mapData = this.getFilteredList
      this.$store.commit('changeLoading' , false);
    },
  }
}
</script>

Is there a way to check if the invalidateSize() methods triggers ? My console.log get no errors but nothing changes so maybe it doesn’t trigger the method ?

Answer

Rather looks like the Leaflet CSS is incorrectly loaded in your production bundle: tiles are scrambled up, no zoom and attribution controls.

Categories
discuss

Why React Router NavLink prop exact (v5) vs. end (v6) lead to different result with trailing slash in url

In React Router Version 5 I had a NavLink which looked the following:

<NavLink
  to="/notes"
  exact={true}
  className="border-transparent text-gray-600 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-800 block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
  activeClassName="bg-indigo-50 border-indigo-500 text-indigo-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
>
  Home
</NavLink>

It recognized both localhost:3000/notes and localhost:3000/notes/ with a trailing slash as active urls.

In React Router Version 6 I refactored it to the following:

<NavLink
  to="/notes" end
  className={(navData) =>
  navData.isActive
    ? "bg-indigo-50 border-indigo-500 text-indigo-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
    : "border-transparent text-gray-600 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-800 block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
  }
>
  Home
</NavLink>

This recognizes only localhost:3000/notes as active url.

Is there a way in v6 to have also the version with trailing slash recognized as an active url?

Answer

I think you’ve mixed up what the react-router-dom v5 exact prop represents, or rather, that you’ve conflated the behavior between the exact and strict props.

v5

  • exact: When true, the active class/style will only be applied if the location is matched exactly.
  • strict: When true, the trailing slash on a location’s pathname will be taken into consideration when determining if the location matches the current URL. See the <Route strict> documentation for more information.

Note the emphasis regarding exactly matching is mine.

v6

In react-router-dom version 6 all routes/links are always exactly matched. The end prop of the NavLink has more to do with the trailing slash "/" than it does route matching.

If the end prop is used, it will ensure this component isn’t matched as “active” when its descendant paths are matched. For example, to render a link that is only active at the website root and not any other URLs, you can use:

<NavLink to="/" end>
  Home
</NavLink>

To address your question

Why React Router NavLink prop exact (v5) vs. end (v6) lead to different result with trailing slash in url

This is because you are comparing apples to oranges. If you want to achieve the same link behavior from v5 to v6 regarding the trailing slash "/" then omit the end prop.

<NavLink
  to="/notes"
  className={(navData) =>
    navData.isActive
      ? "bg-indigo-50 border-indigo-500 text-indigo-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
      : "border-transparent text-gray-600 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-800 block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
  }
>
  Home
</NavLink>

Edit why-react-router-navlink-prop-exact-v5-vs-end-v6-lead-to-different-result-w

Categories
discuss

ReactJS double render for a Boolean state with useState

I’m just playing around with ReactJS and trying to figure out some strange behavior with the useState hook.

A component should not re-rendered if the state is set with the same primitive value (Boolean) as it was before

const useScroll = ({positionToCross = 10}) => {

    const window = useWindow();
    const [isPositionCrossed, setIsPositionCrossed] = useState(window.scrollY > positionToCross);

    useEffect(() => {

        const onScroll = function (e) {

            window.requestAnimationFrame(function () {
                const lastKnownScrollPosition = window.scrollY;
                setIsPositionCrossed(lastKnownScrollPosition > positionToCross);
            });

        }

        window.addEventListener('scroll', onScroll);

        return () => {
            window.removeEventListener("scroll", onScroll)
        }

    }, []);


    console.log(`useScroll - render window.scrollY = ${window.scrollY.toFixed(0)} isPositionCrossed = `, isPositionCrossed)
    return {isPositionCrossed}
}

here is the console output – you can see the component and the hook are both rendered two times with “true” (after scrolled over 100px)

"useScroll - render window.scrollY = 101 isPositionCrossed = ", true
"useScroll - render window.scrollY = 103 isPositionCrossed = ", true

Answer

If you try simple code that on click handler setState and if you click two times and in each update state with same value the component again re-render. As react doc says:

If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)

Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.

I hope the answers from this post and this github discussion help you to understand why this happens

and there are another related topics like this post and this one

Categories
discuss

Why is my Rust program running more that twice as slow as the Java equivalent?

I have a program that finds, for all integers less than or equal to the input, numbers that can be represented as the sum of two cubes, twice, aka the Ramanujan’s number problem.

I have written this in Java and Rust, however, it runs more than twice as slow in Rust as compared to Java.

Is there anything I can do to make it perform better, or otherwise improve it?

Rust code:

use num_integer::Roots;
fn main() {
    let v = 984067;
    // let v = 87539319;
    for i in 1..=v {
        ramanujan(i)
    }
}
fn ramanujan(m: i32) {
    let maxcube = m.cbrt();
    let mut res1 = 0;
    let mut res2 = 0;
    let mut _res3 = 0;
    let mut _res4 = 0;
    for i in 1..=maxcube {
        for j in 1..=maxcube {
            if i * i * i + j * j * j == m {
                res1 = i;
                res2 = j;
                break;
            }
        }
    }
    for k in 1..=maxcube {
        for l in 1..=maxcube {
            if k == res1 || k == res2 || l == res1 || l == res2 {
                continue;
            }
            if k * k * k + l * l * l == m {
                _res3 = k;
                _res4 = l;
                break;
            }
        }
    }
    // if ((res1 * res1 * res1) + (res2 * res2 * res2) == m) && ((res3 * res3 * res3) + (res4 * res4 * res4) == m) {
    //     println!("{} is representable as the sums of two different sets of two cubes!nThese values are {}, {}, and {}, {}.", m, res1, res2, res3, res4);
    // }
}

Java code:

public class Ramun {
    public static void main(String[] args) {
        int v = 984067;
        // int v = 87539319;
        for (int i = 1; i <= v; i++) {
            ramanujan(i);
        }
    }

    public static void ramanujan(int m) {
        int maxcube = (int) Math.round(Math.cbrt(m));
        int res1 = 0, res2 = 0, res3 = 0, res4 = 0;
        for (int i = 1; i <= maxcube; i++) {
            for (int j = 1; j <= maxcube; j++) {
                if (((i * i * i) + (j * j * j)) == m) {
                    res1 = i;
                    res2 = j;
                    break;
                }
            }
        }
        for (int k = 1; k <= maxcube; k++) {
            for (int l = 1; l <= maxcube; l++) {
                if (k == res1 || k == res2 || l == res1 || l == res2)
                    continue;
                if (((k * k * k) + (l * l * l)) == m) {
                    res3 = k;
                    res4 = l;
                    break;
                }
            }
        }
        // if (((res1 * res1 * res1) + (res2 * res2 * res2) == m) && ((res3 * res3 * res3) + (res4 * res4 * res4) == m)) {
        //     System.out.printf("%d is representable as the sums of two different sets of two cubes!%nThese values are %d, %d, and %d, %d.%n", m, res1, res2, res3, res4);
        // }
    }
}

Time output for both programs

Answer

The problem lies in RangeInclusive which can be expensive.

Here’s a version avoiding it:

fn ramanujan(m: i32) {
    let maxcube = m.cbrt() + 1; // we know it can't overflow
    let mut res1 = 0;
    let mut res2 = 0;
    let mut res3 = 0;
    let mut res4 = 0;

    for i in 1..maxcube {
        for j in 1..maxcube {
            if i * i * i + j * j * j == m {
                res1 = i;
                res2 = j;
                break;
            }
        }
    }

    for k in 1..maxcube {
        for l in 1..maxcube {
            if k == res1 || k == res2 || l == res1 || l == res2 {
                continue;
            }
            if k * k * k + l * l * l == m {
                res3 = k;
                res4 = l;
                break;
            }
        }
    }
}

Result:

From: 0.01s user 0.00s system 0% cpu 17.993 total
To: 0.00s user 0.01s system 0% cpu 3.494 total

I added a comment to #45222 to draw attention to this issue.


Looks like for_each() allows better performance too (as for loops are more natural and should have the same performance, it should be considered as a bug):

fn ramanujan(m: i32) {
    let maxcube = m.cbrt();
    let mut res1 = 0;
    let mut res2 = 0;
    let mut res3 = 0;
    let mut res4 = 0;

    (1..=maxcube).for_each(|i| {
        (1..=maxcube).try_for_each(|j| {
            if i * i * i + j * j * j == m {
                res1 = i;
                res2 = j;
                ControlFlow::Break(())
            } else {
                ControlFlow::Continue(())
            }
        });
    });
    (1..=maxcube).for_each(|k| {
        (1..=maxcube).try_for_each(|l| {
            if k != res1 && k != res2 && l != res1 && l != res2 && k * k * k + l * l * l == m {
                res3 = k;
                res4 = l;
                ControlFlow::Break(())
            } else {
                ControlFlow::Continue(())
            }
        });
    });
}
0.00s user 0.01s system 0% cpu 4.029 total
Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..