1

I have tried using two different libraries to handle this issue and both time I get the same behavior.

Libraries tried: OvermindJS, Hookstate

When I click the button I can see the state change is beeing logged in the console but the component will only re-render on the second click

If I change page:

  • click Home
  • click Page1
  • click No Funds

Then it will show 1$

If I click straightway the No Funds button without changing page (first action on page) then the button will not re-render until it is clicked twice.

App.tsx

import * as React from "react"
import {
  ChakraProvider,
  Box,
  Text,
  Grid,
  theme,
} from "@chakra-ui/react"
import { Switch, Route } from "wouter"
import { Appbar } from "./common/AppBar"

export const App = () => (
  <ChakraProvider theme={theme}>
    <Appbar />
    <Box textAlign="center" fontSize="xl">
      <Grid minH="100vh" p={3}>
        <Switch>
          <Route path="/">
            <Text mt={100}>Home</Text>
          </Route>
          <Route path="/home">
            <Text mt={100}>Home</Text>
          </Route>
          <Route path="/page1">
            <Text mt={100}>Page 1</Text>
          </Route>
        </Switch>
      </Grid>
    </Box>
  </ChakraProvider>
)

AppBar.tsx

import React, { ReactNode } from "react";

import {
    Box,
    Flex,
    HStack,
    Link,
    IconButton,
    Button,
    Icon,
    useDisclosure,
    useColorModeValue,
    Stack,
} from '@chakra-ui/react';
import { HamburgerIcon, CloseIcon } from '@chakra-ui/icons';
import { MdAccountBalanceWallet } from 'react-icons/md'
import { useLocation } from 'wouter';
import { actionIncrementFunds, globalState } from "../hookState/state";
import { useState } from "@hookstate/core";
const Links = ['Home', 'Page1'];

const NavLink: React.FC<any> = ({ children, handleClick }: { children: ReactNode, handleClick: any }) => (
    <Link
        px={2}
        py={1}
        rounded={'md'}
        _hover={{
            textDecoration: 'none',
            bg: useColorModeValue('red.200', 'red.300'),
        }}
        onClick={() => handleClick(children)}>
        {children}
    </Link>
);

export const Appbar: React.FC = () => {
    const state = useState(globalState);

    const { isOpen, onOpen, onClose } = useDisclosure();
    const [location, setLocation] = useLocation();
    const handleClick = (path: string) => {
        setLocation(`/${path.toLowerCase()}`)
    }

    const hasFunds = () => {
        return state.currentFunds.get() > 0
    }

    const handleConnectWallet = () => {
        actionIncrementFunds()
    }
    return (
        <>
            <Box zIndex={900} position={"fixed"} top={0} left={0} width="100%" bg={useColorModeValue('gray.100', 'gray.900')} px={4}>
                <Flex h={16} alignItems={'center'} justifyContent={'space-between'}>
                    <IconButton
                        size={'md'}
                        icon={isOpen ? <CloseIcon /> : <HamburgerIcon />}
                        aria-label={'Open Menu'}
                        display={{ md: 'none' }}
                        onClick={isOpen ? onClose : onOpen}
                    />
                    <HStack height={"100%"} spacing={8} alignItems={'center'}>
                        <HStack
                            as={'nav'}
                            spacing={4}
                            display={{ base: 'none', md: 'flex' }}>
                            {Links.map((link) => (
                                <NavLink handleClick={handleClick} key={link}>{link}</NavLink>
                            ))}
                        </HStack>
                    </HStack>
                    <Flex alignItems={'center'}>
                        {hasFunds()
                            ? <Button
                                variant={'solid'}
                                colorScheme={'red'}
                                size={'md'}
                                onClick={handleConnectWallet}
                                mr={4}>
                                {state.currentFunds.get()} $
                            </Button>
                            : <Button
                                variant={'solid'}
                                colorScheme={'red'}
                                size={'md'}
                                mr={4}
                                onClick={handleConnectWallet}
                                leftIcon={<Icon as={MdAccountBalanceWallet} />}>
                                No Funds
                            </Button>
                        }
                    </Flex>
                </Flex>

                {
                    isOpen ? (
                        <Box pb={4} display={{ md: 'none' }}>
                            <Stack as={'nav'} spacing={4}>
                                {Links.map((link) => (
                                    <NavLink handleClick={handleClick} key={link}>{link}</NavLink>
                                ))}
                            </Stack>
                        </Box>
                    ) : null
                }
            </Box >
        </>
    );
}

Behavior example:

behavior with cursor

Repo with example for Hookstate:

https://github.com/crimson-med/state-issue

Repo with example for OvermindJS:

https://github.com/crimson-med/state-issue/tree/overmind

How can I get the button on change on the first click?

Mederic
  • 1,949
  • 4
  • 19
  • 36
  • is `get` in any way asynchronous? – Chris Jun 22 '22 at 07:56
  • nope it is not. On the bigger code it is but this is a small reproduction of the behaviour. I have tried implementing async await also but it didn't change anything – Mederic Jun 22 '22 at 07:58
  • 1
    I'm still skeptical when you say it is in the bigger code. Can you clarify what you mean by that exactly? For example why don't you just store the funds as `state.currentFunds` rather than `state.currentFunds.get()`? – Chris Jun 22 '22 at 08:02
  • Because per the hookstate documentation to get a value you need to use get: https://hookstate.js.org/docs/global-state On OvermindJS if I use `state.currentFunds` I get the same behavior. In my bigger code the click on the button opens a crypto wallet and connects to it. But the behavior is still present in the small example I gave above. The first click is completely ignored and even though the state changes it doesn't re-render. I have attached a github repo with the above example – Mederic Jun 22 '22 at 08:06
  • Okay, never used that library. Could it be something related to `actionIncrementFunds`? Does your `AppBar` re-render on the first click? – Chris Jun 22 '22 at 08:10
  • nope as can be seen in the gif the first click there is always nothing happening. It's only on the second click or if I change page that there is a re-render – Mederic Jun 22 '22 at 08:11
  • Again, I don't know this library nor its pros/cons, but the docs state something about when the state updates: https://hookstate.js.org/docs/typedoc-hookstate-core#usestate `The useState forces a component to rerender every time, when: a segment/part of the state data is updated AND only if this segment was used by the component during or after the latest rendering.` Have you tried using more traditional libraries such as redux or mobx? If anything, these being more common will also make it easier to find help online. – Chris Jun 22 '22 at 08:17
  • I have added the link for the example with using another library: OvermindJs. I don't want to use redux as it is overkill to have a few global states – Mederic Jun 22 '22 at 08:21
  • 1
    Well I'm not sure, but redux is like 2kb. I don't think it's overkill. See if it solves your problem – Chris Jun 22 '22 at 08:30

0 Answers0