How to model relational data in an Om-like immutable app state - javascript

I'm trying to decide whether to use a more traditional Flux implementation or to go with an Om-like structure. I really like the idea of using a single immutable app state object with cursors in javascript, but I'm unsure how to model relational data. I'm looking at using something like Morearty.
My question is how do I avoid duplicating data and deal with nested relational data sent from the server? Let's say I have REST endpoint that gives me inventory and each inventory item has a nested vendor. I also have an endpoint of vendors. I want to have a list of all vendors in my app state, but also reference those vendors on my inventory items. When I make an update to a vendor, I want it to change on all the inventory items that reference that vendor.
Does an Om-like structure work for this kind of application or would a more traditional Flux style app with discreet stores be better?

You probably want to look into something like Redux as your "flux" framework. It's absolutely excellent — we use it at Docker. Here's why it's good and how it can help you.
Why use redux?
It uses a single store model. All stores save state within Redux itself in their own key. An example of the state Redux saves is:
{
vendors: [{...}, ...], // The "vendor" store updates this part of the state
inventory: [...] // the "inventory" store updates this part of the state
}
And Redux, or a redux provider, is the parent of all components. Therefore all components receive the state as props.
How does a single store model improve things?
Each individual "store" which responds to actions within Redux updates only one part of the state object. For example, the "vendor" store only updates the "vendor" key of the state. The individual store is given the current state each time an action happens. This makes stores entirely pure. This is great for testing, immutable data, hot-reloading, rewinds etc.
Because your single top-level Redux store saves all state, each component can receive this state as props and automatically re-render whenever this state changes — even from unrelated components out of hierarchy.
Here's an example of a single store in action, so you can get the idea:
import assign from 'object-assign';
import consts, { metrics, loading, URLS } from 'consts';
const actions = {
// As you can see, each action within a store accepts the current "state"
// and can modify that state by returning new data.
[metrics.FETCH_METRICS_PENDING]: (state, data) => {
return assign({}, state, {status: loading.PENDING});
},
[metrics.FETCH_METRICS_SUCCESS]: (state, data) => {
return assign({}, state, {status: loading.SUCCESS, metrics: data.payload});
},
[metrics.FETCH_METRICS_FAILURE]: (state, data) => {
return assign({}, state, {status: loading.FAILURE});
},
[metrics.OBSERVE_METRICS_DATA]: (state, data) => {
let metrics = state.metrics.slice().concat(data.payload);
return assign({}, state, {metrics});
}
}
// This is the single method that's exported and represents our store.
// It accepts the current state as its primary argument, plus the action
// data as a payload.
//
// This delegates to the methods above depending on `data.type`.
export default function metricStore(state = {}, data) {
if (typeof actions[data.type] === "function") {
return actions[data.type](state, data);
}
return state;
}
How can this model relational data?
Each time data is requested via an action creator it can dispatch multiple actions which update the vendor and inventory state together. This will be reflected in your app within one render.

Related

Vuex/Redux store pattern - sharing single source of data in parent and child components that require variations of that data

I understand the benefits of using a store pattern and having a single source of truth for data shared across components in an application, and making API calls in a store action that gets called by components rather than making separate requests in every component that requires the data.
It's my understanding that if this data needs to change in some way, depending on the component using the data, this data can be updated by calling a store action with the appropriate filters/args, and updating the global store var accordingly.
However, I am struggling to understand how to solve the issue whereby a parent component requires one version of this data, and a child of that component requires another.
Consider the following example:
In an API, there exists a GET method on an endpoint to return all people. A flag can be passed to return people who are off sick:
GET: api/people returns ['John Smith', 'Joe Bloggs', 'Jane Doe']
GET: api/people?isOffSick=true returns ['Jane Doe']
A parent component in the front end application requires the unfiltered data, but a child component requires the filtered data. For arguments sake, the API does not return the isOffSick boolean in the response, so 2 separate requests need to be made.
Consider the following example in Vue.js:
// store.js
export const store = createStore({
state: {
people: []
},
actions: {
fetchPeople(filters) {
// ...
const res = api.get('/people' + queryString);
commit('setPeople', res.data);
}
},
mutations: {
setPeople(state, people) {
state.people = people;
}
}
});
// parent.vue - requires ALL people (NO filters/args passed to API)
export default {
mounted() {
this.setPeople();
},
computed: {
...mapState([
'people'
])
},
methods: {
...mapActions(['setPeople']),
}
}
// child.vue - requires only people who are off sick (filters/args passed to API)
export default {
mounted() {
this.setPeople({ isOffSick: true });
},
computed: {
...mapState([
'people'
])
},
methods: {
...mapActions(['setPeople']),
}
}
The parent component sets the store var with the data it requires, and then the child overwrites that store var with the data it requires.
Obviously the shared store var is not compatible with both components.
What is the preferred solution to this problem for a store pattern? Storing separate state inside the child component seems to violate the single source of truth for the data, which is partly the reason for using a store pattern in the first place.
Edit:
My question is pertaining to the architecture of the store pattern, rather than asking for a solution to this specific example. I appreciate that the API response in this example does not provide enough information to filter the global store of people, i.e. using a getter, for use in the child component.
What I am asking is: where is an appropriate place to store this second set of people if I wanted to stay true to a store focused design pattern?
It seems wrong somehow to create another store variable to hold the data just for the child component, yet it also seems counter-intuitive to store the second set of data in the child component's state, as that would not be in line with a store pattern approach and keeping components "dumb".
If there were numerous places that required variations on the people data that could only be created by a separate API call, there would either be a) lots of store variables for each "variation" of the data, or b) separate API calls and state in each of these components.
Thanks to tao I've found what I'm looking for:
The best approach would be to return the isOffSick property in the API response, then filtering the single list of people (e.g. using a store getter), thus having a single source of truth for all people in the store and preventing the need for another API request.
If that was not possible, it would make sense to add a secondary store variable for isOffSick people, to be consumed by the child component.

How can I fetch backend data in child component and then retrieve an object from store?

My parent component <Room/> build children components <RoomSensor/>, when parent build these children I also send to the <RoomSensor/> uuid, by this uuid I fetch sensor data from a backend.
Store is array of objects.
// Parent <Room/>
return props.sensors.map((uuid, index) => {
return <RoomSensor key={index} uuid={uuid}/>
})
// Children <RoomSensor/>
const RoomSensor = props => {
useEffect(() => {
console.log("useEffect")
props.fetchSensor(props.uuid)
}, [props.uuid])
console.log(props.sensor)
return (
<div className={"col-auto"}>
<small><b>{props.sensor.value}</b></small>
</div>
)
}
let mapStateToProps = (state, props) => {
return {
sensor: filterSensor(state, props.uuid)
}
}
let mapDispatchToProps = {
fetchSensor,
}
export default connect(mapStateToProps, mapDispatchToProps)(RoomSensor)
// Selectors
export const getSensor = (state, uuid) => {
return _.filter(state.sensors, ["uuid", uuid])[0]
}
export const filterSensor = createSelector(
getSensor,
(sensor) => sensor
)
And I cannot understand two things:
When I do refresh I get.
TypeError: Cannot read property 'uuid' of undefined
I understand that there is no data in the state yet, that's why such an error occurs. Is it possible not to render the component until the data comes from the server?
If I comment <small><b>{props.sensor.value}</b></small> no errors occur, data appears in the store, then I uncomment this line and voila everything works. But in the console I see too many component rerende. What am I doing wrong? Is there something wrong with the selector?
In general, I want each sensor component to render independently of the others.
The following is based on a few assumptions derived from the shared code and output:
Currently, there's a hard-coded list of 4 sensor UUIDs.
createSelector is from the reselect package.
_ references an import of the lodash package.
"Is it possible not to render the component until the data comes from the server?"
The short answer to this is yes. There're several approaches to achieve this, so you'll want to evaluate what fits best with the structure of the app. Here're 2 approaches for consideration:
Retrieve the list of sensors from the server. Initialize the store with an empty list and populate it upon getting data back from the server.
In getSensor, return a default value if the uuid isn't in the list.
Either way, I'd recommend adding default state to the store. This will help reduce the amount of code required to handle edge cases.
Here's a rough example of what the new reducer and selector for (1) might look like:
export const storeReducer = (state, action) => {
let nextState = state;
if (!state) {
// State is uninitialized, so give it a default value
nextState = {
sensors: [],
};
}
switch (action.type) {
case 'RECEIVE_SENSORS':
// We received sensor data, so update the value in the store
nextState = {
...nextState,
sensors: action.sensors,
};
break;
default:
break;
}
return nextState;
};
export const getSensors(state) {
return state.sensors;
}
The action upon receiving the data from the server, could look something like:
dispatch({
sensors,
type: 'RECEIVE_SENSORS',
})
"...in the console I see too many component rerende[rs]"
Without additional context, it's hard to say exactly why the re-renders are happening, but the most likely cause is that each call to props.fetchSensor(props.uuid) changes the data in the store (e.g. if it's overwriting the data).
From the console output you shared, we see that there're 16 re-renders, which would happen because:
Each of the 4 instances of RoomSensor calls fetchSensor
This results in 4 updates to the store's state
Each update to the store's state causes React to evaluate each instance of RoomSensor for re-render
Hence, 4 state updates x 4 components evaluated = 16 re-renders
React is pretty efficient and if your component returns the same value as the previous run, it knows not to update the DOM. So, the performance impact probably isn't actually that significant.
That said, if the above theory is correct and you want to reduce the number of re-renders, one way to do it would be to check whether the data you get back from the server is the same as what's already in the store and, if so, skip updating the store.
For example, fetchSensor might be updated with something like:
const existingData = getSensor(getState(), uuid);
const newData = fetch(...);
// Only update the store if there's new data or there's a change
if (!existingData || !_.isEqual(newData, existingData)) {
dispatch(...);
}
This would require updating getSensor to return a falsey value (e.g. null) if the uuid isn't found in the list of sensors.
One additional tip
In Room, RoomSensor is rendered with its key based on the item's index in the array. Since uuid should be unique, you can use that as the key instead (i.e. <RoomSensor key={uuid} uuid={uuid} />). This would allow React to base updates to RoomSensor on just the uuid instead of also considering the order of the list.

React Redux: How to share data between reducers?

I have a basic todo list. The todo input field for the todo list has an onChange event that triggers an action and sends event.target.value to a reducer and stores each character the users types to a property of the store object.
When the user submits the form I want to get the data that was previously stored via the onChange event, then I want to place it on a new property on the store object.
How do I get data that was previously entered from the store and pull it into a different reducer?
In all the examples I've seen, reducers start with an "initial state". I don't want that, I want the previous state that the user entered.
Below is a CodeSandbox version of the code (for some reason the orange-beige tab to the right needs to be switch to the left to blue for it to render the form. If you don't do that it won't work).
https://codesandbox.io/s/pwqlmp357j
Ask yourself whether you've structured your reducers correctly. If a
and b are not independent of one another, why are they separate
reducers?
Well if we talk about correctly structuring the reducer, cachedInput and todoList should live in single reducer. There is no need to create another reducer. Also I guess you need to grab the input value all at once(when user clicks on submit) and send it to store.
If still you want to create seperate reducers then you can follow the approach given below :
Answer to How to access or share data already in store, inside the reducers?
Don't use combineReducers.
Example
replace this code
export const a = combineReducers({
cachInput,
newTodo
});
with
export default (state = {}, action) => {
return {
cachInput: cachInput(state.app, action, state),
newTodo: newTodo(state.posts, action, state),
};
};
reducer would be like
const reducer = (state = initialState, action, root) => {....}
and you can access the previous state from root
onChange event that triggers an action and sends event.target.value to a reducer and stores each character the users types to a property of the store object
Practically, this is not right way to use to redux->action->reducers->store considering you are submitting the form to send the same data to reducers.
This will caused un-necessary rendering of component connected with redux#connect if you have not handled shouldComponentUpdate /componetWillRecieve/getDerivedStateFromProps nicely
Instead store the each character/string that you typed, inside component state and once the user submit it, dispatch the action to pass the string/characters to reducer's.
You are using two different state(or say initialState) for each reducer. Hence update in one object is not reflecting in the other.
You need to use the shared initialState for both of the reducer and your problem is solved.
You can keep initialState in different file and you can import in both the reducers.
Consider below code sample:
1.InitialState.js
export default initialState = {
todoList: [
{ text: "fake todo" },
{ text: "fake todo" },
{ text: "fake todo" }
],
cachedInput: ""
};
2.Now in CachDataOfTodoInput.js
import initialState from "InitialState";
export function cachInput(state = initialState, action)
3.Same in SubmitDataToTodo.js
import initialState from "InitialState";
export function submitNewTodo(state = initialState, action)
Hence intialState is shared between your reducers and you will be able to access data in each reducer.
If you want to load your application with initial state that contains previously cached data and if you are using client side rendering, you may insert cached data in redux store into local storage and use that as initial state.
If you just want data to be imported to the other reducer after form submission, you may use redux-thunk. It lets you load current state in your submit action and pass the catches values into the other reducer and store them.
if you are using ssr and you want data be loaded on server there is no way unless saving data on server ( in a file or database), read the data when request is received on server and update your redux store. It will load data all over your app.

Callbacks using redux-thunk / redux-observable with redux

I am learning how redux works but its a lot of code to do simple things. For example, I want to load some data from the server before displaying. For editing reasons, I can't simply just use incoming props but I have to copy props data into the local state.
As far as I've learned, I have to send a Fetch_request action. If successful, a fetch_success action will update the store with new item. Then updated item will cause my component's render function to update.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id);
}
...
In actions
export function FETCH_REQUEST(id) {
api.get(...)
.then(d => FETCH_SUCCESS(d))
.catch(e => FETCH_FAILURE(e));
}
...
In reducer
export function FETCH_REDUCER(state = {}, action ={}) {
switch (action.type) {
case 'FETCH_SUCCESS':
return { ...state, [action.payload.id]: ...action.payload }
...
}
Back in component
this.props.FETCH_REDUCER
// extra code for state, getting desired item from...
Instead, can I call a react-thunk function and pass some callback functions? The react-thunk can update the store and callbacks can change the component's local state.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id, this.cbSuccess, this.cbFailure);
}
cbSuccess(data) {
// do something
}
cbFailure(error) {
// do something
}
...
In action
export function FETCH_REQUEST(id, cbSuccess, cbFailure) {
api.get(...)
.then(d => {
cbSuccess(d);
FETCH_SUCCESS(d);
}).catch(e => {
cbFailure(d);
FETCH_FAILURE(e);
});
}
...
Is this improper? Can I do the same thing with redux-observable?
UPDATE 1
I moved nearly everything to the redux store, even for edits (ie replaced this.setState with this.props.setState). It eases state management. However, every time any input's onChange fires, a new state is popping up. Can someone confirm whether this is okay? I'm worried about the app's memory management due to redux saving a ref to each state.
First of all, you should call your API in componentDidMount instead of componentWillMount. More on this at : what is right way to do API call in react js?
When you use a redux store, your components subscribe to state changes using the mapStateToProps function and they change state using the actions added a props through the mapDispatchToProps function (assuming you are using these functions in your connect call).
So you already are subscribing to state changes using your props. Using a callback would be similar to having the callback tell you of a change which your component already knows about because of a change in its props. And the change in props would trigger a re-render of the component to show the new state.
UPDATE:
The case you refer to, of an input field firing an onChange event at the change of every character, can cause a lot of updates to the store. As mentioned in my comments, you can use an api like _.debounce to throttle the updates to the store to reduce the number of state changes in such cases. More on handling this at Perform debounce in React.js.
The issue of memory management is a real issue in real world applications when using Redux. The way to reduce the effect of repeated updates to the state is to
Normalize the shape of state : http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Create memoized selectors using Reselect (https://github.com/reactjs/reselect)
Follow the advice provided in the articles regarding performance in Redux github pages (https://github.com/reactjs/redux/blob/master/docs/faq/Performance.md)
Also remember that although the whole state should be copied to prevent mutating, only the slice of state that changes needs to be updated. For example, if your state holds 10 objects and only one of them changes, you need to update the reference of the new object in the state, but the remaining 9 unchanged objects still point to the old references and the total number of objects in your memory is 11 and not 20 (excluding the encompassing state object.)

React redux favorites action

I am new to react-redux world and having some trouble visualising a piece of complex data flow (I think).
Assume the state contains a collection of tracks and an array of favorite track ids. User could favorite a track from a number of various components e.g. musicplayer, tracklist, charts and all the others would have to rerender.
At the moment, I'm triggering an action to add/remove the track id to/from the favorites array. But I can't quite see how to proceed from there.
My plan is to trigger another action for e.g. the trackItem reducer to listen and carry on. Or could each related component directly subscribe to changes of the favorites collection? Or can I have two reducers listening to the same action? I have now idea how to implement something like that and also I have a gut feeling that I'm on the wrong path.
Feels like I'm struggling to get rid of my backbone-marionette habits. How would you do it?
My other plan is to have an isFavorited boolean within the track item json and use an action/reduces to update/toggle that property. I understand that normalizr will merge instances with the same id, so any component subscribed to its changes will react.
Or could each related component directly subscribe to changes of the
favorites collection
They could. But do these components all share some parent component? If so I would have that parent component subscribe to the state change of the favorites array, and pass that down as props to the components that need it.
I would recommend really reading through the redux docs: https://rackt.github.io/redux/
Especially usage with React: https://rackt.github.io/redux/docs/basics/UsageWithReact.html
Typically you would have a 'smart' component that renders for a route, and that would subscribe to the redux store and pass down the data its nested 'dumb' components need.
So have your smart component(s) subscribe to the state change of the favorites array and pass it down as a prop to the components that need it.
It's all right to listen to one action in more than one reducer, so maybe go down that route?
Do your components share common parent component? If they do, connect it to your redux app state and pass favorite ids array down to each one; then dispatch action addFav or removeFav from any component, react in favorites reducer and see redux passing new props to react components.
I think you should first understand about smart and dumb components in reactjs, here is the link, so you will be having a single smart which connects to you reducer and updates your child(dumb) component.
If you still wants to have two reducers, you can have a action which executes its operation as a result it calls another action. to achieve this you need to have a redux-async-transitions, the example code is given below
function action1() {
return {
type: types.SOMEFUNCTION,
payload: {
data: somedata
},
meta: {
transition: () => ({
func: () => {
return action2;
},
path : '/somePath',
query: {
someKey: 'someQuery'
},
state: {
stateObject: stateData
}
})
}
}
}

Categories

Resources