nico.fyi
Published on

Make CLI app with React

Authors
  • avatar
    Name
    Nico Prananta
    Twitter
    @2co_p

React has been around for a while and has been adopted for many kinds of apps, not only web apps but also mobile native apps. But did you know that you can use React to make a command-line app? Using ink, we can use React to create interactive and sleek-looking command-line apps!

To give an example, I made a small demo CLI app. It's a real-time chat app using Supabase's Realtime feature. Here is the main React component called App:

export default function App({
  room,
  nickname,
  supabaseKey,
  supabaseUrl,
}: Props) {
  const supabase = createClient(supabaseUrl, supabaseKey);
  const channel = supabase.channel(room);

  const [userInput, setUserInput] = useState('');
  const [messageToSend, setMessageToSend] = useState('');
  const [isSending, setIsSending] = useState(false);

  useInput((input, key) => {
    if (key.return) {
      setMessageToSend(userInput);
      setUserInput('');
    } else if (key.backspace || key.delete) {
      setUserInput((curr) => curr.slice(0, -1));
    } else {
      setUserInput((curr) => curr + input);
    }
  });

  useEffect(() => {
    if (!isSending && messageToSend.trim().length > 0) {
      if (messageToSend.trim() === '\\quit') {
        process.exit();
      }
      setIsSending(true);
      channel
        .send({
          type: 'broadcast',
          event: 'test-my-messages',
          payload: {
            user: nickname,
            content: messageToSend,
            id: nanoid(),
          },
        })
        .then(() => {
          setMessageToSend('');
          setIsSending(false);
        });
    }
  }, [messageToSend, isSending, channel]);

  const [messages, setMessages] = useState<Array<Message>>([
    {
      user: 'Room',
      content: room,
      id: `the-room`,
    },
  ]);
  useEffect(() => {
    const subscription = channel
      .on('broadcast', { event: 'test-my-messages' }, ({ payload }) => {
        setMessages((curr) => {
          const newMessages = Array.from(
            new Set([...curr, payload as Message])
          );
          return newMessages;
        });
      })
      .subscribe();

    return () => {
      subscription.unsubscribe().then(() => {});
    };
  }, []);
  return (
    <Box>
      <Static items={messages}>
        {(m) => {
          if (m.id === 'the-room') {
            return (
              <Box key={m.id}>
                <Text color="magenta" bold>
                  Room:
                </Text>
                <Text>: {m.content}</Text>
              </Box>
            );
          }
          return (
            <Box key={m.id}>
              <Text color="green">{m.user === nickname ? 'You' : m.user}</Text>
              <Text>: {m.content}</Text>
            </Box>
          );
        }}
      </Static>
      <Text>{userInput}</Text>
    </Box>
  );
}

I found it really cool that I can use useEffect and useState to handle events and state in the CLI app. ❤️

Here's the demo video:

You can find the repository here. Have fun!


Are you working in a team environment and your pull request process slows your team down? Then you have to grab a copy of my book, Pull Request Best Practices!