Reputation: 920
Imagine that you develop some react-redux application (with global immuatable tree-state). And some data have some rules-relations in different tree-branches, like SQL relations between tables.
I.e. if you are working on some company's todos list, each todo has relation(many-to-one) with concrete user. And if you add some new user, you should add empty todo list (to other branch in the state). Or delete user means that you should re-assign user's todos to some (default admin) user.
You can hardcode this relation directly to source code. And it is good and works OK. But imagine that you have got million small relations for data like this. It will be good that some small "automatic" operations/checks (for support/guard relations) performs automatically according to rules.
May be existed some common approach/library/experience to do it via some set of rules: like triggers in SQL:
on add new user => add new empty todos
on user delete => reassign todos to default user
Upvotes: 0
Views: 804
Reputation: 12790
There are two solutions here. I don't think that you should aim to have this kind of functionality in a redux application, so my first example is not quite what you're looking for but I think is more conical. The second example adopts a DB/orm pattern, which may work fine, but is not conical, and requires
These could be trivially added safely with vanilla redux and redux-thunk
. Redux thunk basically allows you to dispatch a single action that its self dispatches multiple other actions--so when you trigger CREATE_USER
, just do something along the lines of triggering CREATE_EMPTY_TODO
, CREATE_USER
, and ASSIGN_TODO
in the createUser
action. For deleting users, REASSIGN_USER_TODOS
and then DELETE_USER
.
For the examples you provide, here are examples:
function createTodoList(todos = []) {
return dispatch => {
return API.createTodoList(todos)
.then(res => { // res = { id: 15543, todos: [] }
dispatch({ type: 'CREATE_TODO_LIST_SUCCESS', res });
return res;
});
}
}
function createUser (userObj) {
return dispatch => {
dispatch(createTodoList())
.then(todoListObj => {
API.createUser(Object.assign(userObj, { todoLists: [ todoListObj.id ] }))
.then(res => { // res = { id: 234234, name: userObj.name, todoLists: [ 15534 ]}
dispatch({ type: 'CREATE_USER_SUCCESS', payload: res });
return res;
})
})
.catch(err => console.warn('Could not create user because there was an error creating todo list'));
}
}
Deleteing, sans async stuff.
function deleteUser (userID) {
return (dispatch, getState) => {
dispatch({
type: 'REASSIGN_USER_TODOS',
payload: {
fromUser: userID,
toUser: getState().application.defaultReassignUser
});
dispatch({
type: 'DELETE_USER',
payload: { userID }
});
}
}
The problem with this method, as mentioned in the comments, is that a new developer might come onto the project without knowing what actions already exist, and then create their own version of createUser
which doesn't know to create todos. While you can never completely take away their ability to write bad code, you can try to be more defensive by making your actions more structured. For example, if your actions look like this:
const createUserAction = {
type: 'CREATE',
domain: 'USERS',
payload: userProperies
}
you can have a reducer structured like this
function createUserTrigger (state, userProperies) {
return {
...state,
todoLists: {
...state.todoLists,
[userProperies.id]: []
}
}
}
const triggers = {
[CREATE]: {
[USERS]: createUserTrigger
}
}
function rootReducer (state = initialState, action) {
const { type, domain, payload } = action;
let result = state;
switch (type) {
case CREATE:
result = {
...state,
[domain]: {
...state[domain],
[payload.id]: payload
}
};
break;
case DELETE:
delete state[domain][payload.id];
result = { ...state };
break;
case UPDATE:
result = {
...state,
[domain]: {
...state[domain],
[payload.id]: _.merge(state[domain][payload.id], payload)
}
}
break;
default:
console.warn('invalid action type');
return state;
}
return triggers[type][domain] ? triggers[type][domain](result, payload) : result;
}
In this case, you're basically forcing all developers to use a very limited possible set of action types. Its very rigid and I don't really recommend it, but I think it does what you're asking.
Upvotes: 1