Giter VIP home page Giter VIP logo

react-atom's People

Contributors

dependabot-preview[bot] avatar derrickbeining 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

react-atom's Issues

`useAtom` `options.select` gets mixed up when more than one atom

the selector map is having key collisions because hook ids are not absolutely unique, they're only unique to the set of hook ids associated with a given atom. It's causing the wrong selector to be used when there are multiple atoms.

Currently the selector map is like this:
{ [hookId: number]: Selector }

But needs to be like this:
{ [atomId: number]: { [hookId: number]: Selector } }

Double renders

It looks like the useAtom() hook produces a double rendering of the component. I have an example running here on codesandbox ... inspect the console output and you see for every update of the stateAtom that the console.log outputs twice in a row.

HOC for use with non hook components

Having a code base that has both hook based and class based components is common. It would be useful for react-atom to provide a HOC for these components. Something simple like the following should work.

const connect = (mapStateToProps, store) => Component => props => {
  const stateProps = mapStateToProps(useAtom(store))
  return <Component {...stateProps} {...props} />
}

Outdated react-dom peer dependency

A project using react-atom 4.1.1 with dependency on react-dom 16.9.0 results in a warning during npm install:

@dbeining/[email protected] requires a peer of react-dom@>=16.7.0-alpha.0 || >=16.8.0-alpha.0 || ^16.8.x but none is installed. You must install peer dependencies yourself.

useAtom Destructuring

Hi,

is there a reason why const { a, b, c } = useAtom(stateAtom) doesn't use the destructed keys as default options.select filter?

I've build a little helper function doing that but I am unsure if this makes sense at all?! Right now it doesn't guard against any invalid keys at all and is more a proof of concept :

const useAtomProxy = (f) => (stateAtom) =>
  new Proxy(
    {},
    {
      get: (_, name) =>
        f(stateAtom, {
          select: (s) => s[name],
        }),
    }
  );
const useAtomSelected = useAtomProxy(useAtom);

...

const { a, b, c } = useAtomSelected(stateAtom);

Subscription list is invalidated if a parent unmounts a child.

In the code below, k can be invalidated if a previous call to changeHandlersByAtomId causes a component to unmount farther down the list.

function _runChangeHandlers(atom, previous, current) {
  Object.keys(changeHandlersByAtomId[atom["$$id"]]).forEach(function (k) {
    changeHandlersByAtomId[atom["$$id"]][k]({
      previous: previous,
      current: current
    });
  });
}

Debugging State

Hi @derrickbeining - great project you got here!

I've been using React Waterflow earlier on (as a replacement for Redux et al.) and I like what you did. Actually, I like it so much, that I converted / introduced only your package for global state management in all recent projects I'm involved in. The only thing that I like more about React Waterflow is the possibility of adding the (very mature / advanced) Redux dev tools.

For me the most important / crucial part about the Redux dev tools was the console logging during development / non-production runtime. So I thought "why not try to re-create that experience". All in all I did not write any compatibility layer, but rather just a lightweight console output.

The code is:

addChangeHandler(globalState, 'debugging', ({ current, previous }) => {
    const action = new Error().stack.split('\n')[6].replace(/^\s+at\s+Atom\./, '');
    console.group(
    `%c Portal State Change %c ${new Date().toLocaleTimeString()}`,
    'color: gray; font-weight: lighter;',
    'color: black; font-weight: bold;',
    );
    console.log('%c Previous', `color: #9E9E9E; font-weight: bold`, previous);
    console.log('%c Action', `color: #03A9F4; font-weight: bold`, action);
    console.log('%c Next', `color: #4CAF50; font-weight: bold`, current);
    console.groupEnd();
});

where globalState is the created globalState. You can see the whole source code / repository here. We placed this code in a conditional to avoid placing the code in the production build.

Maybe you / someone find(s) this snippet useful and we could provide it as a utility out of the box (properly exported such that tree shaking can remove it if not being used, e.g., in a PROD build of the consuming application).

Thanks again for your nice lib - great job! ๐Ÿป

Getting no TypeScript error in swap

Hi!

Thank you for maintaining such a good lib! I wondered if somebody implemented something like an atom in Clojure and I'm here!

UPD:
Probably I should address this question to @libre/atom, but I'll leave it here for now.

I'm playing with it and thinking about using it in my TypeScript project but I'm curious why I'm not receiving any TS error when trying to swap state to something, that doesn't match its initial shape.
For example:

export interface AppState {
    isLoading: Boolean
}

export const appState = Atom.of<AppState>({
    isLoading: false,
})

export const setIsLoading = (isLoading: boolean) =>
    swap<AppState>(appState, (state) => ({
        ...state,
        foo: 0, // No error here
        isLoading,
    }))

Am I doing something wrong?

Thank you in advance!

make `swap` automatically merge return value of `updateFn` with `state`

It would be nice not to have to spread state all over the place in swap, like this:

swap(atom, (state) => ({
  ...state,
  stuff: {
    ...state.stuff,
    things: [...state.stuff.things, newThing]
  }
})

and instead be able to do this:

swap(atom, (state) => ({
  stuff: {
    things: [...state.stuff.things, newThing]
  }
})

swap would still work if you manually spread things like now, and we could avoid unnecessary deep merging by just checking Object.is between the old and new state and just skip merging those that are equal.

Introducing the useAtomState hook

I am using react-atom in multiple projects, often starting with React.useState() then upgrading to atoms. The useAtomState() hook below makes it take only a few keystrokes to replace useState with a react-atom. It has the same return signature as useState but takes an Atom.

Perhaps it could be added to the distribution? It would make it (even) easier for React devs to start using react-atom and upgrade their existing react hook codebase.

function useAtomState (atom) {
  const state = useAtom(atom);
  const setState = useCallback ((obj) => {
    swap(atom, (typeof obj === 'function') ? obj : () => obj)
  }, [atom]);
  return ([state, atom ? setState : null]);
}

Bootstrapping an Atom from localStorage... how to?

I'm new to React and this library, so my question may be a bit silly.

I'm using React hooks like useState, useReducer and dispatch inside components and using custom helper functions. For a small app like mine, this is overkill.

I need to persist the state into the local storage, but I don't know to do this using this library. Using React state management, I would use a custom hook like this:

import React from 'react';

/**
 * @param {string} storageKey
 * @param {*} initialState
 * @param {number} expiration
 */
export const useStateWithLocalStorage = (storageKey, initialState, expiration) => {
  const [value, setValue] = React.useState(() => {
    const value = localStorage.getItem(storageKey);
    if (null === value || 'undefined' === typeof value) {
      return initialState;
    }

    const expiration = parseInt(localStorage.getItem(`${storageKey}_expire`));
    if (!isNaN(expiration) && Math.floor(Date.now() / 1000) > expiration) {
      localStorage.removeItem(`${storageKey}_expire`);

      return initialState;
    }

    if ('{' === value[0] || '[' === value[0]) {
      return JSON.parse(value);
    }

    return value;
  });

  React.useEffect(() => {
    localStorage.setItem(storageKey, null !== value && 'object' === typeof value ? JSON.stringify(value) : value);

    if (expiration > 0) {
      const existingExpiration = localStorage.getItem(`${storageKey}_expire`);
      if (null === existingExpiration || 'undefined' === typeof existingExpiration) {
        localStorage.setItem(`${storageKey}_expire`, Math.floor(Date.now() / 1000) + expiration);
      }
    }
  }, [storageKey, value, expiration]);

  return [value, setValue];
};

optimize `swap` to run selectors prior to useState hooks

swap currently loops through useAtom subscriptions once and, for each subscription, runs both the selector (if present) and the useState hook. I anticipate this being an issue (no data on this), because selectors are potentially expensive computations which could block between useState hooks firing to re-render components. That would probably make the component rendering "janky". Instead, selectors should be run before useState hooks so that the re-render cycle looks seamless.

optimize `select` option in `useState`

Currently, a given selector is run in both swap (to check if selected state did change) and useAtom, which is redundant work. Need to find a way to consolidate these.

How to use `Atom` in component state with context ?

This is a neat library, and I want to try it. But here is one thing i haven't figure it out. If effects takes Atom argument, a bunch of effects need to be wrapped in useCallback like memoIncrement below.

import * as React from "react";
import { useAtom, Atom, swap } from "@dbeining/react-atom";


export type AppState = Atom<{
  count: number;
  text: string;
  data: any;
}>;


// effects
export const increment = (stateAtom: AppState) =>
  swap(stateAtom, state => ({ ...state, count: state.count + 1 }));

export const decrement = (stateAtom: AppState) =>
  swap(stateAtom, state => ({
    ...state,
    count: state.count && state.count - 1
  }));

export const updateText = (
  stateAtom: AppState,
  evt: React.ChangeEvent<HTMLInputElement>
): void => swap(stateAtom, state => ({ ...state, text: evt.target.value }));

export const loadSomething = (stateAtom: AppState) =>
  fetch("https://jsonplaceholder.typicode.com/todos/1")
    .then(res => res.json())
    .then(data => swap(stateAtom, state => ({ ...state, data })))
    .catch(console.error);

const AppContext = React.createContext<AppState | undefined>(undefined);

export const App = () => {
  const stateAtom = React.useRef(
    Atom.of({
      count: 0,
      text: "",
      data: {}
    })
  );

  console.log("App render()");

  return (
    <AppContext.Provider value={stateAtom.current}>
      <Child />
    </AppContext.Provider>
  );
};

export function Child() {
  const stateAtom = React.useContext(AppContext);
  const { count, data, text } = useAtom(stateAtom);

  const memoIncrement = React.useCallback(
    ev => {
      increment(stateAtom);
    },
    [stateAtom]
  );

  return (
    <div>
      <h2>Count: {count}</h2>
      <h2>Text: {text}</h2>

      <button onClick={memoIncrement}>Moar</button>
      <button onClick={ev => decrement(stateAtom)}>Less</button>
      <button onClick={ev => loadSomething(stateAtom)}>Load Data</button>
      <input
        type="text"
        onChange={ev => updateText(stateAtom, ev)}
        value={text}
      />
      <p>{JSON.stringify(data, null, "  ")}</p>
    </div>
  );
}

Your .dependabot/config.yml contained invalid details

Dependabot encountered the following error when parsing your .dependabot/config.yml:

Automerging is not enabled for this account. You can enable it from the [account settings](https://app.dependabot.com/accounts/derrickbeining/settings) screen in your Dependabot dashboard.

Please update the config file to conform with Dependabot's specification using our docs and online validator.

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.