Coder Social home page Coder Social logo

fahad19 / proppy Goto Github PK

View Code? Open in Web Editor NEW
941.0 22.0 27.0 1.31 MB

Functional props composition for UI components (React.js & Vue.js)

Home Page: https://proppyjs.com

License: MIT License

CSS 8.99% HTML 8.62% JavaScript 21.44% Makefile 2.02% TypeScript 58.93%
react vue preact javascript functional-programming props redux rxjs

proppy's Introduction

ProppyJS

Functional props composition for components
A 1.5kB library integrating with your favourite UI framework

Built with โค๏ธŽ by @fahad19 and contributors

What is ProppyJS?

ProppyJS is a tiny 1.5kB JavaScript library for composing props (object that components receive to render themselves).

It comes with various integration packages giving you the freedom to use it popular rendering libraries.

What does it do?

ProppyJS flow of props

The idea is you express the behaviour of your Component as props first, and then connect it to your Component (which can be either React, Vue.js or Preact) using the same API of Proppy.

The API gives you access to other application-wide dependencies too (like a store using Redux) for convenience anywhere in the components tree.

Packages

Package Status Size Description
proppy proppy-status 1.5K Core package
proppy-react proppy-react-status 1.0K React integration
proppy-vue proppy-vue-status 0.7K Vue.js integration
proppy-preact proppy-preact-status 1.1K Preact integration
proppy-redux proppy-redux-status 0.6K Redux integration
proppy-rx proppy-rx-status 0.6K RxJS integration

Quick start

Installation

With npm:

$ npm install --save proppy

Basics

Factories

For the simplicity of this example, we can start with withProps function from proppy first:

import { withProps } from 'proppy';

const P = withProps({ counter: 1 });

Instances

Now we can get an instance from our factory function by calling it:

const p = P();

To access the props synchronously:

console.log(p.props); // { counter: 1 }

Subscription

Given the nature of our instance having static props, if we subscribe to it, it will emit the props only once:

const unsubscribe = p.subscribe(props => console.log(props));
// { counter: 1 }

To unsubscribe, just call the returned function:

unsubscribe();

Destroy

To cancel all listeners and clear the internal state, just call:

p.destroy();

Dynamic props

There are times when your props require a bit of interactivity. Imagine your component wants to render the current state of counter, but also want to be able to update the value.

We can achieve that using the withState function for example:

import { withState } from 'proppy';

// withState(stateName, setterName, initialState)
const P = withState('counter', 'setCounter', 0);

const p = P();

Initially, the instance will only show you the default initial state for counter:

console.log(p.props);
// { counter: 0, setCounter: Function }

But we can update it too:

p.props.setCounter(5);

console.log(p.props);
// { counter: 5, setCounter: Function }

If you subscribed to the instance, it will keep emitting the new props every time a change occurs:

p.subscribe(props => console.log(props));
// { counter: 0, setCounter: Function }
// { counter: 5, setCounter: Function }

Composition

Proppy also ships with a handy compose function which allows you to compose multiple factories into a single one:

import { compose, withProps, withState } from 'proppy';

const P = compose(
  withProps({ foo: 'foo value' }),
  withProps({ bar: 'bar value' }),
  withState('counter', 'setCounter', 0)
);

Once you create an instance, all the props from those factories will be accessible in a single object:

const p = P();

console.log(p.props);
// {
//   foo: 'foo value',
//   bar: 'bar value',
//   counter: 0,
//   setCounter: Function
// }

Providers

Providers are application-wide dependencies that all Proppy instances can access anywhere.

They are useful especially when you want to maintain them in a single place, but you want Components from anywhere to access them at will.

Imagine setting your application's name, in a providers object like this:

const myProviders = {
  appName: 'My Super Awesome App!'
};

Now when composing your props with Proppy, you want to be able to access them:

import { compose, withProps } from 'proppy';

const P = compose(
  withProps({ foo: 'foo value' }),

  // `withProps` can also generate props using a function
  withProps((props, providers) => {
    return {
      name: providers.appname
    };
  })
);

// pass `myProviders` when calling the factory
const p = P(myProviders);

Your Proppy instance now has these props:

console.log(p.props);
// {
//   foo: 'foo value',
//   name: 'My Super Awesome App!'
// }

Rendering with React

Proppy integrates with React via proppy-react package.

You can install it with npm first:

$ npm install --save react proppy-react

Set providers

Then we need to set our custom providers in our root React component:

// components/Root.js
import React from 'react';
import { ProppyProvider } from 'proppy-react';

import Counter from './Counter';

const myProviders = {};

export default function Root() {
  return (
    <ProppyProvider providers={myProviders}>
      <Counter />
    </ProppyProvider>
  );
}

Attach factory

Now from anywhere in our components tree, we can use the attach higher-order component to connect your Proppy factory to React component:

// components/Counter.js
import React from 'react';
import { attach } from 'proppy-react';
import { compose, withProps, withState } from 'proppy';

const P = compose(
  withProps({ foo: 'foo value' }),
  withState('counter', 'setCounter', 0),
);

function Counter(props) {
  const { foo, counter, setCounter } = props;

  return (
    <div>
      <p>Foo: {foo}</p>

      <p>Counter: {counter}</p>

      <button onClick={() => setCounter(counter + 1)}>
        Increment
      </button>
    </div>
  );
}

export default attach(P)(Counter);

You can do the same with Vue.js and Preact too via proppy-vue and proppy-preact packages respectively.

Inspiration

Original inspiration for this project has been recompose. If your project is using React, you should definitely check it out.

Learn more about the differences between Proppy and other libraries (like Redux and Recompose) in our FAQ page.

License

MIT

proppy's People

Contributors

antonk52 avatar chimon2000 avatar dakota avatar fahad19 avatar jchn avatar konsumer avatar leandrooriente avatar reaktivo avatar sobolevn 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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proppy's Issues

typescript OOP support???

I know, I know, seems to be hard could use proppy with TS in a type safe way but are you planning support typescript in a future???...thank you

Funny examples

Funny how "without" examples are written using the longest possible syntax while "with" examples utilizing the shortest syntax to make it look cleaner when it's not.

Possible to update provider state with other Factory

Love this project but had a question around being able to update the provider store with one of the factory functions provided. I know this would replicate some of how redux works but it would be nice if there was a way to do it with the built in factory functions almost like a withProviderHandlers.

Sharing state between components

proppy1

Please refer to the attached gif file.

I tried it the other way, but this.props.setMessages(something) did not change this.props.messages, with
const Proppy = compose( withState('messages', 'setMessages', []) )
and
attach(Proppy)(MyComponent).

It's not complicated at all, so I might have done something wrong, but I do not know what it is. What parts should I check again?

Consider renaming didSubscribe to didMount

Brilliant package. I really like this.

Please consider renaming didSubscribe to didMount so that it is crystal clear what this is doing.

I'm not sure it is clear immediately to users how this aligns to React or Vue. The fact that didSubscribe isn't handled server side means that 'For handling side-effects upon first subscription.' isn't true in every circumstance.

The added advantage is that new developers who have not seen proppy, but have used recompose should immediately understand what 'didMount' does.

Usage with global providers and preact-router

Hi, I am really excited about replacing react+recompose+react-router with much lighter alternatives, but I am having some issues fitting it all together. I am coming from react/recompose, so I'm sure I am doing something just a bit wrong, but I can't seem to figure it out. Any ideas would be much appreciated.

I have a demo that illustrates what I am trying to accomplish. I want global provider for modal & user state. Here is a code sandbox.

Basically, I can't seem to work out subscribing to a global state and updating the app.

To reproduce, click "login" and login with any email/password. This should trigger 3 provider state changes:

  • show modal with login form
  • update user
  • hide modal with login form

which in turn should change these component-state things:

  • change menu items in Header
  • show/hide modal in Header
  • change routes for / from PageHome to PageDashboard (for logged in users)

I did this, which seems like a terrible hack:

// when global state changes, render app
if (typeof window !== 'undefined') {
  providers.subscribe(props => {
    console.log('PROVIDERS', props)
    render(<App />, document.body)
  })
}

It works a little better at refreshing when it should show the modal, but only about half the time (show modal works, but not hide.)

Initial user-state seem to work ok (derive user from JWT works fine) so you can login, then refresh page and it looks how it should. You can click LOGOUT, and refresh, and it kills the token and user-state, and goes back to anonymous view.

Strange warnings: Cannot find source file

Hey, thanks for cool lib. It implements nice separation of concerns approach!

I'm using proppy + proppy-preact. and I received strange warnings when I'm trying to import proppy's utils into my project. Everything works fine, but these warnings trash my console and I want to remove them.

Here is an example:

(Emitted value instead of an instance of Error) Cannot find source file '../src/compose.ts': Error: Can't resolve '../src/compose.ts' in '/Users/user/project/node_modules/proppy/lib'
 @ ./node_modules/proppy/lib/index.js 3:16-36
 @ ./src/components/pages/calculator/Calculator.js
 @ ./src/components/pages/index.js
 @ ./src/components/app.js
 @ ./src/index.js
 @ multi (webpack)-dev-server/client?http://localhost:8080 webpack/hot/dev-server ./index.js```

Consumer used without Provider

I am using preact (in parcel) and getting this warning whenever I compose an HOC. I apologize, in advance, if this issue belongs in another queue, but it's a bit hard to track down.

Consumer used without Provider

The HOC still works fine, and it's not app-breaking, just hoping I am using it right and (I am new to preact & proppy, coming from recompose and react.)

Here is my 1-file example:

import { h, render } from 'preact'
import { compose, withState, withHandlers } from 'proppy'
import { ProppyProvider, attach } from 'proppy-preact'

const P = compose(
  withState('counter', 'setCounter', 0),
  withHandlers({
    increment: ({ counter, setCounter }) => e => setCounter(counter + 1),
    decrement: ({ counter, setCounter }) => e => setCounter(counter - 1)
  })
)

const PageHome = attach(P)( ({ increment, decrement, counter }) => (
  <div>
    <button onClick={decrement}>-</button>
    {counter}
    <button onClick={increment}>+</button>
  </div>
))

// global app-state
const providers = {
  foo: false,
  bar: true
}

const Index = () => (
  <ProppyProvider providers={providers}>
    <PageHome />
  </ProppyProvider>
)

render(<Index />, document.getElementById('app'))

DOC example of why proppy

Hi. It might help to promote poppy if you put live code example with pros/cons of using poppy vs not using poppy.

I was used to using decorators to compose for react. But now I switch to Vue, and I struggle to find use cases that require to wrap the Vue components.

Testing: how to pass new props when testing factories?

What's the recommended way to pass and update props in test?

I'm trying to test this Proppy factory for a timer behavior:

const P = compose(
  withStateHandlers(
    {
      startTime: null
    },
    {
      startTimer: ({t}) => () => ({
        startTime: t
      })
    }
  ),
  map((props) => {
    const {t, startTime, duration} = props;

    if (isNil(startTime)) {
      return {
        ...props,
        timeLeft: duration
      }
    }
    return {
      ...props,
      timeLeft: duration - (t - startTime)
    }
  })
)

On a high level the factory works like this:

  1. you pass in t (the current time)
  2. timeLeft is passed to child
  3. at some point the child calls startTimer
  4. the relentless advance of time causes t to change
  5. updated timeLeft is passed to child

the example here shows how to test a fairly trivial proppy factory, but I'm not sure how to simulate the behavior outlined above.

after some experimentation, I was able to pass in the initial props (see below) but it feels kinda clunky and wrong. I'm also not clear on how to pass new props into the same instance (simulating the passage of time)

const P = compose(
  withProps({t: 0, duration: 180000}),
   behavior
);

const p = P();

expect(p.props.timeLeft).toEqual(180000)

p.props.startTimer();

// rerender with new props??

expect(p.props.timeLeft).toEqual(170000)

Thanks!

parentProps in props (Preact)

When using withHandlers or any other hoc, the props that are passed to the component are inside of a parentProps object while I expect them to be directly inside of the props object.

Example:
https://codesandbox.io/s/4q6wnl3w74

In this example the prop foo is inside of parentProps when logged in withHandlers.

Feature: lifecycle hoc

Currently, recompose offers us the lifecycle hoc where we can do stuff such as

import React from 'react'
import PropTypes from 'prop-types'
import { lifecycle } from 'recompose'

import Contact from '../Contact'
import ContactPropType from '../PropTypes/Contact'

const withContacts = lifecycle({
  componentDidMount() {
    fetch('https://randomuser.me/api/?results=20')
      .then(response => response.json())
      .then(data => data.results)
      .then(contacts => this.setState({ contacts }))
  }
})

const Contacts = ({
  contacts
}) => (
  <div className="contacts">
    {contacts.map(contact => (
      <Contact
        key={contact.id.value}
        contact={contact}
      />
    ))}
  </div>
)

Contacts.propTypes = {
  contacts: PropTypes.arrayOf(ContactPropType)
}

Contacts.defaultProps = {
  contacts: []
}

export default withContacts(Contacts)

And I just realized that proppy nor proppy-react have an option for this.
Is this part of the roadmap? Any opinions are welcome ๐Ÿ˜„

Proposal: React hooks

WIP branch exists here: https://github.com/fahad19/proppy/tree/proppy-react-hook

Background

React hooks will land soon in stable version, and currently already available in alpha version.

More info: https://reactjs.org/docs/hooks-intro.html

Set up

For the examples below, we will be using these providers:

import React from 'react';
import { ProppyProvider } from 'proppy-react';

const providers = {
  foo: 'foo value',
  bar: 'bar value',
};

export default function Root() {
  return (
    <ProppyProvider providers={providers}>
      <MyComponent />
    </ProppyProvider>
  );
}

And the sample factory:

import { compose, withProps, withState, shouldUpdate } from 'proppy';

const P = compose(
  withProps((props, providers) => {}),
  withState('counter', 'setCounter', 0),
  shouldUpdate(props => props.counter % 2 === 0),
);

Usage APIs

useProppy

Using Proppy factory inside Components:

import React from 'react';
import { useProppy } from 'x'; // new package

const P; // factory

export default function MyComponent(props) {
  const { counter, setCounter } = useProppy(P, props);

  return (
    <p onClick={() => setCounter(counter + 1)}>
      {counter}
    </p>
  );
}

useProviders

Access all the providers:

import React from 'react';
import { useProviders } from 'x';

export default function MyComponent(props) {
  const { foo, bar } = useProviders();

  return <p></p>;
}

useProvider

Access individual providers:

import React from 'react';
import { useProvider } from 'x';

export default function MyComponent(props) {
  const foo = useProvider('foo');

  return <p></p>;
}

Return an async iterable

This would give a "native" way to consume changes.

A possible example of usage...

import { withProps } from 'proppy';

const P = withProps({ counter: 1 });

const p = P();

for await (const value of p.subscribe()) {
  console.log({ value });
}

[FR] Framework dependent intercept function

Is there any way to block a component to render until withObservable() subscriptions have completed? Shouldn't this be the default? It would be particularly useful to be able to count on the defined props.

Using with React & RxJS 6.

vue-class-component support

I have been wondering if it is possible to use this library with components defined like so:

@Component
class SomeComponent extends Vue {
   mount () {
      ...
   }
}

Thanks!

Migrate remaining packages to TypeScript

These packages are not yet migrated to TypeScript:

  • proppy-preact
  • proppy-frint-react

Once done, we can remove Babel and ESLint as dependencies from the monorepo.

Missing didUpdate for componentDidUpdate lifecycle function

Attempting to call a state handler using onChange results in an infinite loop. It would be nice if there was a didUpdate function that mirrored the capabilities of (p)react's componentDidUpdate, that allows you to immediately call setState given some conditional.

This feature is important when utilizing routing or similar functionality where a change in props would cause a reset / refetch of data.

As a workaround, calling the state handler in a setTimeout worked fine.

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.