Coder Social home page Coder Social logo

preact-context's Introduction

React's createContext for preact

Build Status npm

Sauce Test Status

This is an implementation of react's new context api. You can read more about it on react's documentation page. Please have in mind that preact X ships with a context implementation

This package provides the createContext factory function that can be used in order to create a context:

import { h } from "preact";
import { createContext } from "preact-context";

const Theme = createContext("dark");

The returned object contains two components: a Provider and a Consumer.

The Consumer

It can be used in order to consume the provided object:

<Theme.Consumer>{theme => <p>Selected theme: {theme}</p>}</Theme.Consumer>

Alternatively, it can also be used with a render property:

<Theme.Consumer render={theme => <p>Selected theme: {theme}</p>} />

The Provider

can be used in order to update the value of a context:

<Theme.Provider value="sunny">

will change "dark" to "sunny" and notify all it's consumers of the change.

Development

This project has been written with typescript. The watch script will watch for changes, compile and run the tests.

$ npm i
$ npm run watch

License

Licensed under the Apache License, Version 2.0

Big Thanks

Cross-browser Testing Platform and Open Source <3 Provided by Sauce Labs

preact-context's People

Contributors

developit avatar kossnocorp avatar valotas 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

preact-context's Issues

Make warning optional: Consumer used without a Provider

I am getting this warning in the console:

Consumer used without a Provider

The message is clear enough, but in my case this is not an issue and I would like to suppress this warning.

I'll describe my use case below and show some example code, but the gist of it is: in my case, the use of context is optional and it is expected that it might not be provided. So I would like the option to suppress the warning.

Would you accept a PR?
EDIT: Too late, I already created one :) #25

Background

I am using preact-context to create a theming solution based on CSS modules. The way it works is that the components will by default render the default class names, but you can load the CSS via CSS Modules and pass the module def to a Provider and the components will pick it up and use the modified class names.

The way I did this was to have a default definition in each component that contains the unmodified class names just like they appear in my CSS.

E.g:

const defaultClasses = { 'test': 'test' }

After loading the CSS through CSS Modules, this will produce a module definition with mangled class names (to provide scoping), that looks like this:

const classes = import('./some/styles.css');
// classes == { 'test': 'test_23rfT4' }

The user of my library can wrap the components in a theme Provider and provide the mangled names so the components will use those names i.s.o the default ones.

The components all use a Consumer defined in a common file and when the user needs a Provider, he gets it from this same file:

theme.jsx

import { h } from 'preact'
import { createContext } from 'preact-context'

export const Theme = createContext({})
export const Provider = Theme.Provider
export const Consumer = Theme.Consumer

export default { Theme, Provider, Consumer }

The components use the Consumer to try to get the property classes from the context, defaulting it to an empty object. They then merge the classes over the defaultClasses and use the resulting definition:

card.jsx

import { h } from 'preact'
import defaultClasses from 'solids/card/classes'
import { createHelper } from '../style-classes'
import { Consumer } from '../theme'

export const Card = (props = {}) => {
  const {
    // show an outlined card
    outlined = false,
    children,
    // other attributes
    ...attributes
  } = props

  return (
    <Consumer>{({ classes = {}, scope = 'local' }) => {

      classes = { ...defaultClasses, ...classes }
      let classNames = createHelper(classes, scope)
      attributes.className = classNames(classes.card, {
      [attributes.className || attributes.class]: attributes.className || attributes.class,
        [classes.outlined]: outlined,
      })

      return (
        <div {...attributes}>
          {children}
        </div>
      );
    }}</Consumer>
  )
}

export default Card;

This all works like a charm, except that I get this annoying warning, even in the production build.

How to provide it to dependencies?

I use react-bootstrap, which uses react.createContext:

TypeError: _react.default.createContext is not a function
    at Object.<anonymous> (/var/home/jgillich/Development/polyfresh/node_modules/react-bootstrap/lib/ThemeProvider.js:17:43)

Is there any way to fix these imports? I tried to play around with webpack resolve alias, but that didn't work. :( I do have an alias for react -> preact-compat though.

Getting error consumer used without provider.

I am trying to get a handle on how preact-context is used within a component ecosystem. Perhaps I am going about this wrong, but here is what I am attempting to play with:

First, I am trying to create a data provider which will (eventually) perform the server logins/logouts/tokens etc.

server.js

import { h, Component } from 'preact';
import { createContext } from 'preact-context';

const DataProviderContext = createContext();
const { Provider, Consumer } = DataProviderContext;

export default class DataProvider extends Component {
  constructor() {
    super();
  }

  state = {
    url: 'http://localhost:3000',
    username: 'testuser',
    password: 'testpw'
  };

  render() {
    return <Provider value={this.state}>{this.props.children}</Provider>;
  }
}

Then, the consumer is the Login page which pulls in the default values and then can set them when the user makes the change.

login.js

import { h, Component } from 'preact';
import { createContext } from 'preact-context';
import style from './style';
import linkState from 'linkstate';

const DataProviderContext = createContext('DataProvider');
const { Provider, Consumer } = DataProviderContext;

export default class Login extends Component {
  state = {
    username: '',
    password: ''
  };

  render() {
    return (
      <div>
        <Consumer>
          {(username, password) => (
            <div class={style.login}>
              <h2>Login Here</h2>
              <input onChange={linkState(this, 'username')} type="text" value={username} />
              <input onChange={linkState(this, 'password')} type="text" value={password} />
              <button onClick={console.log('Clicked')}>Log In </button>
            </div>
          )}
        </Consumer>
      </div>
    );
  }
}

In the main application entry point, the provider wraps the router for all routes within the application so that this information is available (in reality, it will be a token...but testing for now).

app.js

import { h, Component } from 'preact';
import { Router } from 'preact-router';

//import linkState from 'linkstate';
import Header from './components/header';

// Code-splitting is automated for routes
import Login from './routes/login';
import Home from './routes/home';
import Profile from './routes/profile';

import DataProvider from './providers/server';

export default class App extends Component {
  /** Gets fired when the route changes.
   *	@param {Object} event		"change" event from [preact-router](http://git.io/preact-router)
   *	@param {string} event.url	The newly routed URL
   */
  handleRoute = (e) => {
    this.currentUrl = e.url;
  };

  render() {
    return (
      <div id="app">
        <DataProvider>
          <Header />
          <Router onChange={this.handleRoute}>
            <Home path="/" />
            <Login path="/login" />
            <Profile path="/profile/" user="me" />
            <Profile path="/profile/:user" />
          </Router>
        </DataProvider>
      </div>
    );
  }
}

Is there any way to have access to this.context in component code?

When we use react we are able to do something like App.contextType = OurContext; And then in, for example, componentDidMount() write let someVar = this.context.someVarFromContext to get last value of someVarFromContext from imported context.

How can I get the same - this.context.someVarFromContext using preact-context? Or is it not implemented?

empty value

I am just trying to make demo working but it doesn't work.

import {h, Component} from 'preact';

import { createContext } from 'preact-context';

const SecureContext = createContext('password');

export default class Terms extends Component {
  render() {
    return <SecureContext.Provider value="hell world">
      <h1>
        <SecureContext.Consumer render={password => <span>Password is {{password}}</span>} />
        Terms
      </h1>
    </SecureContext.Provider>;
  }
}

password is empty and "Password is Terms" is generated.

No errors in js console.
build log is also fine.

  "dependencies": {
    "@babel/core": "^7.2.2",
    "@babel/plugin-proposal-class-properties": "^7.2.3",
    "@babel/plugin-proposal-object-rest-spread": "^7.2.0",
    "@babel/plugin-transform-react-jsx": "^7.2.0",
    "@babel/preset-env": "^7.2.3",
    "@babel/preset-stage-2": "^7.0.0",
    "@babel/preset-typescript": "^7.1.0",
    "babel-loader": "^8.0.5",
    "preact": "^8.4.2",
    "preact-context": "^1.1.2",
    "typescript": "^3.2.2",
    "webpack": "^4.28.1",
    "webpack-cli": "^3.2.1"
  },
  "devDependencies": {
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
    "@babel/preset-react": "^7.0.0",
    "preact-async-route": "^2.2.1",
    "preact-router": "^2.6.1",
    "tslint": "^5.12.0"
  }

npm --version
6.5.0
16:02$ node --version
v10.14.2
16:02$ webpack --version
webpack: command not found
16:02$ webpack-cli --version
3.1.2

webpack config:
module.exports = {
  entry: "./src/bootstrap.tsx",
  output: {
    filename: "./public/bundle.js",
    publicPath: '/dist/'
  },
  mode: 'development',
  // Enable sourcemaps for debugging webpack's output.
  devtool: "eval",
  resolve: {
    // Add '.ts' and '.tsx' as resolvable extensions.
    extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
  },
  module: {
    rules: [
      // Handle .ts and .tsx file via ts-loader.
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [['@babel/typescript', { jsxPragma: "h" }]],
            plugins: [
              ['@babel/proposal-class-properties'],
              ['@babel/proposal-object-rest-spread'],
              ['@babel/plugin-syntax-dynamic-import'],
              ["@babel/transform-react-jsx", { "pragma": "h" }]
            ]
          }
        }
      }
    ]
  }
};

tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noEmit": true,
    "strict": true,
    "allowJs": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "jsx": "react",
    "jsxFactory": "h"
  },
  "include": [
    "src"
  ]
}

Tyepscript Issue with provider value

Hi,

I'm using preact with typescript and i'm getting this message whenever i wanna pass object to the provider value "[ts] Type '{ test: string; }' is not assignable to type 'string'.

context.d.ts(4, 5): The expected type comes from property 'value' which is declared here on type 'Readonly<ProviderProps & Attributes & { children?: string | number | object | VNode | ComponentChild[] | null | undefined; ref?: Ref | undefined; }>'

By definition i can only pass a string ?

Thanks

Gil

Adding a child consumer while updating provider value does not render the last consumer child correctly

I'm currently experiencing an issue when multiple consumers are conditionally rendered:

https://jsfiddle.net/jesusvilar/9h28gymb/36/

Notice that in the last consumer, value is not updated.

It seems to me that registration to the provider happens immediately after rendering a consumer. If changing the value of provider is done at the same time as conditionally rendering a consumer, the consumer won't pick the updated value.

Is the emitter not supposed to update the value when it registers?

https://github.com/valotas/preact-context/blob/master/src/context-value-emitter.ts#L20

Also, same jsfiddle using React 16: https://jsfiddle.net/jesusvilar/nLaqb7os/

Question: Tag name

Hi,

is there a way to change the tag name of Context.Provider? Now it generates <span> which results oftentimes in invalid markup.

It would also be nice if the props would extends some sort of HTMLAttributes, so one could use that wrapper to add some classes or other valid html attributes to it.
Just to avoid unnecessary div-soup.

Cheers

Aliasing create-react-context

Hi! This module is great but I have one very minor suggestion. If you change how createContext is exported, this module can then be used as a drop-in replacement for React components that use create-react-context (in the same way that you can drop-in preact-compat to replace react-dom).

For example, in Webpack you could do something like:

module.exports = {
    ...
    resolve: {
        alias: {
            "react": "preact-compat",
            "react-dom": "preact-compat",
            "create-react-context": "preact-context"
        }
    },
    ...
}

However, since create-react-context exports on default and preact-context exports on createContext, this is not possible without something hacky.

This would be a breaking change so I understand if you'd want to refrain from doing this.

Issue with typescript enums

When I try to pass down a typescript enum value in a provider the js value is 0. The consumer however receives undefined instead of 0. It works fine when I assign string values to the enum.

Build Error, Node v8.0.0 or v8.11.3

Hi,

I'm trying to build the library(npm i && npm run build) but I'm getting the following error:

> tsc -p .

src/context.ts:27:23 - error TS7017: Element implicitly has an 'any' type because type 'string | number | object | VNode<any> | ComponentChild[]' has no index signature.

27   return (children && children[0]) || render;
                         ~~~~~~~~~~~
src/context.ts:62:32 - error TS2339: Property 'length' does not exist on type 'string | number | object | VNode<any> | ComponentChild[]'.
  Property 'length' does not exist on type 'number'.

62       if (children && children.length > 1) {
                                  ~~~~~~
src/context.ts:67:28 - error TS7017: Element implicitly has an 'any' type because type 'string | number | object | VNode<any> | ComponentChild[]' has no index signature.

67       return ((children && children[0]) || null) as JSX.Element;

I'm not that familiar with TypeScript but I would appreciate any help.

Thanks!

Uglify mangling `window.__extends`

Below are excerpts from the files in node_modules/preact-context/dist/ after installing from npm. It appears that window.__extends is being mangled to window.t by the minification build step, but it probably should be excluded to function properly. As it is, if there is a global variable named t on the page, it will be used as __extends and cause preact-context to break.

uglifyjs is being passed the option --mangle-props regex=/^_/, which matches __extends, so maybe an exception should be added using reserved?

context.js

    var __extends = (window && window.__extends) || (function () {
        var extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return function (d, b) {
            extendStatics(d, b);
            function __() { this.constructor = d; }
            d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
        };
    })();

context.min.js (after formatting)

		e =
			(window && window.t) ||
			((i =
				Object.setPrototypeOf ||
				({ __proto__: [] } instanceof Array &&
					function(n, t) {
						n.__proto__ = t;
					}) ||
				function(n, t) {
					for (var i in t) t.hasOwnProperty(i) && (n[i] = t[i]);
				}),
			function(n, t) {
				function r() {
					this.constructor = n;
				}
				i(n, t),
					(n.prototype = null === t ? Object.create(t) : ((r.prototype = t.prototype), new r()));
			});

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.