I have a react component that represents a document with text and some footnotes. The text should be rendered like this:
This the first footnote[1], this is the second[2].
Here is another [3].
As I'm rendering my component, I want to count up every time I see a footnote so that it's incremented. The tree can be many levels deep so you can't assume that all the footnotes are direct children of the main component.
This should also be dynamic, so that adding references updates the count.
I can't think of a very 'Reacty' way of doing this. Context (as frowned upon as it is) does not seem like the right thing, and otherwise, you have no information about neighboring components.
I think I would handle it like this...
In your container or top-level component, create an array for holding footnotes. Then pass this array down as a prop to any component that may render footnotes, and also to a footnote-rendering component which must be rendered after any of the other components.
const DocComponent = () => {
const footnotes = [];
return (
<div>
<SomeContent footnotes={footnotes} />
<SomeOtherContent footnotes={footnotes} />
<EvenDifferentContent footnotes={footnotes} />
<Footnotes footnotes={footnotes} />
</div>
);
};
Note that the footnotes array must be passed down the hierarchy via props to all components that could render a reference to a footnote. Every time a component renders a footnote reference, it adds a footnote to the array like so:
const SomeContent = ({footnotes}) => {
footnotes.push('This is the footnote text.');
const footnoteIndex = footnotes.length;
return (<p>Hermansen and Shihipar, et al [{footnoteIndex}]</p>);
};
When execution arrives to the Footnotes component, the same footnotes array instance will be passed via prop to it. At that point in execution, the array will be populated with all the footnotes that need to be displayed. And you can just render them in a straightforward way:
const Footnotes = ({footnotes}) => {
const inner = footnotes.map(
(footnote, index) => (<li>[{index+1}] {footnote}</li>) );
return (<ul>{inner}</ul>);
};
This implementation is definitely coupled to the rendering order of components. So the component order in your rendering should match the visual order you would want footnotes to appear in.
Here is a jsfiddle - https://jsfiddle.net/69z2wepo/79222/
Related
I have the following code. Based on selected option Switcher renders specific SwitcherForm.
const Switcher = ({ switcherType }: ISwitcher) => {
switch (switcherType) {
case Switchers.Dvd:
return <SwitcherForm {...SwitchersText[Switchers.Dvd]} />
case Switchers.Book:
return <SwitcherForm {...SwitchersText[Switchers.Book]} />
case Switchers.Furniture:
return (
<div className="switcher">
{SwitchersFurniture.map((item) => (
<SwitcherForm key={item.name} {...item} />
))}
<p className="switcher__descr">Please, provide dimensions.</p>
</div>
)
default:
return <></>
}
}
I checked through console.log in UseEffect of SwitcherForm that when case changes from Switchers.Dvd to Switchers.Book or vice versa, the components doesn't rerender and unmount, they even share the state. It doesn't apply to when case is Switchers.Furniture.
Only if I set different key attributes to SwitcherForm they start normaly rerender and unmount.
The primary way that react figures out whether it should create a new instance of the component or reuse the old one is by comparing the type of component from one render to the next. So if on render #1 you return a <SwitcherForm> and on render #2 you return a <div>, it sees that the types are different, and so it unmounts one and mounts the other.
But if you return a <SwitcherForm> both times, react sees that the component type is identical. Thus, react will keep the current one (including its internal state) and just changes its props. You may have created those on different lines of code, but react just sees what you returned, not that you did a switch statement, etc.
So for cases like this you can use keys, which are the secondary way that react figures out which element corresponds to which. If the key changes, then react knows it should treat them as different, even though the type is the same.
See this page in the documentation for a more thorough walkthrough: https://beta.reactjs.org/learn/preserving-and-resetting-state
I'm building a table with React and Material UI 5, but I have a problem with the input when I'm writing. The input loses the focus in each character digited.
How can I handle with component re-rendering?
The complete code: https://codesandbox.io/s/mytable-rn98b
You are defining new component types in the middle of rendering MyTable. Every time MyTable renders, you define a new TableToolbar, new HeaderList, new RowList, new CellList, new CellEdit. These functions may have the same text as the previous ones, but they do not pass a reference equality check (===) with their previous incarnations, so they are different types of components to react. Since they are different types, react must unmount the old ones and mount the new ones, which wipes out their state and focus.
If you want these to be their own components, they need to be defined just once, outside of the main component. This will require a bit of rewriting, since you can no longer rely on closure variables to access MyTable's data. Instead, pass the needed data as props.
For example, CellList should change from this:
const CellList = ({ row }) =>
headers.map((header) => (
<TableCell key={header.prop}>{row[header.prop]}</TableCell>
));
To this (and make sure to move it outside of MyTable):
const CellList = ({ row , headers }) => {
return (
<>
{headers.map((header) => (
<TableCell key={header.prop}>{row[header.prop]}</TableCell>
))}
<>
)
}
If, as a thought experiment, I were to write my own createElement implementation for JSX, what might support for implicit context look like?
In particular, I can't figure out how with the limited means of JSX's createElement signature, contexts can be independent for different subtrees. (It appears React's Context handling has become more elaborate in recent versions; I'm mostly interested in the seemingly more straightforward mechanisms of earlier versions.)
This might be used to automatically determine heading levels, for example:
<Section title="Hello World">
<Card title="Details" />
</Section>
<Card title="Example" />
Here Card would automatically generate <h3> and <h2>, respectively, by relying on something like context.headingLevel.
A very nice question, that shows how different is the concept of creating React Elements to actually executing the render functions (either the .render method of class components or simply the main body of a functional component).
In JSX itself (which is just React.createElement(…)) there‘s no concept of “context” at all. It comes into existance only when the components are rendered. It is indeed a duty of the React Renderer (such as React DOM or React Native) to actually implement Context APIs.
If you remove the ability to store states and to update the UI you are left with a minimal React implementation that only “renders once”, but perfectly fine to understand the problem at hand.
Everytime the React Renderer needs to render a React Elements tree (such as one built with JSX) it passes every single element and transforms it into a DOM structure, but when it encounters a component node (not a “native” element) it needs to render it to obtain its React Element sub tree, and swap the original node with it.
It’s in this specific moment that React can keep track of which Context values to pass to which components, since it is traversing the tree.
So, to answer directly your question, you can’t implement context in the “element creation phase”, inside your JSX implementation, you need to do it in a subsequent phase when you can traverse the tree.
If you were trying to build an “immediate JSX” you probably have something like this:
function createElement(type, props, ...children) {
props = { children, ...props };
if (typeof type === 'function') {
return type(props);
} else {
return { type, props };
}
}
In thise case you will not be able to implement an API similar to context, because the execution order is inner-then-outer:
const div = createElement('div', {}, createElement(Card, {}));
// identical to
const card = createElement(Card, {}); // inner, and then…
const div = createElement('div', {}, card); // outer
So this is some React Native code from a textbook that I'm going through, specifically it is from the render method of App.js. Of course the /* ...*/ would be filled in with actual code but it's irrelevant to my question.
<MeasureLayout>
{layout => (
<KeyboardState layout={layout}>
{keyboardInfo => /* … */}
</KeyboardState>
)}
</MeasureLayout>
What I don't understand is what is happening with {layout => (.... So I take it that layout is an arrow function that returns this keyboardState component. So how does layout then pass itself into keyboardState's layout prop at this part <KeyboardState layout={layout}>? And why would I want to do that exactly? This whole part here is really baffling me.
React components have props and children properties. The children property is usually a React node, but it can also be a function that returns a React node.
So how does layout then pass itself into keyboardState's layout prop at this part ?
The MeasureLayout component was created so that its children property was defined as a function instead of a React node.
And why would I want to do that exactly?
For dependency injection and as a pattern that allows for a more declarative style of programming with class-based components.
Some more in depth reading:
Topic: Functions as children
https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9
https://codedaily.io/tutorials/6/Using-Functions-as-Children-and-Render-Props-in-React-Components
See that {} inside render method is used for some javascript statements.
For eg.
<Text>
{personFirstNam +" " +personLastName}
</Text>
But now that in your code there is again JSX elements inside {}, it is used inside unnamed function.
i.e.
{layout => (
...// here you can use JSX element which will be returned into render method for UI.
)}
alternatively, if you want some operations there,
{layout =>{
let extractData = fromSomeWhere;
let calculatePosition = getPosition();
return (<KeyboardState layout={layout}>
{keyboardInfo => /* … */}
</KeyboardState>)
}}
All of these was to just do some JS statement executions/operations inside one JSX element.
The <MeasureLayout> is passing an argument to its children as a function. and to recieve it an arrow function is used.
so, basically the code of <MeasureLayout> will be,
function MesauseLayout(props){
//Do things
// layout = some result.
return <div>{props.children(layout)}</div>
}
So, in order to receive this the child will have to be inside a function that accepts this value. So, an arrow function is used to receive this value.
<MeasureLayout>
{layout => (
<KeyboardState layout={layout}>
{keyboardInfo => /* … */}
</KeyboardState>
)}
</MeasureLayout>
But in my opinion, using a Context/Provider with a hook will be a better option if that is possible. This is generally only used in extreme cases. There is also another option to use React.cloneElement and passing additional props. But there are tradeoffs if you have to choose between these two. Plus, There is a concept called render props which is commonly used in new libraries.
I am using redux with react and typescript for my application. I am working with many items used at different places of my app. My state looks like this:
{
items: {42: {}, 53: {}, ... }, //A large dictionary of items
itemPage1: {
itemsId: [ 42, 34, 4 ],
...
},
itemPage2: { ...
},
...
}
The user can modify some attributes of the items dispatching some actions. When this happen I need to redraw the components that have been modified in each pages. The issue is that my items are quite big and I cant afford to redraw all of them at each small modification. I was wondering is this approach would work:
I have a fist component <ItemPage1> which connects to the store to get all of the states stored in the tree under itemPage1 e.g. the list of items id: itemsId.
Inside <ItemPage1>, I loop over the itemsId property to generate multiple FilterItem components: itemsId.map( itemId => return <FilterItem id=itemId>);
Finally each Item is connected using ownProps to get the correct part of the state:
const mapStateToItemProps = (state, ownProps) => {
return {
item: state.items[ownProps.id],
}
}
const mapDispatchToItemProps = (dispatch, ownProps) => {
return null;
}
const FilterItem = connect(
mapStateToItemProps,
mapDispatchToItemProps
)(Item)
Can you confirm or refute that if I update the item of id 42, then only this item is going to be re-rendered ?
When rendering big list you need to take into considerations few things :
Lower the total number of DOM elements that you need to render (by not rendering items that are not actually visible on the screen, also known as virtualization)
Don't re-render items that have not changed
Basically, what you want to avoid is a complete re-render of your list (or your page) when the user edits one single row. This can be achieved exactly how you did it, i.e : by passing to the list container only the ids of items that need to be rendered, and to map over these ids to connect each component by using ownProps. If you have a dump <Item/> component, your <ItemPage/> component will create connected connect(<Item/>) component.
This is going to work, if your put a console.log('item rendered') in your <Item/> component class you will notice that there is only one call.
BUT (and it's a big but), what is not obvious when working with react-redux is that all connected components that depends on their ownProps will always rerender if any part of the state change. In your case, even if the <Item/> components will not re-render, their wrapped component connect(Item) will ! If you have few dozens of items, you might encounter some latency if actions need to be dispatched quickly (for example when typing in an input). How to avoid that ? Use a factory function to use ownProps as the initial props :
const mapStateToItemProps = (_, initialProps) => (state) => {
return {
item: state.items[initialProps.id], // we're not relying on the second parameters "ownProps" here, so the wrapper component will not rerender
}
}
const mapDispatchToItemProps = (dispatch, ownProps) => {
return null;
}
const FilterItem = connect(
mapStateToItemProps,
mapDispatchToItemProps
)(Item)
I suggest you to take a look to this other answer.
You might also be interested in these excellent slides : Big List High Performance React & Redux
And finally, you should definitively take a look to react-virtualized to perform the virtualization of your list (i.e, displaying only the item that the user can actually see).
Ok, I've found this discussion: https://github.com/reactjs/redux/issues/1303
At the bottom it is clearly stated (from multiple protagonists):
[...] react-redux takes care of this. It lets you specify specific parts of the state you care about, and takes care to bail out of updating React components when the relevant parts have not changed.
[...] Just wanted to fully understand that what's going on under the hood here, So if the Redux store gets updated but one specific component state hasn't changed, Redux won't trigger the forceUpdate() method for that component? [...]
The wrapper component generated by React-Redux's connect() function does a several checks to try to minimize the number of times your actual component has to re-render. This includes a default implementation of shouldComponentUpdate, and doing shallow equality checks on the props going into your component (including what's returned from mapStateToProps). So yes, as a general rule a connected component will only re-render when the values it's extracting from state have changed.
So I believe my implementation is good, it won't re-render all the items since only one item will see its properties modified.