Redux: Accessing current state - best practice? - javascript

Here is something I don't understand about Redux. I have an app which goes through items. You can go to the previous item and to the next item. As I understand it, you are not supposed to access the current state in your actions.
As regards my app, I have an array in my redux state which holds all the ids of my items: ["3123123123","1231414151","15315415", etc.] and I have a piece of state which holds the currently selected item (or better, it holds the id of that item). Now when the user clicks nextItem, I need to get the next item. My (unfinished) action look like this:
export function nextItem(currentId) {
//my idea:
//take the currentId, look where in the array it is, and get the position
//increment that position
//get the id of the next item in the array (at position: position+1)
//update state
return {
type: SET_CURRENT_ITEM,
payload: item
}
}
Similar things would apply to the previous Item action creator. However, I am at loss how to implement this action creator, without accessing my current state? Where and how would that ideally happen?

I would suggest that you dispatch an action like:
{
type: INCREMENT_CURRENT_ITEM
}
You can dispatch this from within any connected component directly:
dispatch({ type: INCREMENT_CURRENT_ITEM })
Or if you prefer to use an action creator, that's fine too:
dispatch(incrementItem()) // incrementItem() returns the action above
In your reducer, you have access to the current state, which is where you can increment the item index, without having to search for the current value in an array.

I probably would add a component responsible of items id incrementing through the app
import React from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { nextItem } from "../redux/actions";
const ItemNav = ({ nextItem, items, item }) => {
function setNextItem() {
let currentItemID = items.indexOf(item) + 1;
if (currentItemID >= items.length - 1) currentItemID = 0;
nextItem(items[currentItemID]);
}
return (
<ul>
<li>previous item</li>
<li onClick={setNextItem}>next item</li>
</ul>
);
};
const mapStateToProps = state => ({
items: state.items,
item: state.item
});
const mapDispatchToProps = dispatch => bindActionCreators({ nextItem },dispatch);
export default connect(
mapStateToProps,
mapDispatchToProps
)(ItemNav);

The reducer is a pure function. The producer must receive
arguments of the same type, the producer must calculate the new
version of the state and return it. No surprises. No side effects. No
calls to the third-party API. No changes (mutations). Only the
calculation of the new version of the state.
Pure function - At the fundamental level, any function that does not
change the input does not depend on the external state (database, DOM
or global variable) and returns the same result for the same input
data as a pure function.
In addition, if the values are in a different reducer, what to do?
Action creators - also pure functions, and for calculation we must
receive data from the store
Component - use business logic in the component bad practice
Remain middleware and to not produce many middleware it is better to
use redux-thunk
In addition, a link to a similar question:
Redux: Reducer needs state of other Reducer?
And the link to the first found project realizing this situation:
https://github.com/rwieruch/favesound-redux/blob/master/src/actions/player/index.js

Related

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.

Does NGRX selector return store refernce

I am writing an angular application. in which I am managing state using redux.
I have a store like below
export interface State {
data {
items: any[];
}
}
I have a return selector for getting Items like below
export const getItems = createSelector(getItemState, fromItem.getItems);
fromItem.getItems is like below =>
export const getItems = (state: State): any[] => state.items;
and in my component I have subscribe for selector of items like below
this.store.select(getItems).subscribe((items) => {
this.localItems = items;
}
everything is working fine but in getItems subscription I am getting the reference to the items which are stored in the store. And, if I update any local item it also gets reflected into the store.
I was expecting the subscription of selector (getItems) to return a cloned copy of items from the store but it is returning the reference.
Am I doing anything wrong or is there any way to get a cloned copy of items from the store?
What you're describing is the correct behavior.
State mutations should only happen in reducers in a pure way.
Doing it this way makes using a store performant, we can simply check if the reference is the same - this is very cheap.

Check if the key in redux store is connected to any component in DOM right now

I have a redux store with a reducer data and using redux observable to fill this data in store. I am trying to load some data in store when component is mounted and remove that data when component is unmounted. But before removing I want to check that this data is not used by any other mounted component. What I have till now is this
Store:
{
data: {}
}
My component needs itemList, I dispatch an action LOAD_ITEMS, one epic loads itemList and puts it in store
{
data: { items: {someItems}}
}
This component has following connection to store -
componentDidMount () {
if (!data.items) {
dipatch(LOAD_ITEMS)
}
}
componentWillUnmount() {
// Before doing this I want to make sure that these items are not
// used by any other mounted componeted.
dispatch(REMOVE_ITEMS_FROM_STORE);
}
mapStateToProps = () => ({
data: store.data
})
One way I tried was to save count of all mounted components which uses items from store in store with a key activeComponents. Like following
{
data: {
items: {someItems}
activeComponents: 2 // count of mounted components which are
//using items from store
}
}
So if there are two components which needs items from store the count of activeComponents will be 2, so items will be removed from store only if this count is one, on other removal attempts just activeComponents count is reduced by 1
But this is very complicated approach, I suppose there must be some better and proper way do this. Any thoughts?
I think your idea with storing the number of activeComponents is kinda the right approach, but you should use a boolean value, that will make it much more simple to handle.
So basically instead of storing activeComponents: 3, you can just do isUsed: true / false

Infinite scroll using redux and react

I am trying to implemet infinite scroll in a react based application that uses Redux for state management I am trying to dispatch an action on page scroll. But not able to achieve the same. My code is here
// The Reducer Function
const pinterestReducer = function (state=[], action) {
if (action.type === 'SET_CONTENT') {
state = action.payload;
}
return state;
}
const appReducer = combineReducers({
pinterest: pinterestReducer
})
// Create a store by passing in the pinterestReducer
var store = createStore(appReducer);
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
// you're at the bottom of the page, show all data
store.dispatch({
type: 'SET_CONTENT',
payload: travelJSON
});
}
};
travelJSON is an array of objects. Initialy I dispatch an action that assigns the first 12 objects of travelJSON to state. When user scrolls to bottom of page I have to assign full JSON Below is my component making use of this state:
// Dispatch our first action to change the state
store.dispatch({
type: 'SET_CONTENT',
payload: travelJSON.slice(0,12)
});
render(
<Provider store={store}><Pinterest /></Provider>,
document.getElementById('page-container'));
I would question why you are trying to do this in the model/business logic layer. Typically virtualizing a scroll is a view state concern. You give the view component the entire list of model objects, but it only renders the DOM elements for the objects that would be shown in the viewport of the view component.
One way to do this is to create a component that allocates a div which is tall enough to display every single one of your model objects. The render method renders only those items that would be displayed in the viewport.
There are a number of components that do this for you already. See for example: https://github.com/developerdizzle/react-virtual-list. This is implemented as an HOC, so you could implement it with your current view logic with minimal changes.
It wraps your component. You send your entire data array into the wrapper, and it figures out which elements will be displayed in the viewport and passes those to the wrapped component, it also passes in the 'paddingTop' style required to shift those elements into the viewport considering the current scroll position.
The following code removes the first 12 items from travelJSON
travelJSON.splice(0,12)
on scroll you replace the first 12 items with the remaining items, while you should really add them instead of replace them.
state = action.payload
To add items to your state use something like this:
return [...state, ...action.payload];
(This uses the new spread operator: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Spread_operator )
Have you looked at react waypoint? It seems like a nice component to accomplish exactly what you are trying to do?

React/Redux, implementing multiple actions with Redux Thunk

I am learning a react/redux and have an application with two main pieces of state:
An array of items
An object that contains user-specified filters for those items
I have three functions/actions, createFilter, updateFilter, and deleteFilter that modify the state of #2. I have an action filterItems that modifies #1 based on the state of #2. So whenever #2 changes, this action needs to be dispatched.
This is the component I am working with:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { createFilter } from '../actions/actions'
import { updateFilter } from '../actions/actions'
import { deleteFilter } from '../actions/actions'
import { filterItems } from '../actions/actions'
class ItemList extends Component {
createFilter(input) {
this.props.createFilter(input)
this.props.filterItems()
}
updateFilter(input) {
this.props.updateFilter(input)
this.props.filterItems()
}
deleteFilter() {
this.props.deleteFilter()
this.props.filterItems()
}
...
// Render method
...
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ createFilter, updateFilter, deleteFilter, filterItems }, dispatch)
}
function mapStateToProps({ itemList }) {
return { itemList }
}
export default connect(mapStateToProps, mapDispatchToProps)(ItemList)
What I have found is that when one of the filter methods are sent, the store (state #2) is not yet updated by the time filterItems() is called.
So I need to asynchronously execute the filter functions, and once the store is updated call filterItems.
I am struggling on how to do this with react-thunk. If the first function was an ajax promise I would use .then():
export function updateFilterAndEvaluate(input) {
return (dispatch, getState) => {
updateFilter(input).then(dispatch(filterItems(getState().filters)))
}
}
But these are just functions, and don't have a .then() method. I am trying to figure out what my best course of action is for this implementation. Can I wrap Redux actions in a promise? Am I misusing Thunk? Or should I attempt a different pattern entirely?
I have an action filterItems that modifies #1 based on the state of #2.
This is, generally speaking, an anti-pattern. Since the result array can be computed from the source array and the currently active filters, you shouldn’t be keeping it in the state.
Redux actions should generally look like “events” (e.g. what happened). “Filter was created” and “filter was updated” are good actions. “Filter them now!” looks more like a command, this is usually a sign that it shouldn’t have been an action in the first place, and should be something the components do as they select the data to render.
Instead, do the filtering as part of your mapStateToProps() function when you prepare data for the components. If it gets expensive, look into using Reselect to compute derived data efficiently.
As for your specific question,
What I have found is that when one of the filter methods are sent, the store (state #2) is not yet updated by the time filterItems() is called.
This is incorrect and indicates some other problem in your code. (It’s hard to tell where because the example is incomplete). In Redux, dispatch() is synchronous (unless you have some middleware that delays or batches it which usually isn’t the case), so you don’t need to “wait” for it to finish if it just operates on the local data.
However, in any case, filterItems() is not a very good fit for an action, and I suggest you to look into filtering in mapStateToProps() as I wrote above.

Categories

Resources