An open API service indexing awesome lists of open source software.

https://github.com/workiva/react-dart

Dart Bindings for React JS
https://github.com/workiva/react-dart

dart dart-wrapper javascript react-js reactjs

Last synced: 7 months ago
JSON representation

Dart Bindings for React JS

Awesome Lists containing this project

README

          

# Dart wrapper for [React JS](https://reactjs.org/)

[![Pub](https://img.shields.io/pub/v/react.svg)](https://pub.dev/packages/react)
![ReactJS v18.2.0](https://img.shields.io/badge/React_JS-18.2.0-green.svg)
[![Dart CI](https://github.com/Workiva/react-dart/workflows/Dart%20CI/badge.svg?branch=master)](https://github.com/Workiva/react-dart/actions?query=workflow%3A%22Dart+CI%22+branch%3Amaster)
[![React Dart API Docs](https://img.shields.io/badge/api_docs-react-blue.svg)](https://pub.dev/documentation/react/latest/)

_Thanks to the folks at [Vacuumlabs](https://www.vacuumlabs.com/) for creating this project! :heart:_

## Getting Started

### Installation

If you are not familiar with the ReactJS library, read this [react tutorial](https://reactjs.org/docs/getting-started.html) first.

1. Install the Dart SDK

```bash
brew install dart
```

2. Create a `pubspec.yaml` file in the root of your project, and add `react` as a dependency:

```yaml
name: your_package_name
version: 1.0.0
environment:
sdk: ^2.11.0
dependencies:
react: ^6.0.0
```

3. Install the dependencies using pub:

```bash
dart pub get
```

### Wire things up

#### HTML

In a `.html` file, include the javascript libraries
_(provided with this library for compatibility reasons)_ within your `.html` file,
and also add an element with an `id` to mount your React component.

This package now supports both React 17 and React 18. To opt into React 18, replace usages of this package's JS files with their new, React 18 versions (see table below).

The React 17 JS files are now deprecated, and will be removed in the next major version of this package, 8.0.0.

##### React 18
| Mode | Library | JS File Name |
|-------------|------------------|---------------------------------------------|
| Development | React & ReactDOM | packages/react/js/react.dev.js |
| Production | React & ReactDOM | packages/react/js/react.min.js |

##### React 17 (Deprecated)
| Mode | Library | JS File Name |
|-------------|------------------|---------------------------------------------|
| Development | React | packages/react/react.js |
| Development | ReactDOM | packages/react/react_dom.js |
| Production | React & ReactDOM | packages/react/react_with_react_dom_prod.js |
| Production | React | packages/react/react_prod.js |
| Production | ReactDOM | packages/react/react_dom_prod.js |

Lastly, add the `.js` file that Dart will generate. The file will be the name of the `.dart` file that contains your `main` entrypoint, with `.js` at the end.

```html





Here will be react content



```

> __Note:__ When serving your application in production, use `packages/react/js/react.min.js`
file instead of the un-minified `react.dev.js` shown in the example above.

#### Dart App

Once you have an `.html` file containing the necessary `.js` files, you can initialize React
in the `main` entrypoint of your Dart application.

```dart
import 'dart:html';

import 'package:react/react.dart';
import 'package:react/react_dom.dart' as react_dom;

main() {
// Something to render... in this case a simple

with no props, and a string as its children.
var component = div({}, "Hello world!");

// Render it into the mount node we created in our .html file.
react_dom.render(component, querySelector('#react_mount_point'));
}
```

## Build Stuff

### Using browser native elements

If you are familiar with React (without JSX extension) React-dart shouldn't surprise you much. All elements are defined as
functions that take `props` as first argument and `children` as optional second argument. `props` should implement `Map` and `children` is either one React element or `List` with multiple elements.

```dart
var aDiv = div({"className": "something"}, [
h1({"style": {"height": "20px"}}, "Headline"),
a({"href":"something.com"}, "Something"),
"Some text"
]);
```

For event handlers you must provide function that takes a `SyntheticEvent` _(defined in this library)_.

```dart
var aButton = button({"onClick": (SyntheticMouseEvent event) => print(event)});
```

### Defining custom components

1. Define custom class that extends Component2 and implements - at a minimum - `render`.

```dart
// cool_widget.dart

import 'package:react/react.dart';

class CoolWidgetComponent extends Component2 {
render() => div({}, "CoolWidgetComponent");
}
```

2. Then register the class so ReactJS can recognize it.

```dart
var CoolWidget = registerComponent2(() => CoolWidgetComponent());
```

> __Warning:__ `registerComponent2` should be called only once per component and lifetime of application.

3. Then you can use the registered component similarly as native elements.

```dart
// app.dart

import 'dart:html';

import 'package:react/react.dart';
import 'package:react/react_dom.dart' as react_dom;

import 'cool_widget.dart';

main() {
react_dom.render(CoolWidget({}), querySelector('#react_mount_point'));
}
```

#### Custom component with props

```dart
// cool_widget.dart

import 'package:react/react.dart';

class CoolWidgetComponent extends Component2 {
@override
render() {
return div({}, props['text']);
}
}

var CoolWidget = registerComponent2(() => CoolWidgetComponent());
```

```dart
// app.dart

import 'dart:html';

import 'package:react/react.dart';
import 'package:react/react_dom.dart' as react_dom;

import 'cool_widget.dart';

main() {
react_dom.render(CoolWidget({"text": "Something"}), querySelector('#react_mount_point'));
}
```

#### Custom component with a typed interface

> __Note:__ The typed interface capabilities of this library are fairly limited, and can result in
extremely verbose implementations. We strongly recommend using the
[OverReact](https://pub.dev/packages/over_react) package - which
makes creating statically-typed React UI components using Dart easy.

```dart
// cool_widget.dart
typedef CoolWidgetType({String headline, String text, int counter});

var _CoolWidget = registerComponent2(() => CoolWidgetComponent());

CoolWidgetType CoolWidget({String headline, String text, int counter}) {
return _CoolWidget({'headline':headline, 'text':text});
}

class CoolWidgetComponent extends Component2 {
String get headline => props['headline'];
String get text => props['text'];
int get counter => props['counter'];

@override
render() {
return div({},
h1({}, headline),
span({}, text),
span({}, counter),
);
}
}
```

```dart
// app.dart

import 'dart:html';

import 'package:react/react.dart';
import 'package:react/react_dom.dart' as react_dom;

import 'cool_widget.dart';

void main() {
react_dom.render(
myComponent(
headline: "My custom headline",
text: "My custom text",
counter: 3,
),
querySelector('#react_mount_point')
);
}
```

#### React Component Lifecycle methods

The `Component2` class mirrors ReactJS' `React.Component` class, and contains all the same methods.

> See: [ReactJS Lifecycle Method Documentation](https://reactjs.org/docs/react-component.html) for more information.

```dart
class MyComponent extends Component2 {
@override
void componentWillMount() {}

@override
void componentDidMount() {}

@override
void componentWillReceiveProps(Map nextProps) {}

@override
void componentWillUpdate(Map nextProps, Map nextState) {}

@override
void componentDidUpdate(Map prevProps, Map prevState) {}

@override
void componentWillUnmount() {}

@override
bool shouldComponentUpdate(Map nextProps, Map nextState) => true;

@override
Map getInitialState() => {};

@override
Map getDefaultProps() => {};

@override
render() => div({}, props['text']);
}
```

#### Using refs and findDOMNode

The use of component `ref`s in react-dart is a bit different from React JS.

* You can specify a ref name in component props and then call ref method to get the referenced element.
* Return values for Dart components, DOM components and JavaScript components are different.
* For a Dart component, you get an instance of the Dart class of the component.
* For primitive components (like DOM elements), you get the DOM node.
* For JavaScript composite components, you get a `ReactElement` representing the react component.

If you want to work with DOM nodes of dart or JS components instead,
you can call top level `findDOMNode` on anything the ref returns.

```dart
var DartComponent = registerComponent2(() => _DartComponent());
class _DartComponent extends Component2 {
@override
render() => div({});

void someInstanceMethod(int count) {
window.alert('count: $count');
}
}

var ParentComponent = registerComponent2(() => _ParentComponent());
class _ParentComponent extends Component2 {
final inputRef = createRef(); // inputRef.current is the DOM node.
final dartComponentRef = createRef<_DartComponent>(); // dartComponentRef.current is the instance of _DartComponent

@override
void componentDidMount() {
print(inputRef.current.value); // Prints "hello" to the console.

dartComponentRef.current.someInstanceMethod(5); // Calls the method defined in _DartComponent
react_dom.findDOMNode(dartComponentRef); // Returns div element rendered from _DartComponent

react_dom.findDOMNode(this); // Returns root dom element rendered from this component
}

@override
render() {
return div({},
input({"ref": inputRef, "defaultValue": "hello"}),
DartComponent({"ref": dartComponentRef}),
);
}
}
```

### Example Application

For more robust examples take a look at our [examples](https://github.com/Workiva/react-dart/tree/master/example).

## Unit Testing Utilities

[lib/react_test_utils.dart](https://github.com/Workiva/react-dart/blob/master/lib/react_test_utils.dart) is a
Dart wrapper for the [ReactJS TestUtils](https://reactjs.org/docs/test-utils.html) library allowing for unit tests
to be made for React components in Dart.

Here is an example of how to use `package:react/react_test_utils.dart` within a Dart test.

```dart
import 'package:test/test.dart';
import 'package:react/react.dart' as react;
import 'package:react/react_dom.dart' as react_dom;
import 'package:react/react_test_utils.dart' as react_test_utils;

class MyTestComponent extends react.Component2 {
@override
Map getInitialState() => {'text': 'testing...'};

@override
render() {
return react.div({},
react.button({'onClick': (_) => setState({'text': 'success'})}),
react.span({'className': 'spanText'}, state['text']),
);
}
}

var myTestComponent = react.registerComponent2(() => new MyTestComponent());

void main() {
test('should click button and set span text to "success"', () {
var component = react_test_utils.renderIntoDocument(myTestComponent({}));

// Find button using tag name
var buttonElement = react_test_utils.findRenderedDOMComponentWithTag(
component, 'button');

// Find span using class name
var spanElement = react_test_utils.findRenderedDOMComponentWithClass(
component, 'spanText');

var buttonNode = react_dom.findDOMNode(buttonElement);
var spanNode = react_dom.findDOMNode(spanElement);

// Span text should equal the initial state
expect(spanNode.text, equals('testing...'));

// Click the button and trigger the onClick event
react_test_utils.Simulate.click(buttonNode);

// Span text should change to 'success'
expect(spanNode.text, equals('success'));
});
}
```

## Contributing

Format using
```bash
dart format -l 120 .
```

While we'd like to adhere to the recommended line length of 80, it's too short for much of the code
repo written before a formatter was use, causing excessive wrapping and code that's hard to read.

So, we use a line length of 120 instead.

### Running Tests

#### dart2js

```bash
dart run build_runner test --release -- --preset dart2js
```

> NOTE: When using Dart SDK < 2.14.0, use `--preset dart2js-legacy` instead.

#### Dart Dev Compiler ("DDC")

```bash
dart run build_runner test -- --preset dartdevc
```

> NOTE: When using Dart SDK < 2.14.0, use `--preset dartdevc-legacy` instead.

### Building React JS Source Files

Make sure the packages you need are dependencies in `package.json` then run:
```bash
yarn install
```

After modifying files any files in ./js_src/, run:

```bash
yarn run build
```