vanjs-org / van

🍦 VanJS: World's smallest reactive UI framework. Incredibly Powerful, Insanely Small - Everyone can build a useful UI app in an hour.
https://vanjs.org
MIT License
3.77k stars 87 forks source link

how to manually re-render? #269

Closed farzher closed 6 months ago

farzher commented 6 months ago

maybe i'm just doing it wrong. but all these reactive frameworks are so frustrating.

i don't want to turn some of my state variables into weird van.state proxies. i don't want it to randomly re-render, switching out input elements while typing and causing weirdness.

i just want to render stuff to the screen. i know when my state changed and don't mind manually calling a render/update function.

i tried doing const manualrender = van.state(0) then i can say manualrender.val++ to trigger a redraw. except it only redraws the parts where i'm using manualrender and has stale data for other parts of the UI

Tao-VanJS commented 6 months ago

Hi @farzher,

Thanks for reaching out! Do you have some code to illustrate the problem that you're encountering? If possible, I can try to see how it can be fixed.

sirenkovladd commented 6 months ago

I think you are looking for something like this

const {a, div, li, p, ul} = van.tags

const mainObj = {
  i: 0,
  text: 'Hello'
}

const stateObj = Object.fromEntries(Object.entries(mainObj).map(([k,v]) => [k, van.state(v)]));

function update() {
  for (const key of Object.keys(mainObj)) {
    if (stateObj[key].val !== mainObj[key]) {
      stateObj[key].val = mainObj[key]
    }
  }
}

function recursive() {
  if (mainObj.text.length > 15) {
    return
  }
  const rand = Math.round(Math.random() * 26)
  mainObj.text += String.fromCharCode(65 + rand)
  mainObj.i += rand
  recursive()
}

van.add(document.body, div(stateObj.i), div(stateObj.text))

recursive()
update()
farzher commented 6 months ago

Hi @farzher,

Thanks for reaching out! Do you have some code to illustrate the problem that you're encountering? If possible, I can try to see how it can be fixed.

sure, one use case i have is rendering UI on top of a webgl game canvas.

i have a game state. i have a render function. i simply want to redraw my UI every frame, like a game would.

here's some example code i have of rendering usernames. it mostly works but it's very hairy


<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.2.8.nomodule.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vanjs-ext@0.2.2/dist/van-x.nomodule.min.js"></script>

<style>
  html,body,* {margin:0;padding:0;overflow:hidden;}
  body        {background: rgb(26,26,26);}
</style>
<body></body>

<script>

// some example state

const me = {user_id:1, x: 0, y: 0}
const lobby_state = {}
const chat_bubbles = vanX.reactive({})

chat_bubbles[1] = {username: 'farzher', msg: 'sup', user_id: 1}
chat_bubbles[2] = {username: 'Guest', msg: 'hello', user_id: 2}
lobby_state[2] = {x: 50, y: 50, user_id: 2}

window.requestAnimationFrame(update)
function update() {
  window.requestAnimationFrame(update)

  // simulate player walking around
  for(const user_id in lobby_state) {
    const player = lobby_state[user_id]
    player.x += 1
    if(player.x > 200) player.x = -200
  }
}

// called many times per second
window.requestAnimationFrame(render)
function render() {
  window.requestAnimationFrame(render)
  render_chat_bubbles()
}

const manualrender = van.state(0)
function render_chat_bubbles() {
  manualrender.val++
}

function chat_bubbles_vanjs() {

  const {div, a, code, img, input, hr, span} = van.tags

  const van_if = (bool, val) => bool ? val : div()

  return (
    div({style: 'pointer-events: none'},
      vanX.list(div, chat_bubbles, ({val: msginfo}) => {

        const w = window.innerWidth
        const h = window.innerHeight

        const is_me = msginfo.user_id == me.user_id
        if(is_me) return div({class: 'speech-bubble', style: () => `manualrender:${manualrender.val}; max-width:240px; max-height:100px; padding:10px; text-align:center;    position:absolute; left:${w/2}px; top:${h/2 - 80}px; transform: translate(-50%, -50%); color: white;`}, msginfo.msg)

        // the chat bubble is not from me, we need to render it on the player
        const entity = lobby_state[msginfo.user_id]
        return div({class: 'speech-bubble', style: () => `manualrender:${manualrender.val}; max-width:240px; max-height:100px; padding:10px; text-align:center;    position:absolute; left:${entity.x + w/2 - me.x}px; top:${entity.y + h/2 - 80 - me.y}px; transform: translate(-50%, -50%); color: white;`}, msginfo.msg)
      })
    )
  )
}

van.add(document.body, chat_bubbles_vanjs())

</script>
sirenkovladd commented 6 months ago

If you want to avoid updating the variable immediately after editing, you need to use additional state

here is an example https://jsfiddle.net/Sirenko/suzw85o3/

Tao-VanJS commented 6 months ago

Hi @farzher,

Normally it might not be a good idea to re-render the entire UI. Making the re-rendering as localized as possible is usually better for performance and user experience.

But in case you do want global re-rendering for the entire UI, you can try this pattern:

const frameNum = van.state(0)
const fps = 60  // You can set your desired fps here
setInterval(() => ++frameNum.val, 1000 / fps)

van.add(document.body, () => {
  frameNum.val
  return <The entire DOM tree for your UI>
})

You shouldn't use states or binding functions like vanX.list for the rest of your app, as using states elsewhere might trigger the re-rendering some local parts of the UI.

farzher commented 6 months ago

@sirenkovladd your examples are much better than what i had. but they're still too clumsy to use, for example they still have the problem that window.innerWidth is somehow cached and never recalculated when the window is resized.

farzher commented 6 months ago

@Tao-VanJS this works great, except, yeah, it constantly rewrites the dom which can be a problem. i guess i'm looking for a js library that will let me generate a full virtual dom every frame like that, but it will do a diff on its end and only update on the dom what's necessary. would you happen to know of any lightweight options for this?

Tao-VanJS commented 6 months ago

@Tao-VanJS this works great, except, yeah, it constantly rewrites the dom which can be a problem. i guess i'm looking for a js library that will let me generate a full virtual dom every frame like that, but it will do a diff on its end and only update on the dom what's necessary. would you happen to know of any lightweight options for this?

I guess React is a good diff-based re-rendering? In VanJS, you're encouraged to bind states to local scopes to prevent the entire DOM tree from being re-rendered.