Observable Data Store Service with different Data Sets In Angular2 - angularjs

I'm trying to follow the "Observable data store" pattern in Angular 2 (detailed in this blog post from Angular University) From what I understand, this means that, if I have a service called TodoStore, I would subscribe to TodoStore.items$ so I could get all the latest updates for my to-do list in real time (as other components add, remove, or edit Todos.
However, what if I have two components side-by-side that display different Todolists, filtered on the server-side? One might display Todos due today while another would show Todos due on a user-selected date.
In this case, I would not be able to use the same TodoStore, as both would be fetching different data from the server. How do I handle sharing this service between these two components? My urge is to go back to an angular1-style getToDoList() service method, but then I'm storing state inside a single component and can't possibly share data between multiple components without making extra API calls.

If your lists really have to be filtered server-side and you have an unknown number of simultaneously displayed lists and a new server-request has to me made for each list + filter, then it is perfectly possible that using a single observable (TodoStore.items$) might not be a viable solution here and maybe some kind of getTodoList(forFilter) might be easier/quicker to implement.
Remeber: There is no such thing as "The perfect solution for all cases."
However: Even in this case you could use a store, which could something like this:
interface ITodoListDictionary {
[key: string]: Todo[];
}
#Injectable()
export class TodoStore {
todoLists$: BehaviorSubject<ITodoListDictionary> = new BehaviorSubject<ITodoListDictionary>({});
getListByKey(key: string): Observable<Todo[]> {
return this.todoLists$
.pluck(key)
.distinctUntilChanged() // optional, so you only get the update if there is an actually new list
.share();
}
// this would be called whenever the rest-response for a given list arrives
setListByKey(key: string, list: Todo[]): void {
this.todoLists$.take(1)
.do(dict => {
const newDict = Object.assign({}, dict, {[key]: list});
// if you are using TS2.1 you could also use:
// const newDict = {...dict, {[key]: list}};
this.todoLists$.next(newDict);
})
.subscribe();
}
}
...and in your template you could use it like this
<todo *ngFor="let todo of todoStore.getListByKey(someKey) | async" todo="todo"></todo>
Please keep in mind that is just one possible solution out of many - without seeing your actual application-flow it is hard to tell which might be the best solition.

Related

Create and Read State for thousands of items using Recoil

I've just started using Recoil on a new project and I'm not sure if there is a better way to accomplish this.
My app is an interface to basically edit a JSON file containing an array of objects. It reads the file in, groups the objects based on a specific property into tabs, and then a user can navigate the tabs, see the few hundred values per tab, make changes and then save the changes.
I'm using recoil because it allows me to access the state of each input from anywhere in my app, which makes saving much easier - in theory...
In order to generate State for each object in the JSON file, I've created an component that returns null and I map over the initial array, create the component, which creates Recoil state using an AtomFamily, and then also saves the ID to another piece of Recoil state so I can keep a list of everything.
Question 1 Is these a better way to do this? The null component doesn't feel right, but storing the whole array in a single piece of state causes a re-render of everything on every keypress.
To Save the data, I have a button which calls a function. That function just needs to get the ID's, loop through them, get the state of each one, and push them into an Array. I've done this with a Selector too, but the issue is that I can't call getRecoilValue from a function because of the Rules of Hooks - but if I make the value available to the parent component, it again slows everything right down.
Question 2 I'm pretty sure I'm missing the right way to think about storing state and using hooks, but I haven't found any samples for this particular use case - needing to generate the state up front, and then accessing it all again on Save. Any guidance?
Question 1
Get accustomed to null-rendering components, you almost can't avoid them with Recoil and, more in general, this hooks-first React world 😉
About the useRecoilValue inside a function: you're right, you should leverage useRecoilCallback for that kind of task. With useRecoilCallback you have a central point where you can get and set whatever you want at once. Take a look at this working CodeSandbox where I tried to replicate (the most minimal way) your use-case. The SaveData component (a dedicated component is not necessary, you could just expose the Recoil callback without creating an ad-hoc component) is the following
const SaveData = () => {
const saveData = useRecoilCallback(({ snapshot }) => async () => {
const ids = await snapshot.getPromise(carIds);
for (const carId of ids) {
const car = await snapshot.getPromise(cars(carId));
const carIndex = db.findIndex(({ id }) => id === carId);
db[carIndex] = car;
}
console.log("Data saved, new `db` is");
console.log(JSON.stringify(db, null, 2));
});
return <button onClick={saveData}>Save data</button>;
};
as you can see:
it retrieves all the ids through const ids = await snapshot.getPromise(carIds);
it uses the ids to retrieve all the cars from the atom family const car = await snapshot.getPromise(cars(carId));
All of that in a central point, without hooks and without subscribing the component to atoms updates.
Question 2
There are a few approaches for your use case:
creating empty atoms when the app starts, updating them, and saving them in the end. It's what my CodeSandbox does
doing the same but initializing the atoms through RecoilRoot' initialState prop
being updated by Recoil about every atom change. This is possible with useRecoilTransactionObserver but please, note that it's currently marked as unstable. A new way to do the same will be available soon (I guess) but at the moment it's the only solution
The latter is the "smarter" approach but it really depends on your use case, it's up to you to think if you really want to update the JSON at every atom' update 😉
I hope it helps, let me know if I missed something 😊

Is it OK for a reducer to listen to other actions?

At the moment I'm creating actions and then a reducer to handle different parts of my app... the different domains.
My app lists classes and pupils.
Currently I have an action that the app has loaded so that I know when to remove the loading spinner, I have actions for classes and pupils. My problem is that I find I need to execute several actions in a row and am not sure if this is a valid way to use redux.
Here is an example function that dispatches several actions after the data is loaded:
/**
* Callback used by readAppData.
*
* #param object ioResult An object: {success: boolean, errorObj?: object, data?: string}
*/
dataLoaded(ioResult: Object) {
if (ioResult.success === true) {
this.props.dispatch(appActions.appHasLoaded());
if (ioResult.data !== '') {
const jsonData = JSON.parse(ioResult.data);
this.props.dispatch(classActions.replaceClasses(jsonData.classes));
this.props.dispatch(pupilActions.replacePupils(jsonData.pupils));
}
} else if (ioResult.errorObj.code === 'ENOENT') { // File doesn't exist.
writeAppData('', this.dataSaved);
} else { // Some other error.
this.props.dispatch(appActions.appHasErrored());
}
}
I was thinking about putting the jsonData.classes and jsonData.pupils into the appActions.appHasLoaded() call and just have a new case for APP_HAS_LOADED in my classes and pupils reducers.
Is this a better approach? To reduce it down to one action? Having separate actions makes it easy to see what is happening... maybe in 6 months time I will have to look through my code to work out exactly what happens on APP_HAS_LOADED if I use it in other reducers. Also the data that is loaded on app start is going to expand beyond just classes and pupils so if I don't combine the calls there could soon be many more dispatches to make - maybe I can store the data in separate files and load each one one at a time which would also fix my problem of having to call mutiple actions in a row.
Is it OK to call dispatch multiple times?
Yes, you can.
From Redux creator Dan Abramov:
Many reducers may handle one action. One reducer may handle many actions.
Referenced in Redux Docs
also, there is a conversation on github about this.

Global variables in React

I know Redux solves this but I came up with an idea.
Imagine I have an app that gets some JSON on start. Based on this JSON I'm setting up the environment, so let's assume the app starts and it downloads an array of list items.
Of course as I'm not using Redux (the app itself is quite simple and Redux feels like a huge overkill here) if I want to use these list items outside of my component I have to pass them down as props and then pass them as props again as deep as I want to use them.
Why can't I do something like this:
fetch(listItems)
.then(response => response.json())
.then(json => {
window.consts = json.list;
This way I can access my list anywhere in my app and even outside of React. Is it considered an anti-pattern? Of course the list items WON'T be changed EVER, so there is no interaction or change of state.
What I usually do when I have some static (but requested via API) data is a little service that acts kind like a global but is under a regular import:
// get-timezones.js
import { get } from '../services/request'
let fetching = false
let timez = null
export default () => {
// if we already got timezones, return it
if (timez) {
return new Promise((resolve) => resolve(timez))
}
// if we already fired a request, return its promise
if (fetching) {
return fetching
}
// first run, return request promise
// and populate timezones for caching
fetching = get('timezones').then((data) => {
timez = data
return timez
})
return fetching
}
And then in the view react component:
// some-view.js
getTimezones().then((timezones) => {
this.setState({ timezones })
})
This works in a way it will always return a promise but the first time it is called it will do the request to the API and get the data. Subsequent requests will use a cached variable (kinda like a global).
Your approach may have a few issues:
If react renders before this window.consts is populated you won't
be able to access it, react won't know it should re-render.
You seem to be doing this request even when the data won't be used.
The only downside of my approach is setting state asynchronously, it may lead to errors if the component is not mounted anymore.
From the React point of view:
You can pass the list from top level via Context and you can see docs here.
Sample of using it is simple and exists in many libraries, such as Material UI components using it to inject theme across all components.
From engineering concept of everything is a trade of:
If you feel that it's gonna take so much time, and you are not going to change it ever, so keep it simple, set it to window and document it. (For your self to not forget it and letting other people know why you did this.)
If you're absolutely certain they won't ever change, I think it's quite ok to store them in a global, especially if you need to access the data outside of React. You may want to use a different name, maybe something like "appNameConfig"..
Otherwise, React has a feature called Context, which can also be used for "deep provision" - Reference

Flux/ReactJS - One Store per API endpoint or One Store to all API endpoints?

I'm facing a bit of confusion in the planning of my ReactJS/Flux app. It will be hooking into the WP Api which has the following endpoints:
/api/stores/
/api/posts/
/api/settings/
What would be the best approach from a build perspective? My thoughts currently are to have the following:
API -> ActionCreator -> Store -> ActionCreator -> View(s)
Meaning that in some cases each view will contain change listeners for up to three stores and the potential for a lot of duplicated code.
Another alternative is one actionCreator to many stores, but I am not sure which is the best for scalability.
Can anyone help on this?
ActionCreator is not involved between stores and views just between server/store or views/server
API -> ActionCreator -> Store ---binding_eventChange---> View(s) --actionCreator-->request ...etc
For my api I use one store for each endpoint of the api which are one type of data.
A view will be indeed binded to many stores (3,4,5stores ...etc) that's why I cut the view in several subviews, each subview is binded to one type of data/store.
So I have a big parent view wich render several sub tiny views, it allows to have simple (sub)views easy to read and it also make the code very modular, you can easily make composition of subviews.
Edit:
In your example in comment what you want to do is: 1.Loading your view 2.Your view is requesting a type of data 3.your store receive the asked data. 4.Your view, binded to the store, updates using the new data.
I'll write it quickly in pseudo-code:
1.In your view:
componentDidMount() {
//calling your actioncreator to init the first call to the server
ActionCreator.requestSomeTypeofData();
// Set your store listener so your view can update when the data are here
SomeTypeofDataStore.on('change', handler);
}
handler: function() {
var data = SomeTypeofDataStore.getAll();
this.setState({ data:data });
}
In your actionCreator:
actionCreator.requestSomeTypeofData = function() {
MakeAjaxCall({
success: function(data) {
Dispatcher.handleViewAction({
actionType:"SomeTypeofData",
result: data
});
}
}
Store: when the ajax call is done, new data are dispatched to the store
Basically you are using Dispatcher.register and a switch to select the right action for your store (I let you look at the flux tutorial for that but I guess you know that already).
You also bind a 'change' event when you register new data (see tutorial and how they use EventEmitter that's easy). (example here: https://facebook.github.io/flux/docs/todo-list.html#creating-stores)
Your view receive the update event, call the handler register new state and rerender with your new data
I hope it's clear :)

Relationship between list and item with relational data store (using react-redux)

I'm using React with Redux as my store. I'm also using the react-redux library to integrate the two. My store contains two sets of data:
Task { id, name, assigneeId }
User { id, name }
I have a TaskListComponent which (using react-redux) connect-s to my store using:
#connect(state => {
tasks: state.tasks,
users: state.users
})
I need both because the list has a filter allowing for searching my task name or user name - my component needs to be 'user aware'.
This component lists out another component called TaskItemComponent which displays the task name and the name of the assigned user.
I'm struggling to decide on the best approach for resolving a task's assigned user.
React guidelines tell me that the Item component should take a Task as a prop, and resolve the User itself in the render function. However, this requires the component to be store-aware and, using the react-redux library, it doesn't appear to be designed to allow a component to access the store without being connect-ed to it.
Or I could resolve the User in the list and pass it to the Item component along with the task, e.g. <TaskItemComponent task={task} assignee={resolveTaskAssignee(task)} />. This has the benefit of keeping my Item 'dumb', and means I don't need to have it listening to store changes (or even know about the store).
Any advice would be appreciated.
Both approaches are fine. I'd start with passing props from outside, and once it gets tedious, let the Task component receive task as a prop but read its user by using connect.
See also https://stackoverflow.com/a/25701169/458193.
There's nothing inherently wrong with option #2. You have to do data ops somewhere. It will always be the right place to do those ops in the smart component.
Personally, I'd prefer a better data model in the store. But, you got what you got.

Resources