https://github.com/uqbar-project/njsx
A customizable and declarative interface for creating React and React Native components without JSX syntax.
https://github.com/uqbar-project/njsx
builder customizable dsl functional js jsx react-native reactjs syntactic-sugar ts tsx
Last synced: 20 days ago
JSON representation
A customizable and declarative interface for creating React and React Native components without JSX syntax.
- Host: GitHub
- URL: https://github.com/uqbar-project/njsx
- Owner: uqbar-project
- License: other
- Created: 2017-03-14T04:29:13.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2023-01-27T04:28:12.000Z (about 2 years ago)
- Last Synced: 2025-03-07T20:07:49.611Z (about 2 months ago)
- Topics: builder, customizable, dsl, functional, js, jsx, react-native, reactjs, syntactic-sugar, ts, tsx
- Language: TypeScript
- Size: 74.2 KB
- Stars: 35
- Watchers: 18
- Forks: 4
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
Awesome Lists containing this project
README
# No-JSX
[](https://travis-ci.org/uqbar-project/njsx)
[](https://badge.fury.io/js/njsx)A pure function based interface for creating [React](https://facebook.github.io/react/) and [React Native](https://facebook.github.io/react-native/) components without *JSX* tags.
If you love *React* but don't quite like the embeded *HTML* tags this library may be what you are looking for. Construct your components with code only in a clean, declarative way.
```js
const myView = () =>
div.app(
div.header(
img({src: logo, alt:'logo'}),
h2('Welcome to NJSX')
)
)()
```
-------------------------------------------------#### Table of Content
- [Installation](#installation)
- [Usage](#usage)
- [Getting a Builder](#getting-a-builder)
- [Default Component Builders](#default-component-builders)
- [Third-Party Component Builders](#third-party-component-builders)
- [Creating Elements](#creating-elements)
- [Refining Builders](#refining-builders)
- [Builder Arguments](#builder-arguments)
- [Dynamic Selectors](#dynamic-selectors)
- [Argument Transformation](#argument-transformation)
- [Point-free](#point-free)
- [Working with older versions](#working-with-older-versions)
- [Contributions](#contributions)
- [License](#license)-------------------------------------------------
## Installation
*NJSX* is available on [npm](https://www.npmjs.com), just pick between the *React* and *React Native* flavours and add it to your project's dependencies.
**For React Projects:**
```bash
npm install njsx-react --save
```**For React Native Projects:**
```bash
npm install njsx-react-native --save
```## Usage
*NJSX* is super easy to use: It's all about **Builder Functions**.
You can use *Builders* to to cleanly instantiate *React* and *React Native* elements, or further refine your component configuration just by applying them.
### Getting a Builder
#### Default Component Builders
*NJSX* provides *Builder Functions* for all the default *React* and *React Native* components. Just import whatever element you need from the `react` or `react-native` modules and you are ready to go:
```js
// React project
import {div, p} from 'njsx-react'// React Native project
import {View, Text} from 'njsx-react-native'
```#### Third-Party Component Builders
NJSX is not just for default components! You can get a builder for **any** component just by wrapping it with the `njsx` adapter.
```js
// This is NJSX core. Both njsx-react and njsx-react-native use it to define their builders.
import njsx from 'njsx'
import {SomeThirdPartyComponent} from 'someLibrary'const SomeFunctionalComponent = (props) => ...
class SomeStatefulComponent extends React.Component {
render() { ... }
}// These are all valid Component Builders.
const someComponent = njsx(SomeComponent)
const someFunctionalComponent = njsx(SomeFunctionalComponent)
const someStatefulComponent = njsx(SomeFunctionalComponent)// You can even use a string as component type (although it's not recommended):
const aDivBuilder = njsx('div')
```### Creating Elements
Each *NJSX* builder, once **applied with no arguments**, will return a **ReactElement** just as if you had used the component inside a *JSX* tag:
```jsx
import {div} from 'njsx-react'// These two lines are equivalent.
div()
```This means that *JSX* and *NJSX* elements are completely interchangeable. You can use components created from builders as children for *JSX*'s tags, or refine a builder with tag shaped children, so you can try *NJSX* on any react based project and integrate it gradually.
### Refining Builders
Of course, an empty element is not that useful, so how do you customize it?
When a *Builder* is applied with **one or more arguments**, these will be used to configure the building component. Refining a *Builder* this way returns another ***Builder***, so you can keep refining your component any number of times.
```jsx
import {p} from 'njsx-react'p('some text')
p('some', ' ', 'text')
p('some')(' ')('text')
p(['some', ' ', 'text'])// All these lines build the same:
some text
```It's important to note that refining a builder causes **no side effects or state changes at all**. This means you can safely reuse *Builders*, or partially refine one and pass it forward.
#### Builder Arguments
*Builders* will get refined in a different way, depending on what arguments you apply them with:
- `Basic Objects` are treated as **Component Properties**. Refining a builder with a second set of properties will result in the merge of both, favoring the later in case of repetition.
```jsx
img({src: path, onClick: cb})
img({src: path}, {onClick: cb})
img({src: thisWillBeLost, onClick: cb})({src: path})// All these lines build the same:
![]()
```- `Strings`, `Numbers`, `React Elements` and even other `Builders` will become **Component Children**.
```jsx
div(
div('the answer is ', 42) // <- No need for building it.
)// This line builds:
the answer is 42
```Notice that, since *Builders* can be children too, most of the time you won't be needing to apply them with no arguments to instantiate elements.
- `null`, `undefined` and `Booleans` will be ignored. This allows for a clean way to conditionally set properties and children using `&&` and `||`.
```jsx
div(null)
div(undefined)
div(false && "this won't show")//All these lines the same:
```- `Arrays` of any valid argument will be handled as a sequence of refinements.
```jsx
const guards = ['Nobby', 'Colon', 'Carrot']
ul(guards.map(guard => li(guard)))
ul(guards.map(li))//All these lines the same:
- {guards.map(guard =>
- {guard} )}
```
- Finally, you can also pass a `Refinement Function`, which should take the previous *Component Properties* (including the `children` field) and return the next one.
```jsx
const myRefinement = (src, text) => (prev) =>
{...prev, {src, children: text} }
img(myRefinement(foo, bar))
// This line builds:
bar
```
To wrap it all, any unsuported argument application will raise a `TypeError`.
#### Dynamic Selectors
You can also refine a *Builder* by accessing any keyword as if it was a property. A common use for this is to treat the keyword as a `className`, so you can add classes to components by just naming them:
```jsx
p.highlighted.small("Nice!")
p['highlighted']['small']("Nice!")
p['highlighted small']("Nice!")
p("Nice!").highlighted['.small']
//All these lines build the same:
Nice!
```
Treating these selectors as class names is the default behavior of *NJSX*, but you can change it to whatever you want by changing the `NJSXConfig. dynamicSelectorHandler` setting:
```jsx
import { NJSXConfig } from 'njsx'
// You can set any function that receives the key and returns a valid argument.
NJSXConfig.dynamicSelectorHandler = (key: string) => key
div.foo.bar
// This would yield
// That means you could also return a refining function!
NJSXConfig.dynamicSelectorHandler = (id: string) => (prev) =>
{...prev, id}
div.baz
// This would yield
// You can also disable the whole thing by setting it to undefined.
NJSXConfig.dynamicSelectorHandler = undefined
```
Notice that this feature can only be used on [environments that support *ES6's Proxy*](https://kangax.github.io/compat-table/es6/#test-Proxy) so, sadly, it's not available on *React-Native* projects.
## Argument Transformation
You don't like the way arguments are being handled? No problem! You can customize the way *NJSX's Builders* interpret arguments to fine tune it to your needs.
The `NJSXConfig` object can be used to specify **Argument Transformations**, which are just functions that take each argument and return whatever you want that argument to be. These functions are automatically called each time a *Builder* is applied.
```jsx
import { NJSXConfig } from 'njsx'
import {p} from 'njsx-react'
const translations = {
"this should be translated": "ook"
}
NJSXConfig.argumentTransformations.push( arg =>
typeof arg === 'string' && arg.startsWith('!')
? translations[arg]
: arg
)
p("!this should be translated")
// This build:
ook
```
Please take into account that **all transformations are reduced on every argument**, so don't overdo it and mind the order.
*NJSX* comes with some of these transformations set up by default:
- In *React* projects, **Strings starting with a dot** will be interpreted as a *classNames*:
```jsx
div('.foo .bar')(
'Some content'
)
// This builds:
```
- In *React-Native* projects, **StyleSheet** arguments are interpreted as *styles* (Just import `StyleSheet` from `njsx-react-native` instead of `react-native`).
```jsx
import {StyleSheet, View, Text} from 'njsx-react-native'
// Same StyleSheet interface
StyleSheet.create({
container: { /* ...your regular react-native styles... */ }
description: { /* ...your regular react-native styles... */ }
})
View(styles.container)(
Text(style.description)("These are styled!")
)
```
If you rather all your arguments to just be interpreted as they are, you can disable this feature by setting the `NJSXConfig.argumentTransformations` to an empty array.
## Point-free
Think point-free composition in your render function is a `pipe` dream? Think again, you can use `njsx` to compose components in a point-free style to help with the readability of deeply nested react components:
```jsx
```
Becomes:
```js
import { compose } from 'rambda'
compose(
Provider({ store }),
PersistGate({ loading: null, persistor }),
BrowserRouter,
Route
)({ path: '/', component: App })()
```
Please note that `compose` and `pipe` functions vary in implementation and not all will work with `njsx`, for example, `lodash/fp` seems to have issues at the moment, while `rambda` is working without issue.
## Working with older versions
If you are working with an older release this documentation might not be of any use to you. We follow the [semantic versioning standard](https://semver.org/) so any difference on the Major version will probably imply some incompatibilities. Please refer to [your version's branch](https://github.com/uqbar-project/njsx/releases) README file.
## Contributions
Please report any bugs, requests or ideas on [the issues section of this repository](https://github.com/uqbar-project/njsx/issues) and we will try to see to it as soon as possible.
Pull requests are always welcome! Just try to keep them small and clean.
## License
This code is open source software licensed under the [ISC License](https://opensource.org/licenses/ISC) by [The Uqbar Foundation](http://www.uqbar-project.org/). Feel free to use it accordingly.