How can I prevent React from unmounting/remounting a component? - javascript

I am using react-router and react-redux. I have two routes like this:
<Route path='/edit' component={ EditNew } />
<Route path='/edit/:id' component={ EditDraft } />
where EditNew and EditDraft are data-providing containers that wrap an Editor component using the react-redux connect function:
const EditNew = connect(state => ({}))(React.createClass({
render() {
return <Editor />;
}
}));
and
const EditDraft = connect(state => ({ drafts: state.drafts }))(React.createClass({
render() {
const { params, drafts } = this.props;
const draft = findDraft(params.id, drafts);
return <Editor draft={ draft } />;
}
}));
Now, Editor is rigged up in such a way that when you begin typing into a blank Editor, it triggers a history.replaceState() from /edit to /edit/:id with a ranomly generated ID. When this happens, I get the following sequence of events:
EditorNew unmounts
Editor unmounts
EditorDraft renders and mounts
Editor renders and mounts
When I coded these two containers, I thought that the Editor component contained in both of them would be reconciled without unmounting and remounting. This is problematic for me for several reasons besides the extra unnecessary work, chief among which are that the editor ends up losing focus and proper cursor range after the unmount and remount.
To no avail I have tried specifying key for the Editor component to hint to the reconciliation system that it's the same component, and I've tried shouldComponentUpdate, but that doesn't get called, which makes sense given what React is doing.
Apart from combining the two containers into one container with more complicated render() logic, is there anything I can do to prevent the Editor component from unmounting/remounting during the history transition?

React’s Reconciliation Algorithm says that if the element has a different type (in this case, EditNew and EditDraft), then React will “tear down the old tree and build the new tree from scratch.”
To prevent this, you need to use the same component for both routes.

You can use shouldComponentUpdate and, if the route has changed from /edit to /edit/:id (you can check this getting the router info from the state connected to your component) return false, so it won't refresh the component.

Chances are that this isn't possible with react-router <= v3.
With react-router v4, this should be possible now: https://github.com/ReactTraining/react-router/issues/4578

Related

Best way to get values from multiple complex child components?

I have a parent component that has base data called script, which has multiple sequences and each sequence is composed of multiple items (inputs, dropdown, ... ).
Now I need the updated data in parent since I want to put a save button that is going to save all forms with one click.
It looks something like this:
I tried two ways of handling this:
That each child had an onChange property
in which parent sets the state with the new data. But the problem
here is, that since this is quite a complex form, it re-renders
everything each time, so there was a noticeable delay when typing in
inputs.
The "bad" of just changing the props object in a child,
which is fast, but I know it is a bad practice.
What is the best way of handling forms on a scale like this? Should it be set up differently?
This is a question I've spent some time struggling with myself. There are multiple ways to maintain child state at a higher level; however, I've found that in your particular situation it is often best to use Redux.
To be clear, I generally avoid Redux at all costs (in favor of React's context), but Redux gives you the ability to subscribe to a particular piece of state in your child components. Listening to one piece of state in a child component will prevent your parent and sibling components from updating when you only need a single child to update. This ends up being far more efficient when handling multiple forms at one time.
For example, the following component will only listen to state updates that affect its own state. These updates will bypass the forms parent and sibling components:
import React from 'react';
import { connect } from 'react-redux';
import * as actions from 'redux/actions';
// Custom component
import { InputField } from 'shared';
const FormOne = ({ me, actions }) => (
<form>
<InputField
inputId="f1f1"
label="field one"
value={me.fieldOne}
onChange={(e) => actions.setFormOneFieldOne(e.target.value)}
/>
<InputField
inputId="f1f2"
label="field two"
value={me.fieldTwo}
onChange={(e) => actions.setFormOneFieldTwo(e.target.value)}
/>
<InputField
inputId="f1f3"
label="field three"
value={me.fieldThree}
onChange={(e) => actions.setFormOneFieldThree(e.target.value)}
/>
</form>
);
export default connect(state => ({ me: state.formOne }), actions)(FormOne);
In the above example FormOne is only listening for its own state updates; whereas, similar logic utilizing context instead of Redux will cause the entire component tree that the context provider is wrapping to update (including parent and sibling components):
import React, { useContext } from 'react';
// Custom component
import { InputField } from 'shared';
// Custom context - below component must be wrapped with the provider
import { FormContext } from 'context';
const FormTwo = () => {
const context = useContext(FormContext);
return(
<form>
<InputField
inputId="f2f1"
label="field one"
value={context.state.formTwo.fieldOne}
onChange={(e) => context.setFormTwoFieldOne(e.target.value)}
/>
<InputField
inputId="f2f2"
label="field two"
value={context.state.formTwo.fieldTwo}
onChange={(e) => context.setFormTwoFieldTwo(e.target.value)}
/>
<InputField
inputId="f2f3"
label="field three"
value={context.state.formTwo.fieldThree}
onChange={(e) => context.setFormTwoFieldThree(e.target.value)}
/>
</form>
);
};
export default FormTwo;
There are some improvements that can be made to both of the above components, but they are meant to serve as an example for how to connect child components to an elevated state. It is also possible to connect to a single parent component using props, but that is the least efficient option possible, and will clutter up your architecture.
Key takeaway: Use Redux for your use case. It's the most efficient option if it is implemented correctly.
Good luck!
Wrap all the forms in a component that will only deal with saving all the forms data and running the "save all" function:
the wrapper component should have a state the includes all the forms data, it should probably look something like this:
class Wrapper Component extends React.Component {
constructor(props) {
super(props);
this.state = {
formsData: {},
};
}
}
formsData should probably be structured pretty much like that:
{ 0: { title:"text", type:"video", etc:"etc" },
1: { title:"text", type:"video", etc:"etc" }}
the keys (0,1, etc..) represents the form id, and can be set to any unique modifier each for has.
then make the wrapper component handle the onChange for every individual form -> every change on each individual form should uplift the new state (new updated data) and update the formsData state obj accordingly:
const onChange(formData) {
const formattedData = {[formData.id]: {...formData}}
this.setState({formsData: {...formsData, ...formattedData}})
}
* This is just an example of a case where in each change in each form you uplift the entire data object, you can do it in many ways
Than, the save all button should also be handled in the wrapper component, and uplift all the data you stored with it to the relevant function in a parent component / handle it itself.
Good luck!
Lifting state up is indeed the correct way of doing this. To optimize child sections you can use
PureComponent ==> https://reactjs.org/docs/react-api.html#reactpurecomponent
AKA Memoized Component ==> https://reactjs.org/docs/react-api.html#reactmemo
React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes.
Also if you are within the hooks universe checkout
useCallback : https://reactjs.org/docs/hooks-reference.html#usecallback
useMemo : https://reactjs.org/docs/hooks-reference.html#usememo
If you are using Redux by any chance remember to look at
reselect : https://github.com/reduxjs/reselect

Direct call of a functional component

Stateless functional component is just a function that receives props and returns React element:
const Foo = props => <Bar />;
This way <Foo {...props} /> (i.e. React.createElement(Foo, props)) in parent component could be omitted in favour of calling Foo directly, Foo(props), so React.createElement tiny overhead could be eliminated, yet this isn't necessary.
Is it considered a bad practice to call functional components directly with props argument, and why? What are possible implications of doing this? Can this affect the performance in negative way?
My specific case is that there's some component that is shallow wrapper over DOM element because this was considered a good idea by a third party:
function ThirdPartyThemedInput({style, ...props}) {
return <input style={{color: 'red', ...style}} {...props} />;
}
Here's a demo that shows this case.
This is widely accepted practice but the problem with it is that it's impossible to get ref of wrapped DOM element from stateless function, so the component uses React.forwardRef:
function withRef(SFC) {
return React.forwardRef((props, ref) => SFC({ref, ...props}));
// this won't work
// React.forwardRef((props, ref) => <SFC ref={ref} {...props } />);
}
const ThemedInput = withRef(ThirdPartyThemedInput);
This way it can be used as:
<ThemedInput ref={inputRef} />
...
inputRef.current.focus();
The obvious downside I'm aware of is that withRef requires a developer to be aware of wrapped component implementation, which isn't a usual requirement for HOCs.
Is it considered a proper approach in a situation like described above?
I don't think there's anything wrong with calling Stateless Functional Component directly. As you said it's even one tiny overhead eliminated. As to the possible implications, it would be bold to say that there are none implications and there will be none implications in the future because this is a really rare way of using SFC's. But everything points to conclusion that there shouldn't be any implications (it's just one function call less).
Anyway, below I'd like to present another way of doing this using findDOMNode instead of refs:
I've created Focus component that is really convenient to use but needs to be initialized first (since we need a way to trigger focus outside props since a component may be rerendered with the same props):
// focus.js
import React from "react";
import { findDOMNode } from "react-dom";
export default function createFocus() {
class Focus extends React.Component {
componentDidMount() {
Focus.now = () => {
findDOMNode(this).focus();
}
}
render() {
return this.props.children;
}
}
return Focus;
}
// index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import createFocus from './focus';
const Focus = createFocus();
import { ThirdPartyThemedInput } from './third-party-lib';
function App() {
return (
<div>
<button onClick={() => Focus.now()}>Proceed with form</button>
<Focus>
<ThirdPartyThemedInput placeholder="Fill me" />
</Focus>
</div>
);
}
render(<App />, document.getElementById('root'));
live at: https://stackblitz.com/edit/react-bpqicw
Functional components are very useful when you don't need to use any of the lifecycle method or don't need to update the component state. As far as you don't need to them, you're good and yet best to go with stateless component.
This will not hit the performance issue but gain the profit regarding its performance because we're just simply using function to render the component and not caring for its update, mounts, receive props, etc. But still there's no 100% gain using stateless component because react internally use class to render them.
It's about 45% improvement.
This post will also guide which one to choose between statefull component and stateless component.
Further, you can not only receive the props but can also receive the ref:
const stateless = (props, ref) => <ReturnComponent {...props} ref={ref} />
Okay, let me refine my statement. Most of the blogs and even the docs states that stateless component don't have ref. Here are a few Q/A prepared regarding this issue:
Do I need to use statefull component just to use ref?
No. I already mentioned that we must require the class based component if we have to work with component state or hook some lifecycle method.
How can I create ref in stateless component?
const stateless = () => {
// we can't do this.myRef = React.createRef()
// so, let's create an object
const RefObj = {}
// now, create ref in {RefObj}
RefObj.myRef = React.createRef()
return <input type="text" ref={myRef} />
}

ReactJS - Lifting state up vs keeping a local state

At my company we're migrating the front-end of a web application to ReactJS.
We are working with create-react-app (updated to v16), without Redux.
Now I'm stuck on a page which structure can be simplified by the following image:
The data displayed by the three components (SearchableList, SelectableList and Map) is retrieved with the same backend request in the componentDidMount() method of MainContainer. The result of this request is then stored in the state of MainContainer and has a structure more or less like this:
state.allData = {
left: {
data: [ ... ]
},
right: {
data: [ ... ],
pins: [ ... ]
}
}
LeftContainer receives as prop state.allData.left from MainContainer and passes props.left.data to SearchableList, once again as prop.
RightContainer receives as prop state.allData.right from MainContainer and passes props.right.data to SelectableList and props.right.pins to Map.
SelectableList displays a checkbox to allow actions on its items. Whenever an action occur on an item of SelectableList component it may have side effects on Map pins.
I've decided to store in the state of RightContainer a list that keeps all the ids of items displayed by SelectableList; this list is passed as props to both SelectableList and Map. Then I pass to SelectableList a callback, that whenever a selection is made updates the list of ids inside RightContainer; new props arrive in both SelectableList and Map, and so render() is called in both components.
It works fine and helps to keep everything that may happen to SelectableList and Map inside RightContainer, but I'm asking if this is correct for the lifting-state-up and single-source-of-truth concepts.
As feasible alternative I thought of adding a _selected property to each item in state.right.data in MainContainer and pass the select callback three levels down to SelectableList, handling all the possible actions in MainContainer. But as soon as a selection event occurs this will eventually force the loading of LeftContainer and RightContainer, introducing the need of implementing logics like shouldComponentUpdate() to avoid useless render() especially in LeftContainer.
Which is / could be the best solution to optimise this page from an architectural and performance point of view?
Below you have an extract of my components to help you understand the situation.
MainContainer.js
class MainContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
allData: {}
};
}
componentDidMount() {
fetch( ... )
.then((res) => {
this.setState({
allData: res
});
});
}
render() {
return (
<div className="main-container">
<LeftContainer left={state.allData.left} />
<RightContainer right={state.allData.right} />
</div>
);
}
}
export default MainContainer;
RightContainer.js
class RightContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedItems: [ ... ]
};
}
onDataSelection(e) {
const itemId = e.target.id;
// ... handle itemId and selectedItems ...
}
render() {
return (
<div className="main-container">
<SelectableList
data={props.right.data}
onDataSelection={e => this.onDataSelection(e)}
selectedItems={this.state.selectedItems}
/>
<Map
pins={props.right.pins}
selectedItems={this.state.selectedItems}
/>
</div>
);
}
}
export default RightContainer;
Thanks in advance!
As React docs state
Often, several components need to reflect the same changing data. We
recommend lifting the shared state up to their closest common
ancestor.
There should be a single “source of truth” for any data that changes
in a React application. Usually, the state is first added to the
component that needs it for rendering. Then, if other components also
need it, you can lift it up to their closest common ancestor. Instead
of trying to sync the state between different components, you should
rely on the top-down data flow.
Lifting state involves writing more “boilerplate” code than two-way
binding approaches, but as a benefit, it takes less work to find and
isolate bugs. Since any state “lives” in some component and that
component alone can change it, the surface area for bugs is greatly
reduced. Additionally, you can implement any custom logic to reject or
transform user input.
So essentially you need to lift those state up the tree that are being used up the Siblings component as well. So you first implementation where you store the selectedItems as a state in the RightContainer is completely justified and a good approach, since the parent doesn't need to know about and this data is being shared by the two child components of RightContainer and those two now have a single source of truth.
As per your question:
As feasible alternative I thought of adding a _selected property to
each item in state.right.data in MainContainer and pass the select
callback three levels down to SelectableList, handling all the
possible actions in MainContainer
I wouldn't agree that this is a better approach than the first one, since you MainContainer doesn't need to know the selectedItems or handler any of the updates. MainContainer isn't doing anything about those states and is just passing it down.
Consider to optimise on performance, you yourself talk about implementing a shouldComponentUpdate, but you can avoid that by creating your components by extending React.PureComponent which essentially implements the shouldComponentUpdate with a shallow comparison of state and props.
According to the docs:
If your React component’s render() function renders the same result
given the same props and state, you can use React.PureComponent for a
performance boost in some cases.
However if multiple deeply nested components are making use of the same data, it makes sense to make use of redux and store that data in the redux-state. In this way it is globally accessible to the entire App and can be shared between components that are not directly related.
For example consider the following case
const App = () => {
<Router>
<Route path="/" component={Home}/>
<Route path="/mypage" component={MyComp}/>
</Router>
}
Now here if both Home and MyComp want to access the same data. You could pass the data as props from App by calling them through render prop. However it would easily be done by connecting both of these components to Redux state using a connect function like
const mapStateToProps = (state) => {
return {
data: state.data
}
}
export connect(mapStateToProps)(Home);
and similarly for MyComp. Also its easy to configure actions for updating relevant informations
Also its particularly easy to configure Redux for your application and you would be able to store data related to the same things in the individual reducers. In this way you would be able to modularise your application data as well
My honest advice on this. From experience is:
Redux is simple. It's easy to understand and scale BUT you should use Redux for some specific use cases.
Since Redux encapsulates your App you can think of storing stuff like:
current app locale
current authenticated user
current token from somewhere
Stuff that you would need on a global scale. react-redux even allows for a #connect decorator on components. So like:
#connect(state => ({
locale: state.locale,
currentUser: state.currentUser
}))
class App extends React.Component
Those are all passed down as props and connect can be used anywhere on the App. Although I recommend just passing down the global props with the spread operator
<Navbar {...this.props} />
All other components (or "pages") inside your app can do their own encapsulated state. For example the Users page can do it's own thing.
class Users extends React.Component {
constructor(props) {
super(props);
this.state = {
loadingUsers: false,
users: [],
};
}
......
You would access locale and currentUser through props because they were passed down from the Container components.
This approach I've done it multiple times and it works.
But, since you wanted to really consolidate the knowledge of React first, before doing Redux you can just store your state on the top-level component and pass it down to the children.
Downsides:
You're gonna have to keep passing them down into inner level components
To update state from the inner level components you're gonna have to pass the function that updates the state.
These downsides are a little boring and cumbersome to manage. That's why Redux was built.
Hope I helped. good luck
By using Redux you can avoid such callbacks and maintain the whole state in one single store - so make your parent component connected component - and make left and right components dumb ones - and just pass in the props you get from parent to child - and you don't have to worry about callbacks in this case.

React router not reloading Component when changing url params

I know that it's not a default behaviour / feature of react-router to help us reload easily the current component but I really need this in my application.
My application deals with products. I have a product list that I can load, and when I click on an item, it displays the concerned product details.
On that page, I have related product links that load the same component, but with another product details, located at
<Route path="/products/:id/details" component={ProductDetail} />
I m fetching data in my componentWillMount, and it seems that if I only change the URL, a new component is NOT mounted, and so, I m always having my old data displayed, without fetching anything.
As a beginner using React, I'm looking for some help, or some tricks to reload the component concerned by that page. I mean being able to reload the ProductDetail with the good product.
I tried to look around with componentWillUpdate (a method in which I can see that the router URI changes :D) but I can't setState inside of it to make my component reload (it doesn't seem to be a good practice at all)
Any idea how can I make this work ?
EDIT : According to the first answer, I have to use onEnter. I m now stuck with the way of passing state/props to the concerned component :
const onEnterMethod = () => {
return fetch(URL)
.then(res => res.json())
.then(cmp => {
if (cmp.length === 1) {
// How to pass state / props to the next component ?
}
});
};
The way to handle it depends if you are using flux, redux or however you want to manage your actions. On top of it I would try to make use of onChange property of Route component (check React router docs):
<Route path="/products/:id/details" component={ProductDetail} onChange={someMethod} />
And then in the someMethod create the action if you are using redux or however is done in flux.
The redux would be:
<Route path="/products/:id/details" component={ProductDetail} onEnter={onEnterHandler(store)} />
And the onEnterHandler with the redux store:
function onEnterHandler(store) {
return (nextState, replace) => {
store.dispatch({
type: "CHANGEPRODUCT",
payload: nextState.params.id
})
};
}
And then in your ProductDetail component you would print the new information (It would require a bit more of learning in redux and redux-sagas libraries to complete that part).
Keep in mind that React is just the view part, trying to solve all those problems using only react is not only not recommended but also would mess up your code.

ReactRouter - props from handlers to header

I'm trying out React Router. Simplified example to explain my question (at the bottom of the post):
View1
var View1 = React.createClass({
statics: {
headerAction: function() {
this.handleSomething();
}
},
handleSomething: function() {
this.setState(...);
},
render: function () {
return (
<div>
(...)
</div>
);
}
});
Similar class for View2
App
var App = React.createClass({
render: function () {
return (
<div>
<AppHeader />
<RouteHandler/>
</div>
);
}
});
Router
var routes = (
<Route name="app" path="/" handler={App}>
<Route name="view2" handler={View2}/>
<DefaultRoute handler={View1}/>
</Route>
);
Router.run(routes, function (Handler) {
React.render(<Handler/>, document.body);
});
I want AppHeader to be in the App-class (not in the individual views), because the header/toolbar is always visible. If it where a part of the view class, the header would be re-mounted every time the router opened a different view. However, the props for that component can be different based on what route is active. Like a different title, and an icon with a clickable action causing something (state change) in the view.
I have tried to find out a good way to solve this, meaning different props for the AppHeader based on what view class is the active route handler. Since the view class is the component that varies, I thought about having it in the view components (View1/View2 in the example above) even if the props is not going to be used in that component. I have looked at having it as statics in theese component, then extracting them in Router.run, the problem with that is I cannot call event handlers in the same component (like in the View1 example), like clickable links for the header that is to do something in the view.
I tried to have a reflux store, and in componentWillUpdate of the view component, call some action on the store, that is listened to in the app component and sent as props to the header. However, for the first render, that action is called during the render process of the app component, causing the first header props not to be visible because they are not available yet.
Another approach I thought about was having one Reflux store per view (per router handler), and have the props for the header in the individual stores and pass them to the header in the App component. But then I need to switch between the stores to use, based on what route is active. I have not found any way to have individual stores per handler in addition to selecting the store with React Router.
What other option is there for a case like this?
Not sure if this helps but I solved a similar problem using <Link> from react-router.
render(){
let to_obj={pathname:category.url, query:{text:category.text}}
<Link to={to_obj} className="cat-link">
.....
Then at the destination, I update the navbar title via a ReactiveVar with
GlobalState.title.set(this.props.location.query.text)
I haven't tried it but you may even be able to pass function/handlers down.
Just a thought.

Categories

Resources