npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

declarative-components

v0.0.44-development

Published

Declarative Components for React. Create asynchronous UI:s with ease.

Downloads

49

Readme

Declarative Components

Declarative Components for React. Create asynchronous UI:s with ease.

Installation

npm install declarative-components

Why?

Cohesive UI:s

Easily compose complex UI:s with declarative components. Instead of storing the state in the component the state is stored internally inside the declarative components. This way a change to variable is reflected only to children without the need to re-render the whole root component.

import { Variable, Constant, Async } from "declarative-components"

const App = () => (
  <div>
    <h1>Welcome to my photos</h1>
    <Constant value={Async.GET("https://jsonplaceholder.typicode.com/photos")}>
      {photos => (
        <div>
          <h2>I have {photos.length} photos in total</h2>
          <Variable initialValue={10}>
            {(numberOfPhotos, setNumberOfPhotos) => (
              <Fragment>
                <div>
                  <button
                    onClick={() => {
                      setNumberOfPhotos(numberOfPhotos + 1)
                    }}
                  >
                    Show more
                  </button>
                </div>
                {photos.slice(0, numberOfPhotos).map(photo => (
                  <Variable key={photo.id} initialValue={100}>
                    {(width, setWidth) => (
                      <img
                        onClick={() => {
                          setWidth(width + 10)
                        }}
                        width={width}
                        src={photo.url}
                      />
                    )}
                  </Variable>
                ))}
              </Fragment>
            )}
          </Variable>
        </div>
      )}
    </Constant>
  </div>
)
)

To accomplish this kind of behavior in the traditional React style we would have to create stateful subcomponents for rendering the photo and also for rendering the list.

import { Async } from "declarative-components"

class Photo extends React.Component {
  state = {
    width: this.props.initialWidth
  }
  increaseWidth = () => {
    this.setState(({ width }) => {
      return {
        width: width + 10
      }
    })
  }
  render() {
    return <img onClick={this.increaseWidth} width={this.state.width} src={this.props.src} />
  }
}
class PhotoList extends React.Component {
  state = {
    numberOfPhotos: this.props.initialNumberOfPhotos
  }
  increaseNumberOfPhotos = () => {
    this.setState(({ numberOfPhotos }) => {
      return {
        numberOfPhotos: numberOfPhotos + 1
      }
    })
  }
  render() {
    return (
      <div>
        <button onClick={this.increaseNumberOfPhotos}>Show more</button>
        {this.props.photos.slice(0, this.state.numberOfPhotos).map(photo => (
          <Photo src={photo.url} key={photo.id} initialWidth={100} />
        ))}
      </div>
    )
  }
}

class App extends React.Component {
  state = { photos: null }
  render() {
    return (
      <div>
        <h1>Welcome to my photos</h1>
        {this.state.photos != null && (
          <div>
            <h2>I have {this.state.photos.length} photos in total</h2>
            <PhotoList photos={this.state.photos} initialNumberOfPhotos={100} />
          </div>
        )}
      </div>
    )
  }
  componentDidMount() {
    Async.GET("https://jsonplaceholder.typicode.com/photos").then(photos => {
      this.setState({ photos })
    })
  }
}
export default App

Certainly there is nothing wrong with this type of division of logic to smaller components and some might even prefer it this way. With declarative approach the code is more condensed and the behavior of the component is more clear at a glance.

And actually in the above case the h1 header is still rendered twice versus the declarative approach where it is only rendered once.

Optimizable

Now someone would say that it's easy to optimize the traditional React approach by making the Photo component a PureComponent to avoid the full render of the list every time that the numberOfPhotos is changed. Same can be achieved with the declarative way without the need to create a stateful component.

import { Variable, Constant, Async } from "declarative-components"

class Photo extends React.PureComponent {
  render() {
    return (
      <Variable initialValue={100}>
        {(width, setWidth) => (
          <img
            alt=""
            onClick={() => {
              setWidth(width + 10)
            }}
            width={width}
            src={this.props.photo.url}
          />
        )}
      </Variable>
    )
  }
}

const App = () => (
  <div>
    <h1>Welcome to my photos</h1>
    <Constant value={Async.GET("https://jsonplaceholder.typicode.com/photos")}>
      {photos => (
        <div>
          <h2>I have {photos.length} photos in total</h2>
          <Variable initialValue={10}>
            {(numberOfPhotos, setNumberOfPhotos) => (
              <Fragment>
                <div>
                  <button
                    onClick={() => {
                      setNumberOfPhotos(numberOfPhotos + 1)
                    }}
                  >
                    Show more
                  </button>
                </div>
                {photos.slice(0, numberOfPhotos).map(photo => (
                  <Photo photo={photo} />
                ))}
              </Fragment>
            )}
          </Variable>
        </div>
      )}
    </Constant>
  </div>
)

Drop-In Asynchronous

Values can be of synchronous or asynchronous nature (Promise, Observable or concrete value), it makes no difference. You will get a progress and asyncType injected to children renderer from where you can see what the progress (Progressing, Error, Idle) state and type (Create, Remove, Update, Load) are.

import { Variable, Form, Async, Controlled, Operation } from "declarative-components"

const App = () => (
  <Variable initialValue={1}>
    {(todoId, setTodoId) => (
      <Fragment>
        <h1>
          Edit todo {todoId}{" "}
          <button
            onClick={() => {
              setTodoId(todoId + 1)
            }}
          >
            Next
          </button>
        </h1>
        <Controlled value={Async.GET("https://jsonplaceholder.typicode.com/todos/" + todoId)}>
          {(todo, loadProgress, asyncType) => (
            <div
              style={{
                opacity: loadProgress === Async.Progress.Progressing ? 0.5 : 1
              }}
            >
              <Operation
                onChange={() => {
                  alert("saved")
                }}
              >
                {(updateTodo, updateProgress) => (
                  <Form
                    value={todo}
                    onChange={todo => {
                      updateTodo(Async.PUT("https://jsonplaceholder.typicode.com/todos/" + todoId, todo))
                    }}
                  >
                    {({ Root }) => (
                      <Fragment>
                        <Root>
                          {({ Input, Validation }) => (
                            <Fragment>
                              <Validation for="title">
                                {validation => (
                                  <span style={{ color: validation ? "red" : undefined }}>
                                    <label>Todo title</label>
                                    <Input minLength={3} notEmpty={true} maxLength={100} name="title" />
                                  </span>
                                )}
                              </Validation>
                            </Fragment>
                          )}
                        </Root>
                        <button type="submit" disabled={updateProgress === Async.Progress.Progressing}>
                          Save Todo
                        </button>
                      </Fragment>
                    )}
                  </Form>
                )}
              </Operation>
            </div>
          )}
        </Controlled>
      </Fragment>
    )}
  </Variable>
)

Basic state components

Basic logics can be composed using these components.

| | Has value | Async.Type | Controlled from | Descripton | Shows placeholder when | |------------|-----------|------------------|-----------------|------------------------------------------------------------------------------------------------------------|----------------------------| | Constant | Yes | Load | - | Resolves once and renders children | No concrete value | | Variable | Yes | Load, Upsert | Inside | Resolves initialValue and renders children. When resolving new value injects progress to children. | No concrete value or error | | Controlled | Yes | Load | Outside | Resolves value every time it changes. Injects progress to children when there is no value (First resolve). | No concrete value or error | | Operation | No | Upsert | - | Injects progress of operation to children. | Never |

Acknowledgements

Library boilerplate starter: https://github.com/alexjoverm/typescript-library-starter

Dependencies