Coder Social home page Coder Social logo

ctrlplusb / easy-peasy Goto Github PK

View Code? Open in Web Editor NEW
5.0K 34.0 192.0 9.37 MB

Vegetarian friendly state for React

Home Page: https://easy-peasy.dev

License: MIT License

JavaScript 71.57% TypeScript 27.75% Stylus 0.65% Shell 0.03%
react redux state-management immutable hooks react-hooks immer

easy-peasy's Introduction

 

Vegetarian friendly state for React

 

npm MIT License Codecov

Easy Peasy is an abstraction of Redux, providing a reimagined API that focuses on developer experience. It allows you to quickly and easily manage your state, whilst leveraging the strong architectural guarantees and extensive eco-system that Redux has to offer.

  • Zero configuration
  • No boilerplate
  • React hooks based API
  • Extensive TypeScript support
  • Encapsulate data fetching
  • Computed properties
  • Reactive actions
  • Redux middleware support
  • State persistence
  • Redux Dev Tools
  • Global, context, or local stores
  • Built-in testing utils
  • React Native supported
  • Hot reloading supported

 

All of this comes via a single dependency install.

npm install easy-peasy

 

Fly like an eagle 🦅

Create your store

const store = createStore({
  todos: ['Create store', 'Wrap application', 'Use store'],

  addTodo: action((state, payload) => {
    state.todos.push(payload);
  }),
});

Wrap your application

function App() {
  return (
    <StoreProvider store={store}>
      <TodoList />
    </StoreProvider>
  );
}

Use the store

function TodoList() {
  const todos = useStoreState((state) => state.todos);
  const addTodo = useStoreActions((actions) => actions.addTodo);
  return (
    <div>
      {todos.map((todo, idx) => (
        <div key={idx}>{todo}</div>
      ))}
      <AddTodo onAdd={addTodo} />
    </div>
  );
}

 

Examples 📚

See the example folder for more examples of how to use easy-peasy.

 

Core Team 🛠


Peter Weinberg

Jørn A. Myrland

Sean Matheson

 

Our Sponsors ❤️

We have only but great appreciation to those who support this project. If you have the ability to help contribute towards the continued maintenance and evolution of this library then please consider [becoming a sponsor].

 

Documentation

See the official website for tutorials, docs, recipes, and more.

 

OS Awards Nominee

Easy Peasy was nominated under the "Productivity Booster" category.

easy-peasy's People

Contributors

aniganesh avatar arielhs avatar avramch avatar axel-habermaier avatar bahaaador avatar bdeloeste avatar cevr avatar christianchown avatar chrtze avatar colshacol avatar crissdev avatar ctrlplusb avatar damassi avatar danielruf avatar dependabot[bot] avatar einomi avatar gamtiq avatar ivosh avatar jmalins avatar jmyrland avatar jorasso avatar leiwan5 avatar mfbx9da4 avatar no-stack-dub-sack avatar shoeman22 avatar tehpsalmist avatar troglodytto avatar w3bdesign avatar yard avatar yethranayeh 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

easy-peasy's Issues

need context export

Hello,

I need the ability to import context object to use with https://github.com/supasate/connected-react-router
wich rely by default on ReactReduxContext from react-redux but offer the possibility to provide a custom context and in this case, I need the one from easy-peasy.
Could you please just export it.
Many thanks (and also for making this great library).

How to execute code once an action has updated the store?

I am trying to execute code once the store has been updated from a model action, and I do not see an easy way to subscribe to this.

If I trigger the action in an effect, I am still seeing the effect promise resolving before the triggered action has updated the store. Is there any kind of event we can hook directly into to the action callback or store to achieve this?

Here is some example code of what I am facing:

const defaultState = {
  user: {},
  counter: {},
  ....
}

// model
const model = {
  ...defaultState,
  // Action
  resetState: (state) => ({ ...state, ...defaultState }),
  // Effect
  logOut: effect(async dispatch => {
    const asyncCall = await ApiService.logOut();
    return dispatch.resetState();
  })
}

// View
const logOutButton = () => {
  const logOut = useAction(dispatch => dispatch.logOut);

  return <Button 
                onClick={() => logOut().then(() => console.log('Assume that the store has updated from the resetState action'))}
               >Log Out</Button>
}

In the above psuedo code, I am seeing the console.log function in the logout button firing before the store has finished updating from the resetState action. In the devtools I am actually seeing the resolved promise function being invoked before the resetState action has been triggered.

Any help would be appreciated.

What's the idiomatic way to reuse easy-peasy code?

For example, in react-redux, containers can be reused. One container can be wrapped around as many presentational components as needed.

I understand I could make my own hooks that call easy-peasy's, but that still puts my Redux logic right next to my rendering. What if I have a component that I want to behave differently in three places? Do I still need a HOC for this to pass my own hooks into the presentational component?

Selector not updating

I have a model that looks like this:

  {
    ids: [1,2,3..],
    byId: {
      1: { item},
      2: {item},
     ...
   },
   list: select(state => state.ids.map(id => state.byId[id]),
   updateItem: (state,payload) => {
     state.byId[payload.id].someField = payload.value;
   }

When i update just one field in an item object, the list selector doesn't update. Is this normal behavior?

Hooks can only be called inside the body of a function component

My code is like:

// 1st useAction
import { useAction } from 'easy-peasy'

// 2nd useAction
// function useAction(mapActions) {
//   // Get store myself, I had to rebuild easy-peasy and export StoreContext
//   const store = React.useContext(peasy.StoreContext)
//   return mapActions(store.dispatch)
// }

export const View = () => {
  const updateDocForPrint = useAction(
    (dispatch: any) => dispatch.docForPrint.updateDocForPrint
  );
};

As you can see, View is a function component, however it still produces an error:

/app/node_modules/easy-peasy/node_modules/react/cjs/react.development.js:125
Uncaught Error: Hooks can only be called inside the body of a function component.
    at invariant (/app/node_modules/easy-peasy/node_modules/react/cjs/react.development.js:125)
    at resolveDispatcher (/app/node_modules/easy-peasy/node_modules/react/cjs/react.development.js:1424)
    at Object.useContext (/app/node_modules/easy-peasy/node_modules/react/cjs/react.development.js:1429)
    at Object.useAction (hooks.js:49)
    at exports.USBExplorerView.react_router_1.withRouter (eval at <anonymous> (store.ts:30), <anonymous>:18:37)
    at mountIndeterminateComponent (/app/node_modules/react-dom/cjs/react-dom.development.js:14620)
    at beginWork (/app/node_modules/react-dom/cjs/react-dom.development.js:15091)
    at performUnitOfWork (/app/node_modules/react-dom/cjs/react-dom.development.js:17817)
    at workLoop (/app/node_modules/react-dom/cjs/react-dom.development.js:17857)
    at HTMLUnknownElement.callCallback (/app/node_modules/react-dom/cjs/react-dom.development.js:149)

If I comment out 1st useAction and use 2nd useAction, a customized useAction, it will work.

I'm not sure if this is a React bug or an easy-peasy bug, the definition https://github.com/ctrlplusb/easy-peasy/blob/master/src/hooks.js#L48 is indeed a simple function, just like mine.

Not working on Firefox 63.0.3

When I open the Codesandbox or a code which includes 'easy-peasy in firefox 63.0.3 on MacOs Mojave I get the following error, when on Chrome it is working:

`TypeError: e[n] is undefined
unliftState
http://localhost:3000/:2:30806
getState
http://localhost:3000/:2:30882
useStore
node_modules/easy-peasy/dist/easy-peasy.esm.js:420

417 | var dependencies = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
418 | var store = useContext(StoreContext);
419 |

420 | var _useState = useState(mapState(store.getState())),
| ^ 421 | _useState2 = _slicedToArray(_useState, 2),
422 | state = _useState2[0],
423 | setState = _useState2[1]; // As our effect only fires on mount and unmount it won't have the state

./src/components/Map.js/webpack_exports.default
src/components/Map.js:18

15 | `
16 |
17 | const getPos = useAction(dispatch => dispatch.clickedPos.getPos)

18 | const pos = useStore(state => state.clickedPos.latlng)
| ^ 19 | const markerPosition = [pos.lat, pos.lng]
20 |
21 | return (

./src/index.js
src/index.js:24

21 | },
22 | })
23 |

24 | ReactDOM.render(
25 |
26 |
27 |

webpack_require
/Users/bjoernrave/projects/coolplacesjs/webpack/bootstrap:782

779 | };
780 |
781 | // Execute the module function

782 | modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
| ^ 783 |
784 | // Flag the module as loaded
785 | module.l = true;

fn
/Users/bjoernrave/projects/coolplacesjs/webpack/bootstrap:150

147 | );
148 | hotCurrentParents = [];
149 | }

150 | return webpack_require(request);
| ^ 151 | };
152 | var ObjectFactory = function ObjectFactory(name) {
153 | return {

0
http://localhost:3000/static/js/main.chunk.js:544:18
webpack_require
/Users/bjoernrave/projects/coolplacesjs/webpack/bootstrap:782

779 | };
780 |
781 | // Execute the module function

782 | modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
| ^ 783 |
784 | // Flag the module as loaded
785 | module.l = true;

checkDeferredModules
/Users/bjoernrave/projects/coolplacesjs/webpack/bootstrap:45

42 | }
43 | if(fulfilled) {
44 | deferredModules.splice(i--, 1);

45 | result = webpack_require(webpack_require.s = deferredModule[0]);
| ^ 46 | }
47 | }
48 | return result;

webpackJsonpCallback
/Users/bjoernrave/projects/coolplacesjs/webpack/bootstrap:32

29 | deferredModules.push.apply(deferredModules, executeModules || []);
30 |
31 | // run deferred modules when all chunks ready

32 | return checkDeferredModules();
| ^ 33 | };
34 | function checkDeferredModules() {
35 | var result;

(anonymous function)
http://localhost:3000/static/js/main.chunk.js:1:57`

update configure store example in readme

The code snippet for configuring the store in the README is incorrect. Maybe consider replacing it with the following:

import { composeWithDevTools } from 'redux-devtools-extension';
import { createStore } from 'easy-peasy';
import { model } from './models';

/**
 * model, is used for passing through the base model
 * the second argument takes an object for additional configuration
 */

const store = createStore(model, {
  compose: composeWithDevTools({ realtime: true, trace: true })
  // initialState: {}
});

export default store;

Thoughts on opt-in/opt-out for immer?

Would be cool if we could optionally opt-in for immer or optionally opt-out.

As middleware:

const store = createStore({
  todos: {
    items: ['Install easy-peasy', 'Build app', 'Profit']
  }
}, {
  middleware: [immerMiddleware()]
});

As a function wrapper:

const store = createStore({
  todos: {
    add: immer((state, payload) => {
      state.items.push(payload)
    })
  }
});

Add Unit test example to docs

Hi, I really like this library.
We should improve the docs with a dedicated section for best practices for writing unit tests.

Example for listen(on) contains usage of listeners api which is deprecated

Great lib. Helped me to reduce code and saved me from cognitive load with alternative approaches, as well.

I was going through new listen api. I think example for this api is using deprecated listeners api.

https://github.com/ctrlplusb/easy-peasy/tree/master#listenon

And since this example does not elaborate on listen api, please tell me, how should I refer to an action from another branch of the model, for the first argument of on function of listen?

Be able to provide a parameter to selector at runtime

I'm trying to make a getTodo selector that selects a specific todo from the store by passing in the todo's id into the selector. Here's a hypothetical example:

const store = createStore({
  todos: {
    items: [
      {id: 1, text: 'Todo 1'},
      {id: 2, text: 'Todo 2'},
    ],
    getTodo: select((state, id) => {
      // some logic I don't want to duplicate here

      return todo
    }),
  },
})

function TodoText(props) {
  const {todo_id} = props
  const todo = useStore(state => state.todos.getTodo(todo_id))

  return <span>{todo.text}</span>
}

The current implementation of selectors expose the results through properties instead of method calls. Adding the proposed functionality would change this pattern. I'm also not sure how this would fit within configuring the store.

Is this problem something that can be easily solved already, without introducing code changes? One solution is to import a function that can perform the computation, and have the component pass the current state into it to compute the result, but I think it would be more convenient to have the functionality attached to the store directly. Any thoughts?

Add TypeScript typings

I'm happy to do if you don't use TypeScript. I've begun a WIP, but have a way to go before it's in usable form. It may take me a bit of time to fit in around my other work....

Generalising effects/actions/data

Hello,

first: great and easy to use library. Thank you very much for it

I came along a small problem when trying to reuse my code. I have some state trees, that look very similar to each other. In general they all look the same: Some data, some actions, some selects and some effects. In the effects I need to call actions via the dispatch.

Here the ability to reuse stops, because I need to call the set method with an absolute path and not a relative one. But I might not know in general where and how deep I might be in the "store tree".

Is there an elegant way to solve this?

Cheers
Rüdiger

const store = {
  helper {
    material: {
      list: [],

      ids: select(state => R.pluck('id', state.list)),
      loaded: select(state => state.list === []),

      setList: (state, payload) => {
        return { ...state, list: payload}
      },

      loadAll: effect(async (dispatch, _payload, _getState) => {
        const data = await getAllMaterialien()
        dispatch.helper.material.setList(data) // <<-- I do not know how to generalize this to call this action of my part of the store
      }),
    },
    form: {
      list: [],

      ids: select(state => R.pluck('id', state.list)),
      loaded: select(state => state.list === []),

      setList: (state, payload) => {
        return { ...state, list: payload}
      },

      loadAll: effect(async (dispatch, _payload, _getState) => {
        const data = await getAllFormen()
        dispatch.helper.form.setList(data)
      }),
    },
  },
}

Bake in a "test" mode

Been thinking about this a bit more and it would be great to have this story baked in from the get go.

One idea would be to rather expose a log of actions that got called, along with their payloads, but make the operations a no-op.

Also encourage good practices with examples, showing how taking advantage of the "injections" parameter can help with this.

Problem with union types properties when Typescript has "strict" compilerOptions to true

Having the strict: true compiler option in tsconfig.json creates an error for union types with null

image
image

My files

// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "baseUrl": ".",
    "jsx": "react"
  }
}
// index.tsx
import React from "react";
import { render } from "react-dom";
import { Action, createStore } from "easy-peasy";

interface CartModel {
  products?: string[] | null;
  setProducts: Action<CartModel, string[] | null>;
}

interface Model {
  cart: CartModel;
}

const model: Model = {
  cart: {
    products: null,
    setProducts: (state, payload) => {
      state.products = payload;
    }
  }
};

const store = createStore<Model>(model);

const App = () => {
  return <h1>test</h1>;
};

render(<App />, document.getElementById("app"));

Codesandbox (have to download and run tsc to see the error): https://codesandbox.io/s/321ly1v241

Current workaround:
Set strictNullChecks to false

Property 'search' does not exist on type 'Dispatch<{}, Action<any>>'.

Hey there!
I love this library you have made.

Just downloaded the newest version, but something strange is happening in my typescript.

const saveText = useAction(dispatch => dispatch.search.saveText)

I'm using this in one of my components, and typescript is throwing that error.

My store is created like this:

import { createStore } from 'easy-peasy'
import search from '../models/search/search'

const store = createStore({
  ...search
})
export default store
import { effect } from 'easy-peasy'

const search = {
  search: {
    text: '',
    saveSuccessful: false,
    // Actions
    setText: (state, payload: string) => {
      state.text = payload // 👈 you mutate state to update (we convert
      state.saveSuccessful = true
    },
    // Effects
    saveText: effect(async(dispatch, payload: string, getState) => {
      try {
        // const result = await axios.post('https://google.ca/whatever', payload)
        dispatch.search.setText(payload)
      } catch (e) {
        throw e
      }
    })
  }
}

export default search

If I hover over search in the createStore variable, it seems to pick it up fine, but within the Search.tsx component, it gives me that error.

Some help would be appreciated. I know extremely little about typescript, but I assume this would work. 😅

I get a similar error for state:

Property 'search' does not exist on type 'State<{}>'.

    37   const saveSuccessful: boolean = useStore(state => state.search.saveSuccessful)

Helper example does not work

In the example you do const fetched = _.get(dispatch, meta.parent.join('fetched')); but it seems like the correct way would be to use meta.parent.concat('fetched') instead.

Let me know and I'll do a PR

TypeError: dispatcher.useContext is not a function

When trying easy-peasy in create-react-app I got the following error

TypeError: dispatcher.useContext is not a function

Full stack trace

react.development.js:1497 Uncaught TypeError: dispatcher.useContext is not a function
    at useContext (react.development.js:1497)
    at useStore (easy-peasy.esm.js:377)
    at TodoList (App.js:14)
    at mountIndeterminateComponent (react-dom.development.js:14592)
    at beginWork (react-dom.development.js:15082)
    at performUnitOfWork (react-dom.development.js:17903)
    at workLoop (react-dom.development.js:17944)
    at HTMLUnknownElement.callCallback (react-dom.development.js:147)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:196)
    at invokeGuardedCallback (react-dom.development.js:250)
    at replayUnitOfWork (react-dom.development.js:17224)
    at renderRoot (react-dom.development.js:18037)
    at performWorkOnRoot (react-dom.development.js:18919)
    at performWork (react-dom.development.js:18826)
    at performSyncWork (react-dom.development.js:18799)
    at requestWork (react-dom.development.js:18676)
    at scheduleWork (react-dom.development.js:18480)
    at scheduleRootUpdate (react-dom.development.js:19186)
    at updateContainerAtExpirationTime (react-dom.development.js:19212)
    at updateContainer (react-dom.development.js:19280)
    at ReactRoot.push../node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render (react-dom.development.js:19559)
    at react-dom.development.js:19713
    at unbatchedUpdates (react-dom.development.js:19064)
    at legacyRenderSubtreeIntoContainer (react-dom.development.js:19709)
    at Object.render (react-dom.development.js:19776)
    at Module../src/index.js (index.js:7)
    at __webpack_require__ (bootstrap:782)
    at fn (bootstrap:150)
    at Object.0 (serviceWorker.js:135)
    at __webpack_require__ (bootstrap:782)
    at checkDeferredModules (bootstrap:45)
    at Array.webpackJsonpCallback [as push] (bootstrap:32)
    at main.chunk.js:1

Dependencies

{
  "dependencies": {
    "easy-peasy": "^1.2.0",
    "react": "^16.7.0-alpha.0",
    "react-dom": "^16.6.0",
    "react-scripts": "2.1.1"
  }
}

Code is just copy-paste from the first example in README

import React, { Component } from "react";
import { StoreProvider, createStore, useStore } from "easy-peasy";

const store = createStore({
  todos: {
    items: ["Install easy-peasy", "Build app", "Profit"],
    add: (state, payload) => {
      state.items.push(payload);
    }
  }
});

function TodoList() {
  const todos = useStore(state => state.todos.items);

  return (
    <div>
      {todos.map((todo, idx) => (
        <div key={idx}>{todo.text}</div>
      ))}
    </div>
  );
}

class App extends Component {
  render() {
    return (
      <StoreProvider store={store}>
        <TodoList />
      </StoreProvider>
    );
  }
}

export default App;

Introduce "thunk", deprecate "effect"

Now that we have the listeners API I feel like we should revisit the effect API. I don't like that it's first argument, dispatch, exposes the entire model rather than be scoped, as state is within actions. Equally I dislike the number of arguments that effect accepts - I would prefer if we limit this to 3 arguments.

I want to avoid introducing breaking changes and therefore suggest introducing thunk. It has the same responsibility as effect but with an adapted API. We can then console log out warnings that the effect API is deprecated and will be removed in next major release.

Current effect API:

{
  todos: {
    items: {},
    add: (state, payload) => {
      state.items[payload.id] = payload;
    },
    //  arguments count: 😱     1         2       3           4        5
    fetchById: effect(async (dispatch, payload, getState, injections, meta) => {
	  const todo = await todoService.getById(payload);
      // 😱    👇 annoying having to path through every time
	  dispatch.todos.add(todo);
    })
  }
}

Proposed thunk API:

{
  todos: {
    items: {},
    add: (state, payload) => {
      state.items[payload.id] = payload;
    },
    fetchById: thunk(async (actions, payload, helpers) => {
	  const todo = await todoService.getById(payload);
      // 👇 actions scoped to model
	  actions.add(todo);
      // 👇 "helpers" wraps up all the additional items that 
      //    typically won't be used very often (IMO)
      const { getState, injections, meta, dispatch } = helpers
    })
  }
}

Thoughts?

How to have actions or effects mutate disconnected parts of the state tree?

Let's say I have got a model slice todos. I then add a new model slice toast for toast notifications. I want the toast state to get mutated (and perhaps do async stuff) as a result of todo actions.

In vanilla Redux, for sync mutations, I'd add extra switch cases in my toast reducer, and typically use one of two approaches for async

  • add a custom toastMiddleware that dispatches toast actions when it encounters a todo.add action, or
  • (if the site was using Redux Saga) have a toastSaga take a todo.add action and then dispatch toast actions

Is there a different, easy-peasy, way for sync or async mutations to model slices based on actions (or effects) evoked on other parts of the state tree? A reducer at the toast level seems a good fit for sync, but both that and the toastMiddleware/saga route would seem to need an idiomatic way to get the correct action.type string.

(These also might need a typing helper to get the shape of the vanilla Redux action in Typescript... 🤔)

Anyone run into this and found a pattern that works?

SetState function

Would be handy to have set function on each level:

				set: (state, newState) => {
					Object.assign(state, newState)
				},

What do you think?

Make state be a primitive

How can I make a model be a primitive, e.g userNameModel is just a string but I also want this model to have methods? I don't want to make an object with a separate property on it, this adds unnecessary boilerplate. E.g, accessing via state.user instead of state.user.name

Can't seem to get middleware to work

Trying to hook up some middleware but it doesn't seem to work.
Simple code is as follows:
`
import { createStore} from "easy-peasy";
import { applyMiddleware } from "redux";

const logger = store => next => action => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
export const store = createStore({
{my store here}
},
applyMiddleware(logger)
);`

It's probably me with a simple mistake (99% of the time it is) but I'm not having any luck and I think it's an error...

Thank you!

Fetch from Cloud Firestore

I am trying to fetch data from my Cloud Firebase, but I can't figure out how to do it. I tried following the guide, but nothing works. I am only getting in an endless loop. What am I doing wrong?

My store

article: {
    items: [],

    fetch: effect(async (dispatch, payload, getState) => {
      const saved = firebase
        .firestore()
        .collection("article")
        .get()
        .then(function(querySnapshot) {
          querySnapshot.forEach(function(doc) {
            return doc.data();
          });
        });

      dispatch.article.fetched(saved);
    }),

    fetched: (state, payload) => {
      state.items.push(payload);
    }
  },
  // effects
  initialise: effect(async dispatch => {
    await dispatch.article.fetch();
  })

My simple article component:

import { useStore } from "easy-peasy";

const Articles = () => {
  const articles = useStore(state => state.article.items);
  return (
    <div>
      <div>Articles:</div>
      {articles.map(item => (
        <div key={item.id}>{item.title}</div>
      ))}
    </div>
  );
};

Warning: useLayoutEffect does nothing on the server

Hi,
just a quick thank you for this lib, it look awesome 😄
I've been checking how it could be used in a NextJS project, and while I'm still not comfortable with hooks I wonder how this warning can be a problem :

Warning: useLayoutEffect does nothing on the server, because its effect
 cannot be encoded into the server renderer's output format. 
This will lead to a mismatch between the initial, non-hydrated UI 
and the intended UI. To avoid this, useLayoutEffect should only be 
used in components that render exclusively on the client.

The whole purpose of nextJS is to provide SSR out of the box, and this warning happens if I use your useStore hook in a function component rendered server-side. It does seem to work (I've been only trying with hardcoded store data), but I think you may want to know this use case and eventually provide a hook which uses useEffect if possible 🤔

Also, if anybody has some examples using nextjs & easy-peasy that would be awesome to share them !

cheers 👏

redux, redux-thunk, immer as peer-dependencies

HI! I'm digging easy-peasy. I'm curious as to why redux, redux-thunk, and immer are hard dependencies, but react is a peer-dependency. Could you gain some flexibility with existing redux projects by making them peerDeps instead?

Unused "endpoint" parameter in the example docs

There is a small mistake in the example of how to create a reusable helper. You expect an endpoint function but then proceed to use fetchProducts().

import _ from 'lodash';

const data = (endpoint) => ({
  data: {},
    ids: select(state => Object.keys(state.data)),
    fetched: (state, items) => {
      items.forEach(item => {
        state.data[item.id] = item;
      });
    },
    fetch: effect((dispatch, payload, getState, injections, meta) => {
      //                                                     👆
      // We can get insight into the path of the effect via the "meta" param
      const data = await fetchProducts();
      // Then we utilise lodash to map to the expected location for our
      // "fetched" action
      //                 👇
      const fetched = _.get(dispatch, meta.parent.join('fetched'));
      fetched(data);
    })
})

Can't seem to hook this up to gatsby

Using the standard way of connecting Gatsby to Redux doesn't work with the easy-peasy provider.

useStore and useAction work in the sense that you can see actions and state changes in Redux DevTools, but the App component tree never gets updated.

I think it's possibly because Gatsby uses reach-router, so the wrapRootElement API which is usually used to add providers to the tree passes an element and not the root component?

Any thoughts on how to hook easy-peasy up to Gatsby without falling back to the react-redux provider and connect methods (and losing out on useStore/useAction)

Persist store ?

Is there any way to persist store with redux-persist library ?

Typescript definitions of the EasyPeasyConfig define Injections as void

Inside the typescript definitions of the easy peasy config, it says that the Injections are void

Because of that, injecting injections inside a store with typescript requires you to say as any to let the compiler pass.

I think this should use typescript generics like for the Model to make sure you can actually pass the type of injections you want

Problematic line: Injections = void

export interface EasyPeasyConfig<
  InitialState extends Object = {},
  Injections = void
> {
  compose?: typeof compose
  devTools?: boolean
  initialState?: InitialState
  injections?: Injections
  middlewares?: Array<Middleware<any, any, any>>
  reducerEnhancer?: (reducer: Reducer<any, any>) => Reducer<any, any>
}

Compared to the model that uses generics for example:

export function createStore<Model extends Object = {}>(
  model: Model,
  config?: EasyPeasyConfig,
): Store<Model>

Redux Devtools Extension freezes

My store contains a nested field with thousands of items.

In development mode, when I change some code, due to HMR, the app does refresh>

But when Redux DevTools chrome extension is opened, it freezes the app when I change the code.

When I remove the part that has thousands of items, from store, both (w and w/o ReduxDevtools extension) scenarios run smooth.

Please help me to sanitize the state.
or
to use Redux Devtools (not chrome extension)

I have tried both the options, with my limited competency. But failed.

Listen to reducers

Is there a way to listen to actions added through reducers? Trying to run easy-peasy actions in response to a Router changes via redux-first-history, and listen only applies to those in easy-peasy's format.

I've updated the todo sandbox and changed preferences to a reducer to simulate. Would this have to run through some custom middleware?

Example
https://codesandbox.io/s/zqx558q8jl

// Example reducer
function reducerPreferences(state = { theme: "light" }, action) {
  switch (action.type) {
    case "toggle": {
      return { ...state, theme: state.theme === "light" ? "dark" : "light" };
    }
    default: {
      return state;
    }
  }
}

const log = msg => (...args) => console.log(msg, ...args);

const model = {
  preferences: reducer(reducerPreferences),
  todos: {
    items: {},
    listeners: listen(on => {
      // Works
      on(model.todos.fetched, log("fetched"));

      // How to listen to reducer?
      on(reducer(reducerPreferences), log("reducerPreferences"));
      on(model.preferences, log("preferences"));
      on(model.preferences.toggle, log("toggle"));
      on({ type: "toggle" }, log("actionToggle"));
    }),

integrate third-party redux tools with createStore

How would one implement https://github.com/supasate/connected-react-router with easy-peasy -- specifically around the createStore documentation?

easy-peasy has it's own createStore function that accepts an object, but redux's createStore accepts a function. I'd like to know how to integrate modules like connected-react-router with easy-peasy (if it's possible).

I apologize this isn't an "issue", however it's unclear from the documentation on how to accomplish this. I'll make a PR to update the readme based on feedback from this issue.

Lazy reducers

Often when code splitting, it's necessary to lazily add reducers. I haven't used easy-peasy yet, so if there is an idiomatic way, a quick doc update would be great!

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.