Let's say you have two objects to store in Redux, A and B, which are denormalized per the redux docs, like this:
state = {
a: { byId: { id: { obj } }, allIds: [] },
b: { byId: { id: { obj } }, allIds: [] }
};
You have an action, CREATE_A which adds a new A to the store. But, for every A created, it inherently needs a B as well. Assume that A and B are in separate reducer slices (i.e. combineReducers) and can't be merged into one.
It's easy to make B's reducer react to the CREATE_A event and create a new B, but what if the B object needs the A's ID that was just created?
Even if there's a join table involed to join B to A, there's still the problem of "knowing the A that was created first". The solution I came up with, was to store the last created A like so:
a: { lastCreated: {}, byId: etc, allIds: etc }
And then passing the whole state tree to either B's reducer or the join table reducer, so it can access state.a.lastCreated. It doesn't feel right, though, to just have a key so that a later reducer knows what happened (the idea of reducers requiring a certain ordering also seems wrong)
I thought you could also dispatch a CREATE_B with the new A's id, but that would have to be done in an async action (since you can't dispatch from a reducer), which also doesn't feel right.
In a procedural world, this would be trivial:
a = createA();
createB(a);
But even with two dispatches, I'm not sure how it would work:
dispatch( createA() )
dispatch( createB(???) )
What would be the best way to handle this case of "A-inherently-means-B-as-well"?
Edit: Let me try to use some more concrete examples.
Say you have squares and points. Creating a square inherently means creating 4 points. The points are joined with the square, because they form the square, but they also don't belong to the square, because they can be independent objects of their own right:
So, ADD_SQUARE needs to both add a square, and add 4 points, and then join the two together, and I'm not sure how to do this without writing a reducer directly in the parent "state" like this (I don't want to have to do this, it gets really messy really fast, imagine having to repeat for 3-8 sided polygons):
function reducer(state, action) {
switch(state) {
case ADD_SQUARE:
const points = create4Points();
const square = createSquare();
return {
...state,
squares: {
...state.squares,
byId: {
...state.squares.byId
[square.id]: square
}
},
points: {
...state.points,
byId: {
...state.points.byId,
[point[0].id]: point[0],
[point[1].id]: point[1],
[point[2].id]: point[2],
[point[3].id]: point[3]
}
},
pointsSquares: {
...state.pointsSquares,
byId: {
[square.id]: {
square: square.id,
points: [point1.id, point2.id, point3.id, point4.id]
}
}
}
};
}
}
You could define the same action within both A and B reducers.. like so --
function reducerA(state, action) {
switch(action.type) {
case A_CREATE:
do A stuff
}
}
function reducerB(state, action) {
switch(action.type) {
case A_CREATE:
do B stuff
}
}
Then when you dispatch the action it will affect both reducers.
function createA({id, stuff, etc}) {
return {
type: "A_CREATE",
payload: {id, stuff, etc}
}
}
Then you can tie the id to whatever you need to create... thus "joining" them together.
EDIT:
You could use redux thunk or some other middleware like redux saga in conjunction with promises to dispatch multiple actions.
`
function handleACreation(payload) {
dispatch(
createA(payload)
.then(result => dispatch(updateB(result)))
)
}
`
obviously that exact code wouldn't work - but the general idea remains :] hope this helps!
In my case, I solved this by creating A's id in the action creator instead of in A's reducer. For example,
function createSquare () {
return {
type: CREATE_SQUARE,
id: newUuid()
};
}
function squareReducer ( state, action ) {
switch ( action.type ) {
case CREATE_SQUARE:
return { id: action.id };
}
}
function pointReducer ( state, action ) {
switch ( action.type ) {
case CREATE_SQUARE:
return [
{ id: newUuid(), square: action.id }
{ id: newUuid(), square: action.id } //etc
];
}
}
I'm still open to other solutions though.
Related
I'm learning redux for my first react-redux application. How do I manage to verify payload value before changing my state ? For example the code below:
todoExample = {name: 'learn redux', author: 'myself'}
wrongTodoExample = {name: 'learn redux'}
dispatch(addTodos({todo: todoExample}))
dispatch(addTodos({todo: wrongTodoExample }))
With the above code, I add 2 todo items to my state but they don't have the same keys.
Is there a way to check the payload value in order to authorize the first addTodos but not the second one in my reducer?
I've searched on the internet but I couldn't find an answer. I'm sorry if my question is redundant.
You can use redux middleware to verify things, that is absolutely one of the intended use cases for middleware. Any middleware can inspect and modify any action going through the pipeline before it reaches the reducers, and even prevent an action from continuing on.
const verifyPayload = store => next => action => {
if (isVerifyPayload(action.payload)) {
return next(action);
} else {
return store.dispatch({ type: 'NOT_AUTHORIZED' })
}
}
const store = createStore(
initialState,
applyMiddleware(verifyPayload)
)
Not so clear about your description about same key, you mean name or author, or other specific keys like code\id.
You can try to validate your todos before dispatch or within the addTodos
function addTodos(payload) {
if (!payload.todo.code) return;
// simply return,
// otherwise throw an error to indicate that your todos miss a specific key
}
You can use a ternary operator in your reducer along with some util function to validate your todo. If the todo is valid, then transform your state to include the new todo, if not return the same state (effectively doing nothing).
const isValidTodo = (todo) => {
//Implement your validations. E.g: A valid todo will have a name and an author
return todo.name && todo.author;
}
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return isValidTodo(action.payload) ?
[
...state,
{
name: action.payload.name,
author: action.payload.text,
completed: false
}
]
: state
default:
return state
}
}
I've found a solution that suited well my needs and it's TypeScript. Now I have Payload Type wich allow me to define keys that I need in my action.payload without any validation function.
Thanks all for your asnwers.
I am having a problem in my Vuex.Store:
I would like to get an object (getter.getRecipe) using two state entries as search criteria (state.array & state.selected) with a getter. Then I would like to store that result in my state (state.recipe) in order to be able to update it within components (i.e., changing one key of the recipe object based on client action).
However, I have no idea how I can store the result of getters in my state ("this.getters.getRecipe" is not working...).
Very helpful for hints. Thanks a lot.
//store.js (vuex store)
export const store = new Vuex.Store({
state: {
array: [recipe1, recipe2],
selected: 0,
recipe: this.getters.getRecipe()
},
getters: {
getRecipe: (state) => {
return state.array[state.selected]
}
}
})
Instead, you should use Method style getters with arguments:
You can also pass arguments to getters by returning a function. This is particularly useful when you want to query an array in the store:
A basic example:
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
Then, to use the example:
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
or, from inside a component:
this.$store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
This way you ensure your state remains a pure data structure that follows the one way data flow set forth by the flux pattern.
Suppose I have a redux store with this state structure:
{
items: {
"id1" : {
foo: "foo1",
bar: "bar1"
},
"id2": {
foo: "foo2",
bar: "bar2"
}
}
}
This store evolves by receiving full new values of items:
const reduceItems = function(items = {}, action) {
if (action.type === 'RECEIVE_ITEM') {
return {
...items,
[action.payload.id]: action.payload,
};
}
return items;
};
I want to display a Root view that renders a list of SubItem views, that only extract a part of the state.
For example the SubItem view only cares about the foos, and should get it:
function SubItem({ id, foo }) {
return <div key={id}>{foo}</div>
}
Since I only care about "subpart" of the states, that's what I want to pass to a "dumb" Root view:
const Root = function({ subitems }) {
// subitems[0] => { id: 'id1', foo: "foo1" }
// subitems[1] => { id; 'id2', foo : "foo2" }
const children = subitems.map(SubItem);
return <div>{children}</div>;
};
I can easily connect this component to subscribe to changes in the state:
function mapStatesToProps(state) {
return {
subitems: xxxSelectSubItems(state)
}
}
return connect(mapStatesToProps)(Root)
My fundamental problem is what happens when the part of the state that I don't care about (bar) changes.
Or even, when I receive a new value of an item, where neither foo nor bar has changed:
setInterval(() => {
store.dispatch({
type: 'RECEIVE_ITEM',
payload: {
id: 'id1',
foo: 'foo1',
bar: 'bar1',
},
});
}, 1000);
If I use the "naive" selector implementation:
// naive version
function toSubItem(id, item) {
const foo = item.foo;
return { id, foo };
}
function dumbSelectSubItems(state) {
const ids = Object.keys(state.items);
return ids.map(id => {
const item = state.items[id];
return toSubItem(id, item);
});
}
Then the list is a completely new object at every called, and my component gets rendered everytime, for nothing.
Of course, if I use a 'constant' selector, that always return the same list, since the connected component is pure, it is re-renderered (but that's just to illustrate connected components are pure):
// fully pure implementation
const SUBITEMS = [
{
id: 'id0',
foo: 'foo0',
},
];
function constSelectSubItems(state) {
return SUBITEMS;
}
Now this gets a bit tricky if I use an "almostConst" version where the List changes, but contains the same element.
const SUBITEM = {
id: 'id0',
foo: 'foo0',
};
function almostConstSelectSubItems(state) {
return [SUBITEM];
}
Now, predictably, since the list is different, even though the item inside is the same, the component gets rerendered every second.
This is where I though 'reselect' could help, but I'm wondering if I am not missing the point entirely. I can get reselect to behave using this:
const reselectSelectIds = (state, props) => Object.keys(state.items);
const reselectSelectItems = (state, props) => state.items;
const reselectSelectSubItems = createSelector([reSelectIds, reSelectItems], (ids, items) => {
return ids.map(id => toSubItem(id, items));
});
But then it behaves exactly like the naive version.
So:
is it pointless to try to memoize an array ?
can reselect handle this ?
should I change the organisation of the state ?
should I just implement shouldComponentUpdate on the Root, using a "deepEqual" test ?
should I give up on Root being a connected component, and make each LeafItems be connected components themselves ?
could immutable.js help ?
is it actually not an issue, because React is smart and will not repaint anything once the virtual-dom is computed ?
It's possible what I'm trying to do his meaningless, and hides an issue in my redux store, so feel free to state obvious errors.
You're definitely right about the new array references causing re-renders, and sort of on the right track with your selectors, but you do need to change your approach some.
Rather than having a selector that immediately returns Object.keys(state.item), you need to deal with the object itself:
const selectItems = state => state.items;
const selectSubItems = createSelector(
selectItems,
(items) => {
const ids = Object.keys(items);
return ids.map(id => toSubItem(id, items));
}
);
That way, the array will only get recalculated when the state.items object is replaced.
Beyond that, yes, you may also want to look at connecting your individual list item components so that each one looks up its own data by ID. See my blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance for examples. I also have a bunch of related articles in the Redux Techniques#Selectors and Normalization and Performance#Redux Performance sections of my React/Redux links list.
I have an app which has a few users. I would now like to be able to create a new user. So I have created this actionCreator:
export const createUser = (first, last) => {
console.log("You are about to create user: XX ");
return {
type: 'USER_CREATE',
first: first,
last: last,
payload: null
}
};
I am dealing only with first & last names for now. The actionCreator gets its parameters from the container. There is a button which calls the actionCreator like so:
<button onClick={() =>this.props.createUser(this.state.inputTextFirstName, this.state.inputTextLastName)}>Submit</button>
My UserReducer looks like this:
/*
* The users reducer will always return an array of users no matter what
* You need to return something, so if there are no users then just return an empty array
* */
export default function (state = null, action) {
if(state==null)
{
state = [
{
id: 1,
first: "Bucky",
last: "Roberts",
age: 71,
description: "Bucky is a React developer and YouTuber",
thumbnail: "http://i.imgur.com/7yUvePI.jpg"
},
{
id: 2,
first: "Joby",
last: "Wasilenko",
age: 27,
description: "Joby loves the Packers, cheese, and turtles.",
thumbnail: "http://i.imgur.com/52xRlm8.png"
},
{
id: 3,
first: "Madison",
last: "Williams",
age: 24,
description: "Madi likes her dog but it is really annoying.",
thumbnail: "http://i.imgur.com/4EMtxHB.png"
}
]
}
switch (action.type) {
case 'USER_DELETED':
return state.filter(user => user.id !== action.userIdToDelete);
case 'USER_CREATE':
console.log("Action first:" + action.first);
console.log("Action last:" + action.last);
Object.assign({}, state, {
id: 4,
first: action.first,
last: action.last,
age: 24,
description: "Some new Text",
thumbnail: "http://i.imgur.com/4EMtxHB.png"
});
return state;
}
return state;
}
Now I have a few questions.
1) Is this the proper way to do this, or am I writing bad code somewhere? Keep in mind that I am trying to use Redux here, I am not entirely sure though whether I am not sometimes falling back into React without Redux
2) Am I doing the state thing correctly? I initially used a tutorial and am now building upon that, but I am not sure why state seems to be an array:
state = [ <--- Does this mean that my state is an array?
{
id: 1,
// and so on ...
I am very confused by this, since in other Tutorials state is just an object containing other smaller objects and its all done with parentheses { }
3) What would be the best way to create a new user. My Object.assign does not work, it does not update anything, and I am not sure where the mistake lies.
4) And, relatedly, how could I update one individual user or a property of one individual user?
As Flyer53 states you need to set the state to the return value of Object.assign() as this is designed to not mutate state it will not change the value of the state you're passing in.
The code's fine; I'd tend to use just one property on the action in addition to its type, so have a property of (say) user that is an object containing all the user data (first name, last name etc).
I believe it's quite idiomatic to define a default state outside of the reducer and then set this as the default value for the state parameter in the reducer function:
export default function (state = initialState, action) {
For a brilliant introduction by its creator, see https://egghead.io/courses/getting-started-with-redux
State can be any shape you like. As an application grows in complexity it will usually be represented as an object composed of different sections of data. So, for example, in your case in could be comprised of an array of users and, say, an 'order by' that could apply to some UI state):
{ users: [], orderBy: 'lastName' }
If you carry on using an array of users as the state then you can use the ES6 spread operator to append the new user, for example:
newState = [ ...state, action.user ];
whereas if you move to using an object for state, the following would similarly append a user:
newState = Object.assign({}, state, { users: [ ...state.users, action.user ] };
Finally, to update a single user you could just use map against the array of users as follows (this is obviously hardcoded, but you could match, say, on id and update the appropriate properties).
let modifiedUsers = state.users.map((user) => {
if (user.id === 3) {
user.name = user.name + '*';
}
return user;
});
let newState = Object.assign({}, state, { users: modifiedUsers });
There's maybe an easier way to log the state(users) in an object (not in an array as in your example code above) that works without Object.assign() which is supposed to work with objects, not arrays:
var state = {
user1: {
id: 1,
first: "Bucky",
last: "Roberts",
age: 71,
description: "Bucky is a React developer and YouTuber",
thumbnail: "http://i.imgur.com/7yUvePI.jpg"
}
};
state['user' + 2] = {
id: 2,
first: "Joby",
last: "Wasilenko",
age: 27,
description: "Joby loves the Packers, cheese, and turtles.",
thumbnail: "http://i.imgur.com/52xRlm8.png"
};
console.log(state);
console.log(state.user2);
Just an idea ...
As the doc says:
Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
If I follow the principle, there are some questions about the code orgnization (my app is a file manager).
For example,
default reducer like this:
export default function (state = initialState, action) {
const { path } = action
if (typeof path === 'undefined') {
return state
}
const ret = {
...state,
[path]: parentNode(state[path], action)
};
switch (action.type) {
case OPEN_NODE:
case GO_PATH:
ret['currentPath'] = path
break
default:
break
}
return ret
}
data struct in state[path] likes:
{
'open': false,
'path': '/tmp/some_folder',
'childNodes' : [ {'path':'/some/path', 'mode': '0755', 'isfolder': true}, ....],
'updateTime': Date.now()
}
Now I need several actions such as ADD_CHILD, DELETE_CHILD , RENAME_CHILD, MOVE_CHILD, there are two sulotions(by change state in actions or reducers):
1. All functional code in actions:
actions:
export function updateChildNodes(path, nodes) {
return {
type: UPDATE_CHILD_NODES,
path: path,
loading: false,
loaded: true,
childNodes: nodes,
};
}
export function addChild(path, node) {
return (dispatch, getState) => {
const state = getState().tree[path]
var childNodes = state.childNodes ? state.childNodes :[]
childNodes.push(node)
return dispatch(updateChildNodes(path, childNodes))
}
}
export function deleteChild(parent_path, child_node) {
return (dispatch, getState) => {
const state = getState().tree[parent_path]
var childNodes = state && state.childNodes ? state.childNodes : []
for (var i=0; i <=childNodes.length; i++){
if (childNodes[i].path == child_node.path){
childNodes.splice(i, 1)
return dispatch(updateChildNodes(parent_path, childNodes))
}
}
}
}
export function deleteNode(node) {
return (dispatch, getState) => {
// ajax call
return api.deleteChild(node.path, () => {
dispatch(deleteChild(node.parent, node))
})
}
}
.....
parentNode reducer:
function parentNode(state, action) {
switch (action.type) {
case UPDATE_CHILD_NODES:
return {
...state,
childNodes: action.childNodes
}
default:
return state;
}
}
All variable pass in parentNode from actions, parentNode just assign change to state doesn't do anything else.
All logic of remove node and add node is done by actions, only UPDATE_CHILD_NODES in parentNode.
2. Action just send data to reducer, let reducer to process
actions:
export function updateChildNodes(path, nodes) {
return {
type: UPDATE_CHILD_NODES,
path: path,
loading: false,
loaded: true,
childNodes: nodes,
};
}
export function addChild(path, node) {
return {
type: ADD_CHILD,
path: path,
node: node,
};
}
export function deleteChild(path, node) {
return {
type: DELETE_CHILD,
path: path,
node: node,
};
}
export function deleteNode(node) {
return (dispatch, getState) => {
// ajax call
return api.deleteChild(node.path, () => {
dispatch(deleteChild(node.parent, node))
})
}
}
.....
parentNode reducer:
function parentNode(state, action) {
switch (action.type) {
case DELETE_CHILD:
let childNodes = state.childNodes.slice() // have to clone obj
for (var i=0; i <=childNodes.length; i++){
if (childNodes[i].path == action.node.path){
childNodes.splice(i, 1)
}
}
return {
...state,
childNodes: childNodes
};
case ADD_CHILD:
let childNodes = state.childNodes.slice() // have to clone obj
childNodes.push(node)
return {
...state,
childNodes: childNodes
};
case UPDATE_CHILD_NODES:
return {
...state,
childNodes: action.childNodes
}
default:
return state;
}
}
In my option, the solution 2 is more readable and pretty.
But is it good to change the state by mutate an cloned obj? And when I need set updateTime by Date.now(), I have to generate it from actions and pass to reducer,so that state variables are generated in different place(But I'd like put them together...)
Any opinion for this?
From this redux discussion here:
It is best practice to place most of the logic in the action creators and leave the reducers as simple as possible (closer to your option 1)
for the following reasons:
Business logic belongs in action-creators. Reducers should be stupid and simple. In many individual cases it does not matter- but consistency is good and so it's best to consistently do this. There are a couple of reasons why:
Action-creators can be asynchronous through the use of middleware like redux-thunk. Since your application will often require asynchronous updates to your store- some "business logic" will end up in your actions.
Action-creators (more accurately the thunks they return) can use shared selectors because they have access to the complete state. Reducers cannot because they only have access to their node.
Using redux-thunk, a single action-creator can dispatch multiple actions- which makes complicated state updates simpler and encourages better code reuse.
For small apps I usually put my logic in action creators. For more complex situations you may need to consider other options. Here is a summary on pros and cons of different approaches: https://medium.com/#jeffbski/where-do-i-put-my-business-logic-in-a-react-redux-application-9253ef91ce1#.k8zh31ng5
Also, have a look at Redux middleware.
The middleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
This is an answer provided by Dan Abramov (author of Redux): Why do we need middleware for async flow in Redux?
And here are the official Redux docs: http://redux.js.org/docs/advanced/Middleware.html