I’m new to React and am not sure what I’m doing wrong here. I have a component called Blocks that contains an array of sub-components in state. Right now, when I add the sub-component Paragraph, I do so like this. This is in the parent component Blocks.
handleAddBlock(block) { let new_block = null; let last_block_id = this.state.last_block_id; last_block_id++; new_block = { component: <Paragraph key={last_block_id} id={last_block_id} />, id: last_block_id, value: null } this.setState({ last_block_id: last_block_id }); this.setState({ blocks: [...this.state.blocks, new_block] }); }
The Paragraph component has a state variable “value”, that is updated when a user types into a text box. However, when I go to remove an item from this.state.blocks, any components that come after the component I’m removing all get re-rendered, and lose their state. The components that come before the item I’ve removed keep theirs.The question is why, and how can I stop that from happening? Is this a bad design pattern?
Here’s the code that handles the removal of a sub-component. This is in the parent component Blocks.
handleRemoveBlock(id) { const blocks = [...this.state.blocks].filter(block => { return block.id !== id; }); this.setState({ blocks: blocks }); }
And finally, this is part of the render() method in the parent component Blocks.
render() { const blocks = this.state.blocks.map(block => { return <div key={block.key} className="col p-1" >{block.component} <button className="delete-button" onClick={() => this.handleRemoveBlock(block.id)} type="button">X </button> </div> }) return <section className="row"> <div className="col"> <div className="col"> {blocks} </div> </div> </section> }
Advertisement
Answer
I have a component called Blocks that contains an array of sub-components in state.
You shouldn’t. Components should contain as little data in their state as possible. The main React design concept is that component’s render method is a pure function of props and the state. Based on this, you should move <Paragraph/>
instances (because you should render components only in render
) and last_block_id
(because it’s computable from the blocks
state) from state
to render
:
class Block extends React.Component { handleAddBlock(block) { const new_block = { ... } this.setState('blocks', [...this.state.blocks, new_block]) } get last_block_id() { return this.state.blocks.at(-1).id } render() { // your markup return <...> // create Paragraph here {this.state.blocks.map(block => <Paragraph key={block.id} id={block.id} />) <.../> } }