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!