Security News
RubyGems.org Adds New Maintainer Role
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Ink is a React-based framework for building command-line interface (CLI) applications. It allows developers to use React components to create interactive and dynamic CLI tools.
Rendering Text
Ink allows you to render text in the terminal using React components. The example demonstrates a simple 'Hello, world!' text rendering.
const { render, Text } = require('ink');
const App = () => <Text>Hello, world!</Text>;
render(<App />);
Handling User Input
Ink provides hooks like `useInput` to handle user input. This example shows how to exit the application when the user presses 'q'.
const { render, Text, useInput } = require('ink');
const App = () => {
useInput((input, key) => {
if (input === 'q') {
process.exit();
}
});
return <Text>Press 'q' to exit.</Text>;
};
render(<App />);
Using Components
Ink supports layout components like `Box` to arrange other components. This example demonstrates a vertical layout with two text components.
const { render, Box, Text } = require('ink');
const App = () => (
<Box flexDirection="column">
<Text>Hello</Text>
<Text>World</Text>
</Box>
);
render(<App />);
Styling Text
Ink allows you to style text using properties like `color`. This example shows how to render green-colored text.
const { render, Text } = require('ink');
const App = () => (
<Text color="green">This is green text</Text>
);
render(<App />);
Blessed is a library for creating interactive command-line applications. It provides a wide range of widgets and supports mouse and keyboard input. Compared to Ink, Blessed is more low-level and imperative, whereas Ink leverages React's declarative approach.
Ink-select-input is a component for Ink that allows you to create interactive select inputs. It is specifically designed to work with Ink, providing a higher-level abstraction for creating selection menus. Unlike Ink, which is a full framework, ink-select-input is a specialized component.
Vorpal is a framework for building interactive CLI applications. It provides a command-line interface with built-in help, tab completion, and more. Vorpal is more focused on creating command-based interfaces, whereas Ink is more flexible and component-based.
React for CLIs. Build and test your CLI output using components.
$ npm install ink
const {h, render, Component, Color } = require('ink');
class Counter extends Component {
constructor() {
super();
this.state = {
i: 0
};
}
render() {
return (
<Color green>
{this.state.i} tests passed
</Color>
);
}
componentDidMount() {
this.timer = setInterval(() => {
this.setState({
i: this.state.i + 1
});
}, 100);
}
componentWillUnmount() {
clearInterval(this.timer);
}
}
render(<Counter/>);
console[method]
calls in a scrollable panel.Ink's goal is to provide the same component-based UI building experience that React provides, but for command-line apps. That's why it tries to implement the minimum required functionality of React. If you are already familiar with React (or Preact, since Ink borrows a few ideas from it), you already know Ink.
The key difference you have to remember is that the rendering result isn't a DOM, but a string, which Ink writes to the output.
To ensure all examples work and you can begin your adventure with Ink, make sure to set up a JSX transpiler and set JSX pragma to h
. You can use babel-plugin-transform-react-jsx
to do this. For example, in package.json
:
{
"babel": {
"plugins": [
[
"transform-react-jsx",
{
"pragma": "h"
}
]
]
}
}
Don't forget to import h
into every file that contains JSX:
const {h} = require('ink');
const Demo = () => <div/>;
To get started, quickly scaffold out a project using Ink CLI Yeoman generator. To create a new component that you intend to publish, you can use Ink Component generator.
Mount a component, listen for updates and update the output. This method is used for interactive UIs, where you need state, user input or lifecycle methods.
It automatically enables keypress
events on process.stdin
. Since it requires raw mode to be enabled, Ink handles default behavior for you, like exiting with Ctrl+C.
Type: VNode
Type: Stream
Default: process.stdout
const {h, render, Component} = require('ink');
class Counter extends Component {
constructor() {
super();
this.state = {
i: 0
};
}
render(props, state) {
return `Iteration #${state.i}`;
}
componentDidMount() {
this.timer = setInterval(() => {
this.setState({
i: this.state.i + 1
});
}, 100);
}
componentWillUnmount() {
clearInterval(this.timer);
}
}
const unmount = render(<Counter/>);
setTimeout(() => {
// Enough counting
unmount();
}, 1000);
Render a component to a string and return it. Useful if you don't intend to use state or lifecycle methods and just want to render the UI once and exit.
const {h, renderToString} = require('ink');
const Hello = () => 'Hello World';
process.stdout.write(renderToString(<Hello/>));
Similarly to React, there are two kinds of components: Stateful components (next, "component") and stateless function components. You can create a component by extending Component
class. Unlike stateless function components, they have access to state, context, lifecycle methods and they can be accessed via refs.
class Demo extends Component {
render(props, state, context) {
// props === this.props
// state === this.state
// context === this.context
return 'Hello World';
}
}
If you need to extend the constructor to set the initial state or for other purposes, make sure to call super()
with props
and context
:
constructor(props, context) {
super(props, context);
this.state = {
i: 0
};
// Other initialization
}
Props are basically arguments for components. Every parent component can pass props to their children.
class Child extends Component {
render(props) {
// props === this.props
return `Hello, ${props.name}`;
}
}
class Parent extends Component {
render() {
return <Child name="Joe"/>;
}
}
To set default props on specific component, use defaultProps
:
const Test = ({first, second}) => `${first} ${second}`;
Test.defaultProps = {
first: 'Hello',
second: 'World'
};
// <Test/> => "Hello World"
Ink supports prop types out-of-the-box. All you have to do is set them in the same way you would in React:
const PropTypes = require('prop-types');
Test.propTypes = {
first: PropTypes.string.isRequired,
second: PropTypes.string
};
Note: Prop types are only checked when NODE_ENV
isn't 'production'
.
Lifecycle methods are component methods that are called whenever a certain event happens related to that specific component. All lifecycle methods are called from top to down, meaning that components on top receive those events earlier than their children.
Component is initialized and is about to be rendered and written to the output.
Component is rendered and written to the output.
Component is about to be unmounted and component instance is going to be destroyed. This is the place to clean up timers, cancel HTTP requests, etc.
Component is going to receive new props or state.
At this point this.props
and this.state
contain previous props and state.
Determines whether to rerender component for the next props and state.
Return false
to skip rerendering of component's children.
By default, returns true
, so component is always rerendered on update.
Component is about to rerender.
Component was rerendered and was written to the output.
Each component can have its local state accessible via this.state
.
Whenever a state updates, the component is rerendered.
To set the initial state, extend the constructor and assign an object to this.state
.
The state is accessible via this.state
anywhere in the component, and it's also passed to render()
as a second argument.
class Demo extends Component {
constructor(props, context) {
super(props, context);
this.state = {
i: 0
}
}
render(props, state) {
return `Iteration ${state.i}`;
}
}
Type: Object
Function
Default: {}
Set a new state and update the output.
Note: setState()
works by extending the state via Object.assign()
, not replacing it with a new object. Therefore you can pass only changed values.
class Demo extends Component {
constructor(props, context) {
super(props, context);
this.state = {
i: 0
}
}
render(props, state) {
return `Iteration ${state.i}`;
}
componentDidMount() {
this.setState({
i: this.state.i + 1
});
}
}
The above example will increment the i
state property and render Iteration 1
as a result.
setState()
also accepts a function, which receives the current state as an argument.
The same effect of incrementing i
could be achieved in a following way:
this.setState(state => {
return {
i: state.i + 1
}
});
This is useful when setState()
calls are batched to ensure that you update the state in a stable way.
Refs can be used to get a direct reference to a component instance. This is useful, if you want to access its methods, for example.
Refs work by setting a special ref
prop on a component.
Prop's value must be a function, which receives a reference to a component as an argument or null
when the wanted component is unmounted.
Note: You can't get refs to stateless function components.
class Child extends Component {
render() {
return null;
}
hello() {
return 'Hello World';
}
}
class Parent extends Component {
constructor(props, context) {
super(props, context);
this.state = {
message: 'Ink is awesome'
};
}
render(props, state) {
const setChildRef = ref => {
this.childRef = ref;
};
return (
<div>
{message}
<Child ref={setChildRef}/>
</div>
)
}
componentDidMount() {
this.setState({
message: this.childRef.hello()
});
}
}
Context is like a global state for all components.
Every component can access context either via this.context
or inside render()
:
render(props, state, context) {
// context === this.context
}
To add new entries to context, add getChildContext()
method to your component:
class Child extends Component {
render() {
return this.context.message;
}
}
class Parent extends Component {
getChildContext() {
return {
message: 'Hello World'
};
}
render() {
return <Child/>;
}
}
If you don't need state, lifecycle methods, context and refs, it's best to use stateless function components for their small amount of code and readability.
Using stateful components:
class Demo extends Component {
render(props, state, context) {
return 'Hello World';
}
}
Using stateless function components:
const Demo = (props, context) => 'Hello World';
As you may have noticed, stateless function components still get access to props and context.
Surprise, surprise, our favorite <div>
and <span>
can be used in Ink components!
They are useful for grouping elements
The only difference between <div>
and <span>
is that <div>
inserts a newline after children.
const Demo = (
<div>
<A/>
<B/>
<C/>
</div>
);
There's also <br/>
, which serves the same purpose as on the web - a newline.
const Demo = (
<div>
Line 1
<br/>
Line 2
</div>
);
Ink also supports Fragments for returning multiple children from a component's render method.
const {h, Fragment} = require('ink');
render(
<Fragment>
<A/>
<B/>
<C/>
</Fragment>
);
Or using the shorthand syntax:
const {h} = require('ink');
render(
<>
<A/>
<B/>
<C/>
</>
);
To use the Fragments make sure you have pragmaFrag
in your configuration:
{
"babel": {
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "h",
"pragmaFrag": "h.Fragment"
}
]
]
}
}
You will also need Babel v7.0.0-beta.31 or above, which means you will also need to upgrade any other tools that use Babel to their compatible versions, like @babel/plugin-transform-react-jsx and @babel/core.
The <Color>
compoment is a simple wrapper around the chalk
API it supports all of the chalk methods as props
.
import {Color} from 'ink';
<Color rgb={[255, 255, 255]} bgKeyword="magenta">
Hello!
</Color>
<Color hex="#000000" bgHex="#FFFFFF">
Hey there
</Color>
The <Bold>
and <Underline>
components render their children bolded and underlined respectively.
import {Bold, Underline} from 'ink';
<Bold>
I am bold
</Bold>
<Underline>
I am underlined
</Underline>
MIT © Vadim Demedes
FAQs
React for CLI
The npm package ink receives a total of 393,933 weekly downloads. As such, ink popularity was classified as popular.
We found that ink demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.
Security News
Research
Socket's threat research team has detected five malicious npm packages targeting Roblox developers, deploying malware to steal credentials and personal data.