问题
I have a container component which renders two components inside it. Depending on some props in the container, the top component can change, but the lower component will always remain the same.
I want to render some additional content (DOM elements/other react components) inside the lower component, but the additional content is created inside the top component. I'm struggling to figure out what I need to refactor here to make this work. I initially tried passing the created content up to the container, and then back down to the lower component. I soon realised this doesn't work because the created content relies on the props/state of the component that created it.
Creating the content inside the container isn't feasible because there are so many different options for what content can be created. The codebase I'm working on has 30-40 possible components which all generate different content.
I'm fairly new to React so I'm having difficulty trying to solve this the react way. I've read briefly about render props/HOCs, maybe thats what I need here? Any help would be greatly appreciated.
Here is a very basic example of what it looks like: https://codesandbox.io/s/zqo2p1yy9m
回答1:
There are a couple of ways to approach this issue
1) You can use state management system such as Redux, mobx or you can use React's context API https://reactjs.org/docs/context.html. HOWEVER, since you are fairly new to React, I would suggest not tackle with these right know till you are comfortable with the basic flow
2) You can implement a simple parent-child relationship
to pass data between components. Eventually, this allow you to provide a communication between adjacent(sibling) components. As it's already stated, that flow is generally called lifting up the state. Actually, state management systems works in a similar way. Let me explain this logic by also taking your scenario based code sample into account.
We'll have a Container component. This component will be a stateful component. The sole purpose of this Container component is having it's own holistic state, that will be treated as the source of truth by its children. Having a couple of methods to define ways to update this holistic state. The children components will be pure components, or representational components. That means, they will not have their own states. They will either be used to show data that their parent passes to them or trigger methods that are defined by their parent in order to update source of truth state.
We'll have two scenarios: In scenario 1, the list of content will be represented as it is. In scenario 2, the list of content will be represented by reversing the order of letters
class Container extends React.PureComponent {
state = {
reversed: false,
newContent: "",
contents: [
{
id: 1,
text: "Initial Content"
}
]
};
handleReverse = () => {
this.setState((state) => ({
...state,
reversed: !state.reversed
}))
}
submitNewContent = () => {
this.setState(state => ({
...state,
contents: [
...state.contents,
{ id: Math.random(), text: state.newContent }
],
newContent: ""
}));
};
addNewContent = content => {
this.setState({ newContent: content });
};
render() {
return (
<React.Fragment>
<ContentCreator
value={this.state.newContent}
handleChange={this.addNewContent}
handleClick={this.submitNewContent}
/>
{this.state.reversed ? <ReversedContentDisplayer contents={this.state.contents} /> : <ContentDisplayer contents={this.state.contents} />}
<Reversifier reversed={this.state.reversed} handleReverse={this.handleReverse}/>
</React.Fragment>
);
}
}
As you can see, the container only has a state, some methods to update the state and child component. Not a single html used inside of its render method.
Our first child component is ContentCreator
function ContentCreator({
handleChange,
handleClick,
value
}) {
return (
<div className="App">
<label> New Content</label>
<input type="text" value={value} onChange={event => handleChange(event.target.value)} />
<button onClick={handleClick}>Add Content</button>
</div>
);
}
This component acts like a form (I'm to lazy to actually wrap it with form and onSubmit function). And the main purpose of this component is to update the contents
key of our source of truth state
The second component is called Reversifier
(I don't believe there is such a word though)
function Reversifier({reversed, handleReverse}) {
return <button onClick={handleReverse}>{reversed ? 'Back to normal' : 'Reversify'}</button>
}
The main purpose of this component is to toggle reversed
key of our source of truth state. You can also see that, based on the value of reversed
the text inside of the button also changes (reflects sort of different scenarios in a different way)
And our last two components are
function ContentCreator({
handleChange,
handleClick,
value
}) {
return (
<div className="App">
<label> New Content</label>
<input type="text" value={value} onChange={event => handleChange(event.target.value)} />
<button onClick={handleClick}>Add Content</button>
</div>
);
}
function ReversedContentDisplayer({ contents }) {
return (
<div className="App">
{contents.map(content => (
<div key={content.id}>{content.text.split("").reverse().join("")}</div>
))}
</div>
);
}
Based on the scenario, that is whether display content as regular or reversed, inside or our container component's render method, we display either of them. Actually, these two components can be written as one such as
function ContentDisplayer({ contents, reversed }) {
return (
<div className="App">
{contents.map(content => (
<div key={content.id}>{reversed ? content.text.split("").reverse().join("") : content.text}</div>
))}
</div>
);
}
But in order to reflect different scenarios, I created two distinct components
I created a codesandbox for you in case you want to play with the functionality yo see the end result https://codesandbox.io/s/04rowq150
So by this way, you don't have to create any content inside of the Container
, rather you can use Container
as a guideline by defining initial state and a couple of methods to pass its children to allow them creating new contents, and displaying these newly created contents on behalf of the Container
I hope this sample helps you as a starting point and I hope that I understand your requirement at least 51% right. If you have further questions or confusions, please let me know
回答2:
The nature of react is top/down. So if you need to share a portion of state or some data to pass as props, you need to lift up that code to its parent. It is fine to pass Jsx as props, here you need to do that and you can.
More info: lifting state
——————
A
/ \
B C
So if you have shared code between B and C you lift it to A and pass it to B and C as props. If you need state from B at C, you lift the state to A. If you need to access and modify the state from A at B or C, you just pass a function as a callback to B or C
回答3:
I agree with victor.ja but I was wondering if you have a global store like redux. You can then just call the action to update the state of the store from the top component. The Higher Order Component can receive the state of the store as a prop and update the lower component.
来源:https://stackoverflow.com/questions/54830400/sharing-content-between-adjacent-components-hoc