0

I want my App component to display keys every time keys changes. I'm doing this by passed keys as a prop of App:

import * as React from "react";
import { render } from "react-dom";
import { useState, useEffect } from "react"

let keys: string[] = [];

// This is what is supposed to happen in the real app
// document.addEventListener("keypress", (event) => {
  // keys.push(event.key)
// });

setTimeout(() => {
  keys.push('a');
}, 1000)

function App({ keys }: { keys: string[] }) {
  let [keysState, setKeysState] = useState(keys)

  useEffect(() => {
     setKeysState(keys)
  }, [keys])

  return (
    <div>
      {keysState.map((key: string) => (
        <li>{key}</li>
      ))}
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App keys={keys} />, rootElement);

However, App isn't re-rendering and displaying keys new value:

https://codesandbox.io/s/changing-props-on-react-root-component-forked-3mv0xf?file=/src/index.tsx

Why is this, and how to fix it?

Note: I tried: setKeysState([...keys, 'a']). That doesn't re-render App either.

Live code: https://codesandbox.io/s/changing-props-on-react-root-component-forked-3mv0xf?file=/src/index.tsx

2
  • Your code sandbox is different from your code snippet in your question Commented Nov 28, 2022 at 3:24
  • @Andrew Okay, I fixed that. Commented Nov 28, 2022 at 3:26

2 Answers 2

1

All data that is dynamic needs to be managed by React. Put your event inside the component and update local state.

function App({ initialKeys }: { initialKeys: string[] }) {
  const [keys, setKeys] = React.useState(initialKeys);
  console.log(keys);

  React.useEffect(() => {
    const append = (e) => {
      setKeys([...keys, e.key]);
    };
    document.addEventListener("keypress", append);
    return () => {
      document.removeEventListener("keypress", append);
    };
  }, [keys]);

  return (
    <div>
      {keys.map((key: string, idx) => (
        <li key={idx}>{key}</li>
      ))}
    </div>
  );
}

https://codesandbox.io/s/changing-props-on-react-root-component-forked-l869dd?file=/src/index.tsx

Sign up to request clarification or add additional context in comments.

2 Comments

you suppose to change state in a timeout rather than instantly right before component initializing
Thanks but is there a way to render keys in the React component without putting the keypress logic inside it? For example, detecting changes in key and re-rendering the component?
1

if you use the below strategy it works as you want it to work. React cannot see state changes out of its built-in functions so it didn't track the change on your array which was out of its state scope

import * as React from "react";
import { render } from "react-dom";
import { useState, useEffect } from "react";

let keys: string[] = [];

function App(props: any) {
  const [keys, oldKeysState] = useState(props.keys);
  const [keysState, setKeysState] = useState(keys);

  useEffect(() => {
    setKeysState(keys);
  }, [keys]);

  // componentWillMount
  useEffect(() => {
    setTimeout(() => {
      oldKeysState([...keys, "a"]);
    }, 1000);
  }, []);

  return (
    <div>
      {keysState.map((key: string) => (
        <li>{key}</li>
      ))}
      <button onClick={() => setKeysState([...keysState, "+"])}>+</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App keys={keys} />, rootElement);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.