Git Product home page Git Product logo

karet's Introduction

Karet is a library that allows you to embed Kefir properties into React Virtual DOM. Embedding observable properties into VDOM has the following benefits:

  • It allows you to use only functional components, because you can then use observables for managing state and component lifetime, leading to more concise code.
  • It helps you to use React in an algorithmically efficient way:
    • The body of a functional component is evaluated only once each time the component is mounted.
    • Only elements that contain embedded properties are rerendered when changes are pushed through observables. An update to a deeply nested VDOM element can be an O(1) operation.

Using Karet couldn't be simpler. Usually you just import * as React from 'karet' and you are good to go.

npm version Gitter Build Status Code Coverage

To use Karet, you simply import it as React:

import * as React from 'karet'

and you can then write React components:

const App = () => (
  <div>
    <h1>What is the date and time</h1>
    {Kefir.interval(1000)
      .toProperty(() => {})
      .map(() => new Date().toString())}
  </div>
)

with VDOM that can have embedded Kefir properties. This works because Karet exports an enhanced version of createElement.

Note that the result, like the date and time display above, is just a React component. If you export it, you can use it just like any other React component and even in modules that do not import karet.

Here is a live example in CodeSandbox.

More links to live examples in the Calmm documentation Wiki.

Karet passes through the following exports from React:

  • Children as is. Note that with observable properties in children these functions may not do exactly what you want and you might want to lift them.
  • Fragment as is. It should work without problems.
  • createContext as is. Note that with Karet it is preferable to put observable properties into the context and let changes propagate through them rather than update the context. Also note that neither the provider nor the consumer are lifted by default. Lifting the consumer will likely cause no issues, but lifting the provider would eliminate observables from the value property and could cause problems. If you need to have observable children inside the provider, you can wrap the children inside a Fragment. See the CodeSandbox examples
    • Exam Events Form where the context is used to transmit the language as an observable atom, and
    • Form using Context where context is used to transmit form properties to form elements.
  • createElement which lifts Kefir properties in fragments and built-in HTML elements.
  • forwardRef as is.
  • useContext as is.

Notably the following are not exported:

  • Component and PureComponent, because with Karet you really don't need them and the render method can cause undesired component remounting when used with observable properties embedded into VDOM.
  • cloneElement does not work out of the box with elements containing Kefir properties. It should be possible to support it, however.
  • createRef is not exported, because Karet Util provides an alternative that works better with observable properties.
  • Suspense and lazy are not needed since import() returns a promise and you can just convert that to an observable property like this.
  • Most React hooks are not exported because they are not typically needed in Karet components. State should be handled in atoms. Effects can already be handled using observable properties as children or props.

Karet only lifts built-in HTML elements and fragments implicitly. The karet-lift attribute on a non-primitive element instructs Karet to lift the element.

For example, you could write:

import Select from 'react-select'
import * as React from 'karet'

// ...

const ReactSelect1 = ({value}) => (
  <Select
    karet-lift
    name="form-field-name"
    value={value}
    options={options}
    onChange={o => value.set(o && o.value)}
  />
)

to be able to use Select from React Select with embedded Kefir Atoms.

Here is a live example in CodeSandbox.

Karet only lifts built-in HTML elements and fragments implicitly. fromClass allows one to create lifted version of a given React component.

For example, you could write:

import Select from 'react-select'
import * as React from 'karet'

const SelectLifted = React.fromClass(Select)

const ReactSelect2 = ({value}) => (
  <SelectLifted
    name="form-field-name"
    value={value}
    options={options}
    onChange={o => value.set(o && o.value)}
  />
)

to be able to use Select from React Select with embedded Kefir Atoms.

Here is a live example in CodeSandbox.

The React inline elements transform is incompatible with Karet, because it bypasses React.createElement. OTOH, the React constant elements transform works just fine with Karet.

karet's People

Contributors

abstracthat avatar polytypic avatar rikutiira avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

karet's Issues

Provider to support children with observables

https://codesandbox.io/s/yw63o1zr1x

Here's an example case where you need to explicitly use <React.Fragment> to wrap children inside <Provider> because one of the children is an observable (due to U.when(obs, value)).

It'd be preferable if karet's Provider would work out of the box with observable children.

Another thing here is that for some reason React is warning about missing keys when using children inside the React.Fragment. I think there has been issues with same thing previously but those were fixed?

Accept ES7 Observables instead of Kefir

I know this library is specially modelled for Kefir streams, hence its name, but I think it would be great if any reactive streams library could be used (I personally like https://github.com/staltz/xstream, although Kefir looks great for my use-case), maybe using a different name and a different repository.

I've come up with the following changes: master...fiatjaf:b460d77fa8b38188e8003dc3de90bf161dc89d67

But they don't pass the tests (I've used xstream on the tests instead of Kefir), so I'm looking for help.

Support different VDOM implementations

Even now, Karet essentially just works with Preact in addition to React. Karet only uses createElement and Component and assumes that Components support (componentWillReceiveProps (is not strictly required),) componentWillMount, componentWillUnmount, render and forceUpdate methods (React setState is not used). It would be nice to make Karet directly configurable or parameterized with respect to the VDOM library. Suggestions on how to best expose such configurabilty or parameterization are welcome!

Implement template string helpers for detecting observers

Today if you write a templated string like

`my count is ${Kefir.sequentially(1000, [1, 2, 3, 4, 5])}`

karet will not know there's an observable inside the string and will print my count is [sequentially] instead.

If there was a helper function k, for example, for passing template strings to, that could identify which of the template strings' variables were observers and treat them like a children. Basically just turning a template string inside a React children as multiple different children would make this work, so instead you would have to write

k`my count is ${Kefir.sequentially(1000, [1, 2, 3, 4, 5])}`

And that's it.

What do you think? Should I submit a PR? What do you think would be the best way to implement such a feature?

Help needed in porting this to RxJS

Hi.

I decided to help build a port of this library to rxjs so it is also available for projects that rely on Rx as their FRP library.

So far I have managed to port most of the functionality but I am still 2 test cases away from succeeding.

One scenario is when a single react node has more than 1 Observable embedded.

The other is that the child context test does not render.

Any help or suggestion is greatly appreciated. I have been following this project for a while and I would like to contribute in the best way I can.

Thanks in advanced

This is the source code:
https://github.com/MarcCloud/xaret/blob/master/src/xaret.js

Pass through React Hooks

Current version of Karet does not pass through the proposed React Hooks that are available in 16.7.0-alpha.0. React Hooks do seem to work fine with Karet, e.g. using useContext. It would be useful to "support" React Hooks by passing through the exports like is done for a number of other React exports.

  • Create branch for Hooks support.
  • Upgrade React to version ^16.7.0-alpha.0 in the branch.
  • Add the pass through exports.
  • Make a prerelease version of Karet, e.g. version ^4.1.0-alpha.0, with the new exports.

Add jsx-runtime support

React 17 has introduced support for a new JSX transform.

The old JSX transform would do the following:

import React from 'react';

function App() {
  return <h1>Hello World</h1>;
}

code is transformed into:

import React from 'react';

function App() {
  return React.createElement('h1', null, 'Hello world');
}

The new JSX transform would do the following:

function App() {
  return <h1>Hello World</h1>;
}

code is transformed into:

// Inserted by a compiler (don't import it yourself!)
import {jsx as _jsx} from 'react/jsx-runtime';

function App() {
  return _jsx('h1', { children: 'Hello world' });
}

karet and lists of children

I'm working on a framework of my own, borrowing some ideas from karet, and I'm struggling with iterated lists, specifically the key attribute. What I'd like to be able to do is structure a list something like this:

const TodoList = ({ props$ }) => (
    <div>
        <h2>My Todos</h2>
        {props$.map(todos => todos.map(todo => (
            <TodoItem props$={/* how do i get a stream representing an individual todo? */}
                      key={todo.id} />
        )))}
    </div>
)

As the comment suggests, I'd like to provide the child component a stream representing the current state of the individual list item, but I'm not seeing a good way to structure the todos object that doesn't end up requiring a lot of iteration to create a stream of todo instances.

Based on the documentation, it appears you're mostly using the array indicies with lenses to decompose (? is this the right word?) the central Atom into into individual streams for each instance. The problem I'm having with that is using the array index as the key is a React anti-pattern. While the linked post suggests it could produce strange or broken results if you add or remove elements from the array, in my use case, it would also prevent the VDOM implementation from rearranging the currently existing DOM nodes and produce node updates instead. Let me explain:

If we have a list that looks like this:

<h1>My Favorite View Libraries</h1>
<ol>
    <li>React</li>
    <li>karet</li>
    <li>Vue.js</li>
</ol>

And we decide to move karet to the first spot to make it look like this:

<h1>My Favorite View Libraries</h1>
<ol>
    <li>karet</li>
    <li>React</li>
    <li>Vue.js</li>
</ol>

using array indexes would produce 2 text node changes, rather than rearranging the current DOM. This is inefficient, as a single move would be better than 2 text changes, and it denies us the ability to animate the DOM movement, as the DOM node doesn't actually change position.

I've played around with karet and I love the idea of being able to embed observables into the VDOM like that, and I'm trying to take it a bit further, such that all possible interactions in the DOM are expressed as embedded Observables, including DOM attributes & events, but I'm struggling with the API design around children, and I'm wondering how you solve this with karet / kefir.atom.

My current thought is to require something like this:

const TodoList = ({ props$ }) => (
    <div>
        <h2>My Todos</h2>
        {props$.map(todos => todos.order.map(key => (
            <TodoItem props$={props$.map(todos => todos.dict[key])}
                      key={key} />
        )))}
    </div>
)

Where todos looks like this:

const todos = {
    order: ['a', 'c', 'b'],
    dict: {
        a: todo1,
        b: todo2,
        c: todo3
    }
}

Is this a pattern you've used before with karet? Do you have any insight / suggestions for this API design?

Thanks for your time!

Breaking change: reconsider exports

The idea originally with Karet was to setup the exports so that one could just import from 'karet' instead of 'react'. IOW, so that the exports would be identical except that the Karet versions would support observables. While it is possible to make it so, the current hack is just incorrect and fixing it seems like waste, because it would require duplicating the React exports manually (and possibly matching them version by version).

What I mainly want is to be able to just import from 'karet' to make the default Babel JSX transform use createElement from Karet instead of React. This way one can conveniently decide, module by module, whether one uses Karet or plain React. In order to achieve that, Karet only needs to have a named export for createElement and one can then import * as React from 'karet'.

Changing the exports of Karet is a breaking change. The basic fix is fairly simple:

- import React from 'karet'
+ import * as React from 'karet'

Dropping the hacky default export means that dead code elimination will potentially work better (when React does it properly—currently it doesn't). Karet would only export createElement, fromKefir, and fromClass.

Also, if you are using named exports from React (the real React), you will then need to import those from the real 'react'. E.g.

import {Component} from 'react'

Consider supporting `React.cloneElement`

Karet currently only replaces the React.createElement function. If you use React.cloneElement to clone an element without observables in such a way that the new clone has observables in it, then the resulting element currently will not be lifted.

Respond to React lifecycle methods?

I've been playing around with some of Karet's ideas in a library of my own, but one of the things I'm struggling with is an idiomatic way to respond to componentDidUpdate with observables.

In my specific example, I want to run a syntax highlighter over a code element after the element's text has been updated, but because the FromClass component returned by createElement never appears in "userland", there's no good way to respond to React's lifecycle methods without dropping down into a "plain" React component, which isn't a terrible solution but something I was hoping to avoid.

Is this something you've run into? How have you solved it?

Do not call `forceUpdate` during mounting

The current implementation calls forceUpdate during initial mounting if all the observables produce values synchronously. The call causes no problems with React, but Preact doesn't like that. The call to forceUpdate is unnecessary and could be considered a bug that should be fixed (even before 2.0.0).

How does it work?

I'm puzzled by the magic that seems to be happening here (also, it's not working in my case, so it means I've misunderstood everything).

Reading the source didn't work. I can't wrap my mind around the fact that a function may know beforehand that a variable will be used on its body then automatically call a function on that variable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.