I'm giving redux-orm a try and from what I can gather, redux-orm creates an object in store, whose key is the value specified via Model.name, for every registered model.
Well,to create my "entities" reducer, I'm using combineReducers, passing in the reducers for all my entities, like this:
import { combineReducers } from 'redux';
import City from './cityReducer';
import Poi from './pointOfInterestReducer';
const entitiesReducer = combineReducers({
City,
Poi
});
export default entitiesReducer;
And finally this is how I create my rootReducer, also using combineReducers:
The problem is that,I think that, by using combineReducers I'm duplicating the keys in my store like it's shown here:
Do you have any idea how I can avoid this, and have all my models be direct descendants of "entities"?
Something like entities>City and not entities>City>City
Edit: cityReducer.js
import orm from './../../orm/orm';
import * as types from '../../actions/actionTypes';
const loadCities = (state, action) => {
// Create a Redux-ORM session from our entities "tables"
const session = orm.session(state);
// Get a reference to the correct version of model classes for this Session
const { City } = session;
const { cities } = action.payload;
// Clear out any existing models from state so that we can avoid
// conflicts from the new data coming in if data is reloaded
City.all().toModelArray().forEach(city => city.delete());
// Immutably update the session state as we insert items
cities.forEach(city => City.parse(city));
return session.state;
};
const updateCity = (state, payload) => {
const { id, newItemAttributes } = payload;
const session = orm.session(state);
const { City } = session;
if (City.hasId(id)) {
const modelInstance = City.withId(id);
modelInstance.update(newItemAttributes);
}
return session.state;
};
const deleteCity = (state, payload) => {
const { id } = payload;
const session = orm.session(state);
const { City } = session;
if (City.hasId(id)) {
const modelInstance = City.withId(id);
// The session will immutably update its state reference
modelInstance.delete();
}
return session.state;
};
const createCity = (state, payload) => {
const { city } = payload;
const session = orm.session(state);
const { City } = session;
City.parse(city);
return session.state;
};
const citiesReducer = (dbState, action) => {
const session = orm.session(dbState);
switch (action.type) {
case types.LOAD_CITIES_SUCCESS: return loadCities(dbState, action);
case types.CREATE_CITY_SUCCESS: return createCity(dbState, action);
case types.UPDATE_CITY_SUCCESS: return updateCity(dbState, action);
case types.DELETE_CITY_SUCCESS: return deleteCity(dbState, action);
default: return session.state;
}
};
export default citiesReducer;
There's two ways you can use Redux-ORM to define its "tables" structure, and write reducer logic to use those tables. I gave examples of both approaches in my blog posts Practical Redux, Part 1: Redux-ORM Basics, Practical Redux, Part 2: Redux-ORM Concepts and Techniques, and Practical Redux, Part 5: Loading and Displaying Data. (Note that those posts cover use of Redux-ORM 0.8, and version 0.9 has some API changes. I listed those changes in Practical Redux, Part 9.)
The first approach is to write your reducer logic attached to your model classes, and use the Redux-ORM ORM instance to generate a reducer that creates the "tables" and manages them for you. Per the example on the front of the Redux-ORM repo:
import {createReducer} from "redux-orm";
import {orm} from "./models";
const rootReducer = combineReducers({
entities: createReducer(orm)
});
The other approach is to write your own function that creates the initial state, and possibly other functions that handle the updating:
import {orm} from "./models";
const initialState = orm.getEmptyState();
export default function entitiesReducer(state = initialState, action) {
// handle other actions here if desired
return state;
}
In other words, don't define the separate per-model-type "tables" yourself. Let Redux-ORM define those, either automatically in its own reducer, or by using it to generate the initial state for your entities reducer.
Related
I'm trying to figure out how to set my initial recoil state while still using nextjs`s ISR feature.
So I made a product.ts file inside of a states directory the file contains the following code
const productsState = atom({
key: 'productState',
default: []
})
I thought about calling my api here and instead of setting the default as an empty array have it filled with data from the api call, but I'm sure I would lose out of ISR and SWR benefits that nextjs brings?
So I thought about setting initial state inside of the getStaticProps method
export const getStaticProps: GetStaticProps = async () => {
const res: Response = await fetch("http://127.0.0.1:8000/api/products");
const {data} = await res.json();
return {
props: {
data
},
revalidate: 10
}
}
But this would only run once on build time so data would be stale, so I made a hook to get my products using SWR
import useSWR from "swr";
import { baseUrl} from '../lib/fetcher'
export const useGetProducts = (path: boolean | string, options: {} = {}) => {
if (!path) {
throw new Error("Path is required")
}
const url = baseUrl + path
const {data: products, error} = useSWR(url, options)
return {products, error}
}
this is then called inside of the page component
const Home: NextPage = ({data}: InferGetStaticPropsType<typeof getStaticProps>) => {
const {products, error} = useGetProducts('/api/products', {
initialData: data,
})
}
Now I'm just wondering if this is a viable way to set initial state in recoil without sacrificing ISR and SWR benefits?
coming from vue/nuxt I would make a global store where I would call my api and set state to the api data, but it seems in react/recoil it's a bit different?
In react-redux, I'm trying to create a generic reducer, meaning a reducer with common logic that writes (with that logic) each time to a different section in the store.
I read Reusing Reducer Logic over and over, I just can't wrap my head around it. Let's say I have this state:
{
a: { b: { c: {...} } } },
d: { c: {...} }
}
a and d are two reducers combined with combineReducers() to create the store. I want section c to be managed with common logic. I wrote the reducer logic for c, I wrapped it to create a higher-order reducer with a name.
How do I create the a reducer with the c reducer with reference to its location (and also d accordingly)? Maybe in other words, how do I create a reducer with a "store address", managing his slice of the state, agnostic to where it is?
I sure hope someone understands me, I'm new to redux and react.
Reducer are now simple function and can be reuse somewhere else
const getData = (state, action) => {
return {...state, data: state.data.concat(action.payload)};
};
const removeLast = (state) => {
return {...state, data: state.data.filter(x=>x !== state.data[state.data.length-1])};
}
Action type and reducer function are now declared in an array
const actions = [
{type: 'GET_DATA', reducer: getData},
{type: 'REMOVE_LAST', reducer: removeLast}
];
Initial state for the reducer
const initialState = {
data: []
}
actionGenerators creates an unique Id using Symbol and assign that Id to actions and reducer function.
const actionGenerators = (actions) => {
return actions.reduce((a,c)=>{
const id = Symbol(c.type);
a.actions = {...a.actions, [c.type]: id};
a.reducer = a.reducer ? a.reducer.concat({id, reducer: c.reducer}) : [{id, reducer: c.reducer}];
return a;
},{});
}
reducerGenerators is a generic reducer creator.
const reducerGenerators = (initialState, reducer) => {
return (state = initialState, action) => {
const found = reducer.find(x=>x.id === action.type);
return found ? found.reducer(state, action) : state;
}
}
Usage
const actionsReducerCreator = actionGenerators(actions);
const store = createStore(reducerGenerators(initialState, actionsReducerCreator.reducer));
const {GET_DATA} = actionsReducerCreator.actions;
store.dispatch({type: GET_DATA});
Checkout my github project where I have a working todo application utilizing this implementation.
Redux-Reducer-Generator
I have the following two #ngrx/store reducers:
import {ActionReducer, Action} from '#ngrx/store';
import {UserAccount} from '../shared/models/useraccount.model';
export const SET_CURRENT_USER_ACCOUNT = 'SET_CURRENT_USER_ACCOUNT';
export const UPDATE_CURRENT_USER_ACCOUNT_FIRST_NAME = 'UPDATE_CURRENT_USER_ACCOUNT_FIRST_NAME';
export const currentUserAccountReducer: ActionReducer<UserAccount> = (state: UserAccount, action: Action) => {
console.log('currentUserAccountReducer:', state, action);
switch (action.type) {
case SET_CURRENT_USER_ACCOUNT: {
return action.payload;
}
case UPDATE_CURRENT_USER_ACCOUNT_FIRST_NAME: {
state.firstName = action.payload;
return state;
}
}
};
export const SET_AUTHENTICATED = 'SET_AUTHENTICATED';
export const SET_UNAUTHENTICATED = 'SET_UNAUTHENTICATED';
export const authenticatedReducer: ActionReducer<boolean> = (state: boolean, action: Action) => {
console.log('authenticatedReducer:', state, action);
switch (action.type) {
case SET_AUTHENTICATED: {
return true;
}
case SET_UNAUTHENTICATED: {
return false;
}
}
};
However, for some reason when I issue a dispatch for the 1st reducer (i.e. currentUserAccountReducer) then it changes the state for the 2rd reducer (i.e. authenticatedReducer)...
Here is the dispatch causing this issue:
this.store.dispatch({type: SET_CURRENT_USER_ACCOUNT, payload: currentUserAccount});
Here is how I initialize the store in the imports section:
StoreModule.provideStore(
{
currentUserAccount: currentUserAccountReducer,
authenticated: authenticatedReducer
})
Can someone please provide advice?
edit: The issue is that authenticated ends up undefined!!
The switch statements in your reducers do not contain default cases. You need to add default cases that return the state, as the reducers will be called for all actions - the store has no way of knowing which reducer should be called for a particular action type, so each dispatched action is passed to every reducer.
How is it possible to save a function as state in redux store?
Example:
I pass a function as parameter to a redux-thunk dispatcher function and i want to save this filter function in my redux store:
export const SET_FILTERED_USERS = 'SET_FILTERED_USERS';
export function setFilteredUsers(filter) {
return (dispatch, getState) => {
const allUsers = getState().users.allUsers;
const filteredUsers = allUsers.filter(filter);
dispatch({
type: SET_FILTERED_USERS,
data: {
filteredUsers,
filter
}
});
const activeUser = getState().users.activeUser;
if (activeUser && !_.isEmpty(filteredUsers) && filteredUsers.indexOf(activeUser._id) === -1) {
dispatch(setActiveUser(filteredUsers[0]));
} else {
dispatch(setActiveUser(allUsers[0]));
}
}
}
In ReduxDevTools i can see, "filter" is not dispatched and saved in store. Is there a way to do this?
Thanks
Update: my shortend reducer:
import {
SET_FILTERED_USERS
} from '../actions/users';
import assign from 'object-assign';
export const initialState = {
filter: null,
filteredUsers: null
};
export default function (state = initialState, action = {}) {
const {data, type} = action;
switch (type) {
case SET_FILTERED_USERS:
return assign({}, state, {
filteredUsers: data.filteredUsers,
filter: data.filter
});
default:
return state;
}
}
As Sebastian Daniel said, don't do that. Per the Redux FAQ, that breaks things like time-travel debugging, and is not how Redux is intended to be used: Can I put functions, promises, or other non-serializable items in my store state?
What you could consider as an alternative is storing some kind of description of the filtering you want. As a sort of relevant example, in my current prototype, I'm creating a lookup table of dialog classes that I might want to show, and when I need to show one, I dispatch an action containing the name of the dialog type. My dialog manager class pulls that "dialogType" field out of state, uses that to look up the correct dialog component class, and renders it.
The other question, really, is why you actually want to store a function in your state in the first place. I see what you're trying to do there, but not actually what you're hoping to accomplish with it.
Just built my first API Middleware and was just wondering where I'm suppose to chain promises for action creators that dispatch multiple actions. Is what I did an anti-pattern:
export const fetchChuck = () => {
return {
[CALL_API]: {
types: [ CHUCK_REQUEST, CHUCK_SUCCESS, CHUCK_FAILURE ],
endpoint: `jokes/random`
}
}
}
export const saveJoke = (joke) => {
return { type: SAVE_JOKE, joke: joke }
}
export const fetchAndSaveJoke = () => {
return dispatch => {
dispatch(fetchChuck()).then((response) => {
dispatch(saveJoke(response.response.value.joke))
})
}
}
Should fetchAndSaveJoke dispatch the section action in my react component or is it okay to have it as its own action creator?
I would say that at this point in the Redux world, it's not super clear what's best practice and what the anti-patterns are. It's a very unopinionated tool. While that's been great for a diverse ecosystem to flourish, it does present challenges for people looking for ways to organize their apps without running into pitfalls or excessive boilerplate. From what I can tell, your approach seems to be roughly in line with the advice from the Redux guide. The one thing that looks funny to me is that it seems like CHUCK_SUCCESS should probably make SAVE_JOKE unnecessary.
I personally find it rather awkward to have action creators dispatch more actions, and so I worked out the approach behind react-redux-controller. It's brand new, so it's certainly not a "best practice", but I'll throw it out there in case you or someone else wants to give it a try. In that workflow, you'd have a controller method that looks something like:
// actions/index.js
export const CHUCK_REQUEST = 'CHUCK_REQUEST';
export const CHUCK_SUCCESS = 'CHUCK_SUCCESS';
export const CHUCK_FAILURE = 'CHUCK_FAILURE';
export const chuckRequest = () => { type: CHUCK_REQUEST };
export const chuckSuccess = (joke) => { type: CHUCK_SUCCESS, joke };
export const chuckFailure = (err) => { type: CHUCK_FAILURE, err };
// controllers/index.js
import fetch from 'isomorphic-fetch'; // or whatever
import * as actions from '../actions';
const controllerGenerators = {
// ... other controller methods
*fetchAndSaveJoke() {
const { dispatch } = yield getProps;
// Trigger a reducer to set a loading state in your store, which the UI can key off of
dispatch(actions.chuckRequest());
try {
const response = yield fetch('jokes/random');
dispatch(actions.chuckSuccess(response.response.value.joke));
} catch(err) {
dispatch(actions.chuckFailure(err));
}
},
};