(This is maybe not a redux question per sé)
My current strategy/thoughtprocess is this: every container component that needs data, dispatches an action that fetches that data in componentDidMount (which then triggers an api request, that dispatches an action that updates the store)
The problem with this: some components are fetching the exact same data. This also means that when I load my app a (sometimes "large") number of unneccessary requests and rerenders happen.
My question: what's a better way of handling this?
Personally I see two ways:
somehow cache the data returned from the server, and if a new request is triggered that has cached data (and happens within a certain timeout), return that cached data instead of making a new request.
fetch all my app data somewhere else in one spot. (the drawback I see is that my container components aren't "self-sufficient" then)
My guess is that there is some better way that I can't seem to see :)
To improve the initial render, I would:
fetch the data on the server and populate redux store
do server rendering and pass the redux store state to client
add a check to your componentDidMount to see if the data is available
Something like
componentDidMount() {
if (this.props.shouldFetchData) {
this.props.fetchData()
}
}
We have a system like this in place and it works fine. The only big downside is that you have to do the api call in 2 different places ( client and server ), but I've yet to see a solution with server side rendering that doesn't have that duplication.
I like to use redux-thunk for this type of thing as you can get access to the state and make decisions accordingly. e.g.
const fetchData = (id) => {
return (dispatch, getState) => {
if (getState().path.to.data.isLoading)
return
dispatch({type: 'LOADING_DATA'})
fetch(`${url}/${id}`).then(data => dispatch({type: 'SET_DATA', data}))
}
}
Then in your reducer, you set isLoading true when the 'LOADING_DATA' action is dispatched and then toggle it back to false when the 'SET_DATA' is received. e.g.
const initialState = {isLoading: false}
const reducer = (state = initialState, action) => {
switch(action.type) {
case 'LOADING_DATA':
return { ...state, isLoading: true }
case 'SET_DATA':
return { ...state, ...data, isLoading: false }
}
}
All components looking for the same data in the store will be rerendered when the store is updated.
You can also extend this pattern as much as you like to suit your need, for example, store a timestamp instead of a boolean and don't follow through on the request for a certain timeout.
Related
Now if i want to change value in store i should do following steps:
Go to constants/actionTypes file, create a line with action type
Go to actions and create action function
In each component where i use it i should create a function for mapDispatchToProps
In reducer i should write a logic of changing
Whats the point of such complexity?
Will it be wrong if i will do just one file with actions which will change the state? For example:
// actions.js
export const setCategories = (payload, setState, currentState) => setState({ categories: payload })
export const addCategory = (payload, setState, currentState) => setState({ categories: [...currentState.category, payload] })
To make it work i can create just couple of universal functions for all projects
1) getActions, which authomaticly collects all exports from actions.js and provide them to mapDispatchToProps, so in all components we could just write
const mapDispatchToProps = getActions
code of it can be something like
// actionsDispatcher.js
import * as actions from 'actions'
const getActions = (dispatch, ownProps) => {
Object.keys(actions).forEach(actionName => {
const fn = actions[actionName]
actions[actionName] = payload => dispatch({ action: fn, payload, type: _.toSnakeCase(actionName) })
}
return actions
}
which means we pass to dispatch the function of action from actions.js
2) setState which will work similary to react function, but for redux state
then in reducer function we just right
function rootReducer(state = initialState, action) {
if (action.action) {
action.action(action.payload, setState, state)
}
// here we make it extandable for ordinary way if required
if (action.type === '...') {
// ...
}
}
and nothing else...
So the question is whats wrong in such approach that will require for coder just write a function in one file 'actions.js' and call it from any component as props.someActionName(someParams) instead of changing 4 differents files?
Thank you
Redux is supposed to make complex requirements easier to implement but if you have simple requirements then it makes implementing these requirements more complicated.
The motivation mentions CQRS(Command Query Responsibility Segregation) that separates how you read from store (in redux with selectors and I'm a big fan of reselect) with how you write to it (with action and reducers).
The actions and reducers are the command (write) part of CQRS and is event sourcing, redux is sometimes referred to as an event store. This enables you to add or remove handlers (reducers or middle ware) for your events (actions) that can update the store, dispatch other events (=actions), do asynchronous stuff, write to local storage.
If you need to do all these things in one function (async fetch, write to local storage, call other functions (dispatch other actions),...) then that function becomes unmanageable.
Even if the function only calls other functions then it still needs to know the entire process of certain action. But if (for example) you had a local storage middleware that would write to storage on certain actions then no other code needs to know how or when it's called. So when logic of writing to local storage changes it is limited to the local storage middle ware.
This is the advantage of handlers (reducers, middleware) listening to events (actions), the handler only needs to know about a small portion of the process, not the entire process.
With event resourcing we also know why the state has a certain value instead of only knowing what the state is, the article states:
However there are times when we don't just want to see where we are, we also want to know how we got there.
Another big advantage of an event store is that you can re create the data by playing back the events. All this is excellently done with redux def tools.
Here is a great book on React with Redux.
Conventional-redux is a:
Library for small and medium react applications, it wraps the react-redux and provides API based on convention over configuration pattern without breaking redux compatibility.
You simply define an interactor:
class CounterInteractor {
// initial state
defaultState() {
return 0;
}
// actions:
doubleAsync() {
setTimeout(() => { this.dispatch('counter:double') }, 500)
}
// reduce methods:
onIncrement() {
return this.state + 1;
}
onDouble() {
return this.state * 2;
}
}
And dispatch actions to that interactor from your connected component component. That's it!
We handle side-effects in middlewares, not in reducers, which means we always need to dispatch an action to handle side-effects. Do you think it's proper to dispatch an action, which is not changing the state, to make an API call? I've always avoided it because Action stands for changing the State in my understanding.
I am also handling it in my projects just like you described (only dispatching Actions, which are changing the state, but also having an side effect for api calls).
Whats wrong with doing so, but you have the additional attributes loading and loaded in your state?
So within your reducer function for the initial Action, you would only set loading = true and trigger the side effect.
Within the reducer function for the Success Action you would set your state like this:
return {
...state,
data: action.payload,
loading: false,
loaded: true
};
This also has the advantage that you can easily display a loading indicator, if your rest call takes a while.
You can use action generators which may or may not dispatch actions as per your need
This is a action generator which is used to remove a library item from store on a condition that if it is found on the server otherwise not
export const startRemoveLibraryItem = (libraryItemId) => {
return (dispatch) => {
return database.ref(`library/libraryItems/${libraryItemId}`)
.then((snap) => {
// if item found on server I am going to dispatch a action now
if(snap.val() == something)
dispatch(removeLibraryItem(libraryItemId));
})
.catch((e) => {
// item some error do not do anything
console.log('Error in removing Library Item..');
});
}
}
My questions is a conceptual one and based on the issue outlined in this post: React Redux capture updated store state after updating database. I don't think any code is needed to understand or be able to answer it. But if not it is at the link above.
I think I might have missed a small detail about the react/redux state update process following an action that changes the back-end data that a state variable reflects. My question is: When I dispatch a save action, should I then also be dispatching a request to update any state that depends on that underlying data?
So for example, right now the way I'm thinking about it and implementing my code is as follows:
app starts and ParentComponent loads and dispatches GET_DATA on componentDidMount which initializes state variable data which is reflected on ParentComponent in a table
when a link is clicked on ParentComponent, ParentComponent renders ChildComponent which is a react-modal popup that displays elements of data so it can be updated
there is and Save and Close button on ChildComponent; when you click the button, SAVE_DATA is dispatched and the changes to data that are made on ChildComponent get saved to the database
THIS is where my question arises... at this point should I also be calling GET_DATA to dispatch the process of "refreshing" data in my state? Would this be the right way to handle saving data to a database when using redux so that all components that rely on data get updated?
Note: What I'm currently doing is that after step 3, I am simply triggering a refresh function in ParentComponent so that it rerenders and hence reflects data in state. The epiphany I just had is that there is no way for data in state to reflect the new saved data because GET_DATA has not been dispatched after saving and rerendering the component does not trigger GET_DATA.
Are my assumptions correct? Should I be calling GET_DATA somewhere else in my ParentComponent like ComponentWillReceiveProps? The issue I had here is that maybe I'm doing something wrong, but it triggers an endless loop. Somehow though I feel that is the only place where I can address my need to dispatch GET_DATA after the local ParentComponent state is changed by setting refresh (a ParentComponent state variable) to true.
I think it would benefit you to refactor your actions a bit to take advantage of the action/middleware/reducer pattern.
You would have an action GET_TRANSACTIONS, that would take your year param. Your transactionsMiddleware would respond to the GET_TRANSACTIONS action by making your fetch request and would dispatch GET_TRANSACTIONS_SUCCESS with the respond data on success. You transactions reducer would then process the data into your store.
actions
export const getTransactions = year => {
return {
type: "GET_TRANSACTIONS",
year
};
};
export const getTransactionsSuccess = payload => {
return {
type: "GET_TRANSACTIONS_SUCCESS",
payload
};
};
middleware
function getTransactions(year) {
fetch().then(response => dispatch(actions.getTransactionsSuccess(response.data));
}
reducer
const getTransactionsSuccess = (state, action) => {
return Object.assign({}, state, newStuffFromActionPayload);
}
You would also have an action SAVE_TRANSACTIONS, which would be what your button would dispatch, along with the data to save. Your transactionsMiddleware would respond to the action by dispatching the update request. Your API would return the data from the updated record.
This is where you would have the middleware dispatch a follow-up action. It could be your getTransactions action, but it'd be even better to dispatch an action that your reducer would respond to by merging in the new data to your store.
actions
export const updateTransaction = payload => {
return {
type: "UPDATE_TRANSACTION",
payload
};
};
export const updateTransactionSuccess = payload => {
return {
type: "UPDATE_TRANSACTION_SUCCESS",
payload
};
};
middleware
function updateTransaction(transUpdate) {
fetch().then(response => dispatch(actions.updateTransactionSuccess(response.data))
}
reducer
const updateTransactionSuccess = (state, action) => {
find the record in the state, update it with data from action.payload
return Object.assign({}, state, updatedRecord);
}
If everything is set up correctly, it should trigger an update on your parent when it detects the change in the store. You avoid making two API calls for every save as well.
This is the one thing that I haven't found a standard solution to yet.
I have my store setup with redux-saga for side effect handling, I dispatch an action (that has async side effects) from a component, and now want the component to do something once the side effects are handled (for example navigate to another route/screen, popup a modal/toast or anything else).
Also I want to display a loading indicator or any errors on failure.
Before redux, this kind of flow was straight forward, it'd look something like this:
try {
this.setState({loading: true});
const result = await doSomeRequest();
this.setState({item: result, loading: false});
} catch (e) {
this.setState({loading: false, error: e});
}
With redux, I'd typically want to dispatch an action initiating the flow and have all related information in the store, to allow many components to listen to what is happening.
I could have 3 actions, 'requested', 'success', 'failed'.
In my component I would dispatch the requested action.
The responsible saga will then dispatch either the 'success' or 'failed' action upon handling 'requested'.
My Component will reflect on the changes.
But I haven't found out a standard way to figure out if the action has completed. Maybe the store hasn't updated as result of the async action (NO-OP, but loading state would still change I guess). But the action still succeeded, and I want to do something like navigate to another screen.
I really tried finding this kind of (seemingly common) scenario in the redux docs, redux-saga docs or Stackoverflow/Google, but no success.
Note: also with redux-thunk I think this behaviour is straight forward to achieve, since I can just .then on an async action dispatch and would receive the success action or the error action in catch (correct me if I'm wrong, never really used thunk). But I haven't seen the same behaviour achieved with redux-saga yet.
I've come up with 3 concrete solutions:
Most primitive solution, handling only the 'success'/'failed' actions from the component. Now this solution I am not a big fan of. In my concrete implementation there is no action that indicates that the async request has been started. The side effects are handled right there in the Component, instead of being abstracted away within a saga. Lots of potential code repitition.
Running a one time saga right before dispatching the request action, that races the 'success'/'failed' actions against each other and allows to react on the first occurring action. For this I've written a helper that abstracts the running of the saga away: https://github.com/milanju/redux-post-handling-example/blob/master/src/watchNext.js
This example I like a lot more than 1. since it's simple and declarative. Though I don't know if creating a saga during run time like this has any negative consequences, or maybe there is another 'proper' way to achieve what I'm doing with redux-saga?
Putting everything related to the action (loading, successFlag, error) into the store, and reacting in componentWillReceiveProps on action changes ((!this.props.success && nextProps.success)) means the action has completed successful).
This is similar to the second example, but works with whatever side effect handling solution you choose.
Maybe I'm overseeing something like the detection of an action succeeding not working if props hail in very fast and props going into componentWillReceiveProps will 'pile up' and the component skips the transition from non-success to success altogether?
Please feel free to have a look at the example project I've created for this question, that has the full example solutions implemented: https://github.com/milanju/redux-post-handling-example
I would love some input on the methods I use to handle the described flow of actions.
Am I misunderstanding something here? The 'solutions' I came up with were not straight forward to me at all. Maybe I'm looking at the problem from the wrong angle.
Are there any issues with the examples above?
Are there any best practice or standard solutions for this problem?
How do you handle the described flow?
Thanks for reading.
If I understand your question correctly, you want your component to take action based on actions fired off by your saga. This would typically happen in componentWillReceiveProps - that method is called with the new props while the old props are still available via this.props.
You then compare the state (requested / succeeded / failed) to the old state, and handle the various transitions accordingly.
Let me know if I've misinterpreted something.
I achieved the point of having an asynchronous action callback in a component using saga the following way:
class CallbackableComponent extends Component {
constructor() {
super()
this.state = {
asyncActionId: null,
}
}
onTriggerAction = event => {
if (this.state.asyncActionId) return; // Only once at a time
const asyncActionId = randomHash();
this.setState({
asyncActionId
})
this.props.asyncActionWithId({
actionId: asyncActionId,
...whateverParams
})
}
static getDerivedStateFromProps(newProps, prevState) {
if (prevState.asyncActionId) {
const returnedQuery = newProps.queries.find(q => q.id === prevState.asyncActionId)
return {
asyncActionId: get(returnedQuery, 'status', '') === 'PENDING' ? returnedQuery.id : null
}
}
return null;
}
}
With the queries reducer like this:
import get from 'lodash.get'
const PENDING = 'PENDING'
const SUCCESS = 'SUCCESS'
const FAIL = 'FAIL'
export default (state = [], action) => {
const id = get(action, 'config.actionId')
if (/REQUEST_DATA_(POST|PUT|DELETE|PATCH)_(.*)/.test(action.type)) {
return state.concat({
id,
status: PENDING,
})
} else if (
/(SUCCESS|FAIL)_DATA_(GET|POST|PUT|PATCH)_(.*)/.test(action.type)
) {
return state
.filter(s => s.status !== PENDING) // Delete the finished ones (SUCCESS/FAIL) in the next cycle
.map(
s =>
s.id === id
? {
id,
status: action.type.indexOf(SUCCESS) === 0 ? SUCCESS : FAIL,
}
: s
)
}
return state
}
In the end, my CallbackableComponent knows if the query has finished by checking if this.state.asyncActionId is present.
But this comes at the cost of:
Adding an entry to the store (though this is inevitable)
Adding a lot of complexity on the component.
I would expect:
the asyncActionId logic to be held on the saga side (eg. when the async action is connected using mapActionsToProps, it returns the id when called: const asyncActionId = this.props.asyncAction(params), like setTimeout)
the store part to be abstracted by redux-saga, just like react-router is in charge of adding the current route to the store.
For now, I can't see a cleaner way to achieve this. But I would love to get some insights on this!
Maybe I didn't understand the problem your facing but I guess they meant the client would look something like this:
mapStateToProps = state => {
return {
loading: state.loading,
error : state.error,
data : state.data
};
}
and the component will render like:
return(
{this.props.loading && <span>loading</span>}
{!this.props.loading && <span>{this.props.data}</span>}
{this.props.error && <span>error!</span>}
)
and when requested action is dispatched the its reducer will update the store state to be {loading: true, error: null}.
and when succeeded action is dispatched the its reducer will update the store state to be {loading: false, error: null, data: results}.
and when failed action is dispatched the its reducer will update the store state to be {loading: false, error: theError}.
this way you won't have to use componentWillReceiveProps .
I hope it was clear.
I've got a component that uses componentWillMount to make an API call through redux to get data and update the state.
Before calling this method, I need to go to the DB and get a property upon which I'll decide if the data retrieval (from the 1st paragraph) should happen.
What I was thinking of doing (using promises) -
Fetch the property (from paragraph 2)
then, if data is needed, dispatch the normal flow (paragraph 1).
if data is not needed, carry on.
My question is WHERE should it go in your opinon.
On the one hand, it feels like a mega overkill to do it through the store. On the other hand, any chance I'll encounter side effect problems.
In addition, I could implement the logic in the component or in the action creator. What do you think is best?
Additional info:
1. I'm using redux-thunk. Changing to sagas is out of the question.
2. The property that I'm checking is in 1 reducer, while the data that needs to be fetched is in another reducer (dunno, might be problematic for some solutions.).
Option 1:
import {getData} from '....../dataActions';
import {getToken} from '......../userActions';
const MegaComponent extends React.Component {
componentWillMount() {
getToken(uid)
.then(shouldUpdate => {
if (shouldUpdate) {
getData(uid);
} else {
console.log('no need to get data');
}
})
}
}
funciton mapStateToProps(state, ownProps) {
return {
user: state.user
}
}
function mapDispatchToProps(dispatch) {
return {
getToken: (uid) => dispatch(getToken(uid)),
getData: (uid) => dispatch(getData(uid))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MegaComponent);
Option 2 (what I think should be done)
export function getToken(uid) {
return dispatch => {
return api.getToken(uid)
.then(token => {
if (token === 'what it should') {
return {type: 'NO_DATA_CHANGE_NEEDED', action: null};
} else {
// the action that handle getting data is in a different action creator.
// I either import it here and call it, or there's a better way.
}
})
}
}
UPDATE
This might come in handy for future visiotrs - getting state inside an action creator
Hey this is a very broad question, but I want to give a short advice on how I would solve it although I am not quite sure if i got it totally right.
So I would start by separating the concerns of point 1. (database call) and 2. (everything else)
You could start by writing a Component-Wrapper (HoC) that only does the database call to get the prop you need for further processing.
Once you fetched the data from db you can render the InnerComponent with the data as prop and do everything else you need there just by checking the prop you injected and trigger additional actions.
I wouldn't let my component deal with logic. keep your components all about View. Think of a component as a visual representation of a point-in-time state.
In componentWillMount you can check if the data exists, if it's not then you call your action which will fetch the data through an API and pass the data to the reducer. the reducer will 'update' the state.
the dispatch calls should be invoked from actions.js and not from your component.
So, my feeling was right - option 2 it is. Much like #guruPitka suggested.
In my action creator (that is being called by the component), I get the current app state, make an API call, compare the tokens and act accordingly.
export function getToken(uid) {
return (dispatch, getState) => {
const currentToken = getState().user.token;
return api.lastAppLaunch(uid)
.then(newToken => {
if (newToken === currentToken) {
// All is good, carry on.
} else {
dispatch(getData(uid)); //getData is from a different action creator.
}
});
}
}
And the call in the component -
this.props.getToken(uid);