I'm looking for ways to structure state in my React components so that I can accomplish two goals together: (1) managing browser history in a complex app, and (2) letting individual components be easily reusable in environments outside the main app.
The app has a Panel component which lets users navigate and read content on our site. In the main app, multiple panels can be opened and arranged together. Outside of the main app, there are other pages where I'd just like to be able to easily drop in a single Panel component.
As this code has developed so far, each Panel has a rich state representing what content it has loaded and other display settings. This makes it easy to drop in to another page - I can just render a Panel component and the user can interact with it and it takes care of itself.
For browser history however, this is getting hairy. There is an App component which manages multiple Panels and the states pushed to and popped from history. In order for it to have a complete picture of the state of the whole app, each panel pushes up a copy of its state when changes occur. When state is popped from the history that App component can pass down an initialState from each of the Panels it renders. As you can imagine, this is getting messy and error prone with the cycles of updates that end up looping back to each other.
It feels like the approach I need to take is to Centralize State (a la the recommendation here) and try to make each individual Panel stateless. Since it will be a significant effort to rewrite all the calls to setState already in the code, I'm trying to evaluate if this is the only way to go. It also feels like this approach will make it harder to reuse the components outside the app. I'll lose the feeling that a Panel is a self-contained component which I can just render on a page, since it will need some kind of outside manager as well to handle its state and all the events that change it from within.
What would you recommend?
I made a application on react and implementing react-router you can pass throw the URL data to every panel know in what state are and what content is loaded, by example you can have an url like:
http://yoururl.com/index/user/userID1/item/itemID2/filterDate?="19/09/2014"
To reuse your panels you can made a url that made an herarchical route view: You can pass throw components everything you want to know on the render wich view you have to load.
My routerDomExample
I recommend to investigate react-router.
Related
I'm learning react and having some trouble getting my head around some code design. I have a Session component that has several Activity components in my project. In the Session component I also have a Timeline component and I need to show the total duration of all of the events. So I need to have the start time for each of the Activities in the Session component and the end time of the last. I know of "lifting state up", but I find it a little strange in my OO way of thinking from C++ to store the data in the parent and not where it "belongs". What if I later need some other data from the activity.. Then I would lift parts of the Activity data up and store some of it in the Activity.. seems quiet messy?
I thought of also having a ActivityData object and store a list of them in the Session and pass that in to each of the activity to display it there. Another way I thought about would be to have a SessionModel object and a ActivityModel object and have this seperate from the component all together and pass these models in for rendering in the component.
I am also just getting into typescript and moving my code into that. I was thinking that I could define a custom ActivityData type in the same file as the Activity component and still store the list in the Session, but then at least its more explicit that the data belongs to the activity.
What is the right (or React) way of doing this?
You are right in your thinking and yes it isn't necessarily an OO way of doing things but more of a functional programming approach
Each component can have state, and it should store that state in it's own component. If that state is then needed by another component you can pass it down as a prop. However, if it isn't a child component that needs it then like you said you should lift state up.
The next problem happens when your app starts to grow. So then you need to make some choices. You should split your components up so they don't get too big. You can have some more logical components and then have some presentational components that don't handle logic but essentially just take props and render the views from you.
However, your app is still growing so at this point you might want to invest some time in introducing a state management tool to your app. React has the context
api built into so you can use that. or you could use a library likeredux. Redux is particularly good at abstracting state to a "global" store and you each component can "connect" to the store to access the state. Really good for apps where you have lots of shared state and lots of components need to know about similar pieces of state
In terms of Typescript then it's certainly a wise idea to include that as the language is heading that way. You can keep types in the same file or keep them in the same directory but have a .types.ts file that you import into your code and declare your types/interfaces in there
I was looking to build a feed into a react app that as you were scrolling through it it will constantly fetch more data to display to you (it basically has no end). I was looking for a way to avoid re-rendering the whole feed every time new data was fetched to be displayed to the user since it will start to get slow to re-render the whole component after a while.
I know I can do it with server-side rendering or through ReactDom.createPortal to render the element outside the react tree. The first option is out since that would be the only thing I would need a backend for and is not worth it.
The second option looks like the way to go but I am not sure if I can still access the state object and other react features from within the component rendered outside the react tree (I don't know if I would really need them or not but having them can't be bad)
Outside those two options, is there any other that I am not aware of?
Coming from some classic server-side template languages (php, jsp), I have a general architectural question on React.js:
Can I limit certain components of a page to be rendered server-side only? And reduce the client-side javascript bundle accordingly?
*I find that often ridiculously large. One reason (afaik): Every component must have the capability to be re-rendered on state changes and SPA-(aka soft, virtual..) page navigation, since all changes come in as data, not as prerendered html chunks (afaik).
Basically, I see 2 different types of content sections in almost all of my web projects:
1) highly dynamic “facebook-ish” interactive sections
Personal greetings, messages and message counters, likes and replies… here default React behavior is at its best: new data comes in, global state (redux store) changes and all affected components get re-rendered. Which would be a daunting task without react and redux sound principles. And certainly client-side rendering/updates are the way to go.
That's often the user-login area at top ("Hello Joe, 5 unread messages"), some live data (stock, weather,…) in the middle and said comments closer to the bottom.
2) SSR “static” content (think PHP)
However, for many sections I know for sure, nothing is client-side dynamic. Footer Menus for example, might stem from a database, but are certain to not change for the duration of the session. (Even if John Doe decides to like, comment or change his name…)
Rendering them only server-side would be enough. Often the main content block can also do with SSR-only. (And all the layout-ish sub-components needed to render its html)
Still, I have to give all components to the client bundle, so that also virtual/soft page navigation works... (which transmits new data, but not pre-rendered sections)
You could tell me as a workaround to simply keep the footer out of the react container mounting point, but that's not my point... "static" aka sections that can do with pure SSR might be elsewhere, too, between dynamic header and lower response/feedback/liking sections…
I would like to mark Type-2-components as "SSR-is-enough" (and also their sub-components – unless webpack dependency tree figures out, they are used in Type1-CSR-components, too…).
So send it as a single html blob. Also receiving it "pre-rendered" on SPA-ish virtual page navigation would be needed afaik. (since the component knowledge will be missing from the client bundle)
Is there a way to do this? Has someone thought of this general, imho common problem before...?
Hydrating only certain components is definitely a problem that is being thought about.
One web framework that solves is problem is Astro. It only hydrates components which are actually interactive.
Another is Fresh, which has a concept called "islands." The entire page is server-side generated and when you need interactivity, you create an "island" which is a component which is hydrated on the client.
In my application, there are views with dependencies. For example, in one view a user could select an item from a list (generated on the server), and in next view the user would perform operations on the item. The item is passed to the second view in props. I'm moving to using react router, but there are some difficulties:
I can't use props for transferring data anymore. What would be a preferred way to pass data? Do I have to use redux?
Users can navigate from any view to any other view by directly using url. However, some transitions don't make sense: e.g. user navigates to item editing view from somewhere else, and therefore does not have an item selected. Is there a way to limit allowed transitions?
This is a very broad question, but I'll take a stab at it.
Can you use Redux? Sure, Redux is good for centralizing your state which can easily be shared among your components. As far as limiting the url's they have access to, I would use your reducer to look at your current state, if you're using Redux and if data is not there, meaning they should not be at this step, use a javascript redirect to where they should be instead.
Finally, you don't have to use Redux to share data between components this could be done by setting global variables your components can access, but cross component communication is where Redux shines.
I currently have my application structured so that I have a FormWrapper component which loads the configuration for the passed in FormName, which might be rendered by React-Router like this -
<Route path="/Diary/Test" components={require('./components/wrapperComponents/formWrapper.jsx')} FormName="AppointmentBooking"/>
When FormWrapper loads it calls an ActionCreator and gets the configuration from the server about which page and renders the page passing in the configuration via objProps.
render: function() {
return(React.createElement(PageComponents[this.props.FormName], objProps);
}
The reason for doing things this way is that originally we wanted to be able to allow users to easily be able to customize how forms looked and which components were shown so it allowed us to store the forms and their accompanying child components in configuration and then just load them out with a generic wrapper component. So anything generic to every form could sit in the form wrapper, and then form specific code could sit in each page component and load only the parts relevent to that particular page.
The issue I'm having is that if the AppointmentBooking form also needs to fire an action in the componentWillMount/componentDidMount methods to load some data(for example the options in a dropdown list for appointment locations) I receive a "Cannot dispatch in the middle of a dispatch" error.
My reasoning for the issue was that the initial action triggered by the form wrapper was causing a re-render of the formWrapper because it then had data, and then React.createElement was trying to render the AppointmentBooking component which was also triggering an action thereby causing the dispatcher error.
I've read things about using waitFor() to wait for the first action to complete before triggering the second, but this would then involve the child component knowing about and being dependant on it's parent which I thought might mean I'm not keeping a proper separation of concerns.
Is this a common issue and am I approaching this the right way? And if not is there a suggested way of going about acheiving this?
Flux best practices recommend using "Container" components, and I think doing so may solve your problem. https://facebook.github.io/flux/docs/flux-utils.html#best-practices
A "Container" component is a dummy component whose only purpose is to facilitate store communication. By offshoring that responsibility to a single component, you get around weird race issues like this.
The basic rules for containers are that they should always render the same child components, while passing in unique information (as props) gathered from any relevant store. No conditional logic. Just routing of information.
If you're able to restructure into this paradigm, it'll solve your issues. But that's no small task if you already have actions and listeners spread across your application.