Wait for Redux action to dispatch a second one - reactjs

Hi and thanks in advance for reading my question.
I am working on a React app (version 16) with router v3 and Redux.
The app has an structure similar to this:
<Route path="/" component={BakeryComponent}>
<IndexRoute component={LandingComponent}/>
<Route path="employees" component={EmployeesComponent}/>
<Route path="clients" component={ClientComponent}/>
</Route>
So we have like different entities. Bakery which is the main one, employees that depends on the selected bakery and clients which also depends on the bakery.
When we access the app we land on the landing page which shows general info of the bakery (there is one by default selected) like name, employees quantity and some other. All that info comes from the backend, it is retrieved in the componentDidMount of either Bakery or Landing component (at this point it does not make any difference).
When the user is in the landing page he can navigates to either employees or clients views. When that happens the component is mounted, let's suppose it is employees component, and in the component's componentDidMount method we retrieve the needed information (the employees list in this case).
Everything works fine until now.
Now, if the user refreshes the page everything should still works (it is a requirement) but it doesn't. The problem is that when that happens BakeryComponent and EmployeesComponent (both of them) are mounted again, so new backends calls are triggered (in parallel). But the backend call to employees depends on bakery to be resolved first because we need to know the employees of what bakery we want, so we get an error at thi point.
I tried to check in the invoked action (the one to retrieve the employees) if the bakery was already resolve (using getState) and if not, wait for it to be resolved. But it does not convince me, it looks kind of a hack. I have the feeling that React/Redux should already have the tools to solve this but I just don't know them. Am I right?
Any help will be really helpful.
Thanks !

I can think of a way for you, although It's actually not a cool solution. You can check if the data in the BakeryComponent is ready or not before calling dispatch in the LandingComponent. Something like this:
BakeryComponent
componentDidMount() {
// after this one finish we may have something in the store like: state.bakery.loaded = true
dispatch(loadBakery())
}
LandingComponent
// Landing component should connect to state.bakery.loaded to get updated data
componentDidMount() {
initData()
}
componentDidUpdate(){
initData()
}
initData() {
// check if bakery loaded and landing data is not loaded
if (state.bakery.loaded && !state.landing.loaded) {
// this should load data and set the state.landing.loaded to true
dispatch(loadLanding())
}
}

Related

How to use context provider with react router components?

I have browsed almost every resource on the internet and have not found a solution so please don't recommend me someone else's question that is identical because I have most likely already tried it.
Here is my container that has all of the routes. These reside in the App component which is basically the uppermost component.
Here is my provider. Basically, I have a title and when you click on the different links that take you to different views (i.e., Home, Client, Company) I do something like...
const { setHeader } = useContext(HeaderContext);
useEffect(() => {
setHeader("Company Dashboard");
document.title = "Company Dashboard"
})
As you can see the "setHeader("Company Dashboard")" should set the header. The header is actually a separate component that rerenders if the HeaderContext updates.
The Provider works btw. This is because the Header component has uses this context to set the element. So if I change the empty string to "oeijsofiej" it will display "oeijsofiej" as the title BUT it does NOT change when you click on different tabs.
I have tried to surround all of the components in a but unfortunately, that does absolutely nothing. No errors thrown and the setContext doesn't do anything. I have tried other ways of formatting the element like
<Route path="/" exact render={() => <HeaderContextProvider><Home></HeaderCOntextProvider>}/>
Unfortunately, this also does nothing. I've tried other methods and get weird errors. Not sure what I'm doing wrong I've exhausted all of my resources. Please help.

How does react-router persist location state over page refreshes?

I am just so very confused.
I have a workflow where someone can start to fill out a form for a product. It is a long form and I want to save progress to the server as they type (but not until they fill it out for a bit). So we start at a url for creating the form, after they've typed for a bit, we POST to create a resource on the server, and after the request finishes, we update the url to the edit route with the new id.
In other words, you start filling out the form at url /product then after you've filled it out for a bit the url shifts to /product/123. After that, loading that URL gives you your form.
So basically I have
<Route path={`/product`} exact component={CreateProduct} />
and
<Route exact={true} path="/product/:productId" render={({
match: {params: {productId}},
location: {state: {data}={}}
}) => (
<EditProduct productId={productId} initialData={data}
)} />
See that state? That's because the way I do the switch over from create to edit mode is something like this
const id = await apiFetch(`/api/product`, data, {method: `POST`})
this.props.history.push({pathname: `/product/${id}`, state: {data} })
in the constructor of my <EditProduct> component I have
constructor({productId, initialData}) {
this.super()
this.state = {}
if(initialData)
this.setState({data: initialData})
else
getProduct(productId).then(({data}) => this.setState({data}))
}
By doing that, the initial data into the <EditProduct> is seeded from the <CreateProduct> component and I don't need to reload it from the server or anything.
This works, the transition is smooth, the url updates, and everything is hunky dory.
I can now continue editing the <EditProduct> component and it saves properly. I can open a new tab to the same url and it loads everything up and I can continue. This happens because in that situation initialData is undefined so it's loaded from the server. yay!
BUT
If I instead refresh the original tab things get weird. Any changes that have accumulated since the save are lost. Drilling down in the debugger I see the issue is that initialData passed from the location.state.data object is not empty - it is the initial object from when the product was first created.
So where on earth does it come from? I just did a full page refresh (even a "hard" refresh with no cache and devtools open). That data isn't in the URL (and in fact copy pasting the url into another tab in the same window doesn't have this issue).
The only mechanism I'm aware of that can persist data across refreshes but not to new tabs like this is sessionStorage, yet when I check it in the console, I am told
> sessionStorage
< Storage {length: 0}
I've even thought that maybe react-router is manipulating session storage just before the page unloads and just after it loads, but breaking on the first line of my javascript bundle shows the exact same thing.
So how on earth is this persistence happening!?
I believe the asker already resolve this problem, the answer is buried in the comment though.
The question is actually down to this:
Where the state come from when the user reloads the page? And state refers to props.location.state provided by react-router
TLDR; the state is not a plain javascript implementation, it is bound to the browser environment.
The BroswerRouter of react-router use the underlying native broswer history API directly so the history API is bound to the platform, you can not predict its behavior based on the normal rule.
Here is the special part:
The state object can be anything that can be serialized. Because Firefox saves state objects to the user's disk so they can be restored after the user restarts the browser
Most of the users treat the state as a plain javascript, so there is a problem
I had a very similar problem, and the same confusion.
Solved it with window.history.replaceState()
I had a simple search form which redirected to a second page, and used the location state from the router to repopulate the search input on the second page.
In myse case, this happened:
Search for "foo" on the first page -> Get redirected to the second page, and see search+results for "foo".
Search for "bar" on the second page. -> See results for "bar".
Hit refresh. Expectation? Either an empty search bar, or search+results for "bar". -> Instead, see search+results for "foo" (??)
I solved this by making it so that every time the user does a search on the second page, I replace the state using window.history.replaceState with the correct search term. This way a refresh gives the user the expected search. Replacing the state with an empty object on each search from the second page worked fine as well, giving the user an empty search on each refresh.

How to update match.params?

The react app has search page. There are input.
The path is 'search/:query', and by default you see zero results.
If you go to 'search/star%20wars' you will see some results. In componentDidMount() I added if statement to load result if match.params.query is not null.
If I type into search input Spider Man and click submit - I trigger a search and show results. But if you reload page - you will see the result about Star Wars. So how update match.params.query? Or may be there other solution of fix this.
You need to update the history object as well.
What you are doing is altering the history object available to you and calculating the results based on that object. But when you will refresh the page it still holds the original history object.
One way of doing it, you need to push or replace a new route in the history.
Because evert search page is a new page, so if you want the previous pages to stay preserved you should use history.push otherwise history.replace
Implement it like this:
var routeObj = {
pathname: samePath,
state: sameState,
query: newQuery
}
//push it in your history using which ever routing library you are using.
//For Example:
router.history.replace(routeObj);
Note: Do not worry about rendering speed on changing the history. React is smart enough to handle that. Basically whenever you will push a route whose component is already mounted it will not unmount and remount the same component again, rather it will just change the props and will re render it.
The callback for this case will be => componentWillReceiveProps
#misha-from-lviv The way I see your problem statement is that you have two source of truth on is the query params, using which you should update your state, and the other is the default state which is populated from the default value of your filters.
As #Akash Bhandwalkar suggested, you do need to update the route in using the History API. But also you also a need a top-level orchestrator for your application state, which will allow you to read and write to the history api ( change your route ) and also do an XHR / fetch for you to get the results.
How I'd approach this is that I'd start with a Parent component, namely FiltersContainer , which actually does this orchestration to read and write to the url. This Container would have all the side-effect knowledge for fetching and updating the routes ( error handling included ). Now the all the child components ( filters and search results maybe ) will just read the state thus orchestrated and re-render.
Hope this guides your thinking. Do revert here if you need further guidance. 😇
Cheers! 🍻

React Router+Redux: how to fetch prior to dispatching a route change, but also respond to the navigation events?

I'm learning to use React + Redux + react-router-redux. The project is a toy project, and there are only two routes:
<Route path="/" component={ItemList} />
<Route path="/:id/" component={ItemView} />
First, I have implemented basic operation: when user clicks on ItemList, I call dispatch(push(/${id}/)). Then, using <Router onChange={...}> I detect the navigation and dispatch an action to fetch item's data from server. When the data arrives I can finally put it in my state and ItemView updates from just saying "Loading..." to a proper item display.
However, I want things shiny and fancy. On click, I want to start fetching data but keep old UI (the item list) until the data arrives, and only then - having the data - quickly switch the route. Just like GitHub or GitLab do. So I had researched a bit, threw in redux-thunk, redux-promise-middleware and react-redux-loading-bar and wrote some code.
In the list component, I have what's essentially onClick={dispatch(getItem(id))} where getItem is:
export function getItem(id) {
return function (dispatch) {
return dispatch({
type: GET_ITEM,
payload: new Promise(resolve => {
fetch(`/${id}/?json`)
.then(response => resolve(response.json()))
})
}).then(() => {
dispatch(push(`/${id}/`));
})
}
}
When the promise is resolved, redux-promise-middleware
generates a 'GET_ITEM_FINISHED' action for me. So, in my reducer,
I just put the data I got from server into state:
...
case 'GET_ITEM_FINISHED':
return Object.assign({}, state, {
item: action.payload.item
});
...
(The code above had evolved a few dozen of trial-and-error iterations, so it's not clean. I know, it could be prettier, sorry about this.)
It seem to work at first glance, but this way fetching data is completely detached from navigation events - and that's bad. Whenever there's a navigation event that happens from any cause other than getItem dispatch (e.g. back/forward buttons), there won't be any fetches and necessary state transitions. This means, the approach I've taken is wrong, on the concept level.
I'm sort of stuck thinking of how to do it. My only thought is that the state should be like a cache that can "hit" (have the item data at navigation event's time) or "miss" (don't have it). When I have the data - like in my code above - I can readily display it. When there's a miss, things get hairy - either a reducer or a component would have to dispatch a "GET_ITEM" action, but while this would probably work, it somehow feels like a bad idea.
Basically, all I want is that red thin "loading" line when navigation's within my UI, but also proper handling of back/forward buttons (or whatever may change location). When user navigates directly to the page (full page load), the backend will make data available in HTML it serves, as store's preloadedState.
What I need is a high-level idea/overview of how such things are generally done properly. Or a pointer to any library that implements such idea, if there is anything readily available.

How to load initial data in Flux, but only if a component needs it

I am trying to adhere to the Flux pattern as best I can, but I must be missing something, because I can't figure how to get this to work without using a hack or doing something that feels wrong for the pattern.
I have an application that is segmented by big buckets of data. Let's say its a Resource Management app, and each Department in the company has a different set of resources. Users of the app will always go to whatever department they are a member of, and stay there. Some people may have multiple departments they manage.
So, at the top of this app there will be a way to select what Department you are working with.
The key here is that I cannot really display anything useful until the list of departments has loaded and I have selected which department to display (which will just be the first item in the list for now).
To accomplish this, the root component of the app looks at the DepartmentStore to determine if it is currently loading or not. If it is loading, it does not even bother rendering the component. Something like this:
function initApp(projectSlug) {
// prefetch some data!
DeptActions.getAll();
}
function getStateFromStores() {
return {
loading: DeptStore.getLoading()
};
}
export default React.createClass({
getInitialState: function() {
return getStateFromStores();
},
componentDidMount: function() {
initApp();
DeptStore.addChangeListener(this._deptChange);
},
componentWillUnmount: function() {
DeptStore.removeChangeListener(this._deptChange);
},
_deptChange: function() {
this.setState(getStateFromStores());
},
render: function() {
return (
this.state.projectsLoading
? <div>Loading...</div>
: (<div>
<Header/>
<div className="body-content">
<div className="grid-container">
{this.props.children}
</div>
</div>
<Footer/>
</div>)
);
}
});
The props.children will be whatever component was designated by React Router.
This way, the actual component for the page can render and take for granted that all the departments are loaded and there is a "current" department already all set up.
The problem is that the component, once it renders, needs to start loading the resources for the department. In order to begin that process, it kicks off an action when it mounts:
componentDidMount: function(){
ResourceActions.getResources();
},
This action will kick off an API call that uses the DeptStore's current Department to know what to load. Then it dispatches a RESOURCE_LOADING event, and, perhaps you can guess, it fails, because:
Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.
That's because basically, the action of the departments loading causes the resource component to render, which tries to kick off a resource fetch action. One action leads to another.
All the examples and similar questions about this type of issue aren't satisfactory to me. For example:
Flux Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch
a scenario flux doesn't support
Even the Todo list and Chat App examples from Facebook do not address this. The Todo list doesn't even have 'initial' data that loads. The Chat App does, but it simply loads all the messages when the app starts.
I cannot load the Resources when the app starts, because I have to know for which department first. Even if I somehow knew that, I do not want to load the Resources unless the Resources component needs them. The user may be on other pages with the current Department, which do not display the Resources. So somehow loading the Resources in response to the original Dept change event is not viable to me either.
Please help me get the insight I'm missing to solve this problem? :) How do I keep the list of Departments separate, and then load Resources in response to the Departments loading, and do it so that it is somehow driven by the component, so that I don't make an expensive Resources API call unless the component on the page needs it?
As the answers in the other posts point out, this is a shortcoming/ hole in the flux pattern solution. The only workaround (similar to previous answers), is to create a call from componentDidMount() and from componentDidUpdate() in the top component, which does something like:
checkAndFetchResources: function() {
if (this.props.resourcesToRender != undefined) {
ResourceActions.getResources();
}
}
And pass the resources fetched as props to the resources component.
That way, your fetch action is called after the previous dispatch round followed by render cycle is completely finished.
This is similar to previously provided answers, so may not be sufficient for you (as you state in your question). In that case, further clarification on WHY the answers do not meet your needs would help.

Resources