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>
))}
<>
)
}
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 having a little bit of difficulty figuring out how to reuse components in many places.
Obviously the reason why react is so good is because you can create common components and reuse them across many different pages, or just multiple times in the same page.
I have this component for displaying a dropdown that a user can use to select a new value, and change their settings. This component is reused in many different places on my website.
<ChannelSelect
channelID={
saveEnabled.saveEnabled.newSettings.verificationPanel.channelID
}
settings={
saveEnabled.saveEnabled.newSettings.verificationPanel.channelID
}
/>
What I am struggling with is telling this component which key: value in the state (object) to change. This is because simply passing in the code below, doesn't actually allow me to change the state of this specific key.
settings={saveEnabled.saveEnabled.newSettings.verificationPanel.channelID}
In my select menus onChange event I have the code snippet below. However, this doesn't actually allow me to change the specific value declare, it would actually just overwrite the whole object.
const { saveEnabled, setSaveEnabled } = useContext(SaveContext);
const onChange = (event) => {
props.settings = "newValue"
setSaveEnabled(props.settings)
Any idea how I can do this?
I need to plot a table using data from header component ( 2 drop-downs & one Apply button), while header, table section & footer are unrelated to each other
I have tried to create an array in separate Utils file, which is populated when Apply button is hit, it is passed as
<Table data={utils.sortArray}/>
While data is populating sortArray when Apply button is hit in header component, it is showing length 0 Still
When Apply button is hit, new array data should get passed to table component
If you need the table to update based on input in the header the components aren't really unrelated (they are related through the data they have in common / are sharing).
To communicate between the two you will need a way to pass the data - the logical approach is to have the parent component coordinate between the two as it is contextually aware of both. In this case, you can:
Pass callback a prop to your header component that you call with the required data
Store the data sent in the callback in the parent's state
Pass the state data to your Table.
E.g., in your parent component:
state = {
sortArray: '', // What ever your default value is
}
onSort = (sortArray) => {
this.setState({
sortArray,
});
}
render() {
...
<Header onSort={this.onSort} />
...
<Table sortArray={this.state.sortArray} />
...
}
And then call onSort in your header with the required value as needed.
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/
const TrackerRow = (props) => {
const renderCampaignItems = props.group.map(
campaign => <CampaignRow key={campaign.tracker + '|' + campaign.token} name={campaign.tracker} token={campaign.token} />)
return (
<tr>
<td>{renderCampaignItems}</td>
<td><a onClick={props.onDeleteTracker}>Delete</a></td>
</tr>
)
}
I have a series of nested children in an app. The highest level component manages all the state everything else is stateless. Now in the component I've provided code for above I want to carry out a database operation with values that are currently in props.group and then remove the row from a list. Currently the onDeleteTracker is defined at the top level component and passed down the chain to the above component. I'm now considering how do I get the values of this component back to the top level component to tell it which items to delete. Should I just be performing the delete functionality within the TrackerRow component instead of passing the prop values back to the toplevel component as function parameters. Is there any reasoning which method to use here? I'm unclear of the pros and cons of either method (or alternatives.)
Definitely pass the instruction to 'delete item x' up to the component that manages state (or straight through to a store if you had one).
Actually I don't think there's even a choice. Remember that you can't mutate props, and that the data source is represented as state in the parent component.
But are you removing a 'group'? Or are you removing a 'campaign'? From your code, the property group appears to just be an array of campaigns (for what it's worth, I'd suggest always using the plural of the item for arrays, e.g. renaming 'group' to 'campaigns').
So I'll guess that you're actually wanting to remove a campaign, in which case you would call onDeleteTracker from inside the <CampaignRow/> component (when it's clicked or whatever). Which would look something like onClick={() => props.onDeleteTracker(props.id)}.