Skip to content

Commit

Permalink
Add isActive option to useInput hook
Browse files Browse the repository at this point in the history
If there are multiple `useInput` hooks rendered at once, all of them will capture
and handle input, which is not desired most commonly.

This change adds `isActive` option to let developer selectively enable or disable
each `useInput` hook, so that input is handled by one hook at a time.
  • Loading branch information
Vadim Demedes committed Apr 12, 2020
1 parent b829a4c commit f419028
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 14 deletions.
38 changes: 27 additions & 11 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ Usage:
## Hooks
### useInput
### useInput(inputHandler, options?)
This hook is used for handling user input.
It's a more convienient alternative to using `StdinContext` and listening to `data` events.
Expand All @@ -816,62 +816,78 @@ const UserInput = () => {
};
```
#### inputHandler
Type: `Function`
The handler function that you pass to `useInput` receives two arguments:
#### input
##### input
Type: `string`
The input that the program received.
#### key
##### key
Type: `object`
Handy information about a key that was pressed.
##### key.leftArrow
###### key.leftArrow
##### key.rightArrow
###### key.rightArrow
##### key.upArrow
###### key.upArrow
##### key.downArrow
###### key.downArrow
Type: `boolean`<br>
Default: `false`
If an arrow key was pressed, the corresponding property will be `true`.
For example, if user presses left arrow key, `key.leftArrow` equals `true`.
##### key.return
###### key.return
Type: `boolean`<br>
Default: `false`
Return (Enter) key was pressed.
##### key.ctrl
###### key.ctrl
Type: `boolean`<br>
Default: `false`
Ctrl key was pressed.
##### key.shift
###### key.shift
Type: `boolean`<br>
Default: `false`
Shift key was pressed.
##### key.meta
###### key.meta
Type: `boolean`<br>
Default: `false`
[Meta key](https://en.wikipedia.org/wiki/Meta_key) was pressed.
#### options
Type: `object`
##### isActive
Type: `boolean`<br>
Default: `true`
Enable or disable capturing of user input.
Useful when there are multiple `useInput` hooks used at once to avoid handling the same input several times.
### useApp
`useApp` is a React hook, which exposes props of [`AppContext`](#appcontext).
Expand Down
20 changes: 17 additions & 3 deletions src/hooks/use-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export interface Key {
meta: boolean;
}

type Handler = (input: string, key: Key) => void;

interface Options {
isActive?: boolean;
}

/**
* This hook is used for handling user input.
* It's a more convienient alternative to using `StdinContext` and listening to `data` events.
Expand All @@ -37,18 +43,26 @@ export interface Key {
* };
* ```
*/
export const useInput = (inputHandler: (input: string, key: Key) => void) => {
export const useInput = (inputHandler: Handler, options: Options = {}) => {
const {stdin, setRawMode} = useContext(StdinContext);

useEffect(() => {
if (options.isActive === false) {
return;
}

setRawMode(true);

return () => {
setRawMode(false);
};
}, [setRawMode]);
}, [options.isActive, setRawMode]);

useEffect(() => {
if (options.isActive === false) {
return;
}

const handleData = (data: Buffer) => {
let input = String(data);
const key = {
Expand Down Expand Up @@ -90,5 +104,5 @@ export const useInput = (inputHandler: (input: string, key: Key) => void) => {
return () => {
stdin?.off('data', handleData);
};
}, [stdin, inputHandler]);
}, [options.isActive, stdin, inputHandler]);
};
27 changes: 27 additions & 0 deletions test/fixtures/use-input-multiple.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, {FC, useState, useCallback, useEffect} from 'react';
import {render, useInput, useApp, Text} from '../../src';

const App: FC = () => {
const {exit} = useApp();
const [input, setInput] = useState('');

const handleInput = useCallback(input => {
setInput(previousInput => previousInput + input);
}, []);

useInput(handleInput);
useInput(handleInput, {isActive: false});

useEffect(() => {
setTimeout(exit, 1000);
}, []);

return <Text>{input}</Text>;
};

const app = render(<App />);

(async () => {
await app.waitUntilExit();
console.log('exited');
})();
9 changes: 9 additions & 0 deletions test/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,12 @@ test('handle right arrow', async t => {
await ps.waitForExit();
t.true(ps.output.includes('exited'));
});

test('ignore input if not active', async t => {
const ps = term('use-input-multiple');
ps.write('x');
await ps.waitForExit();
t.false(ps.output.includes('xx'));
t.true(ps.output.includes('x'));
t.true(ps.output.includes('exited'));
});

0 comments on commit f419028

Please sign in to comment.