Reputation: 472
I have a table component (parent) and in each row of the table, there's another component that's basically an image button (child). When clicked, the image button switches from its default three vertical dots (https://png.icons8.com/windows/1600/menu-2.png) to a download icon. The three vertical dots image has an onClick listener that switches it to the download icon and the download icon has an onClick listener that downloads the file in the table row. Each image button has it's own state (based on which image to display). In the table component, I have a div that wraps the entire screen. When that div is clicked (so basically if you click anywhere outside the images), I want to be able to reset all the images in the table back to the three dots. I'm not sure how to accomplish this since there's many child components, each with it's own state (therefore I don't think redux would work). Is there a way to share the state of each image button with the table component?
Here's some of the table code
<div className='shield' onClick={() => this.resetDownloads()}></div>
<table className='table table-striped'>
<tbody>
this.props.songs.data.map((song, index) =>
<tr key={index}>
<td>{song.name}</td>
<td>{song.artist}</td>
<td>{song.duration}</td>
<td><ActionButton song={song}/></td>
</tr>
</tbody>
</table>
Here's some of the ActionButton component
class ActionButton extends React.Component {
constructor(props) {
super(props);
this.state = {
download: false
};
this.toggle = this.toggle.bind(this);
}
toggle(e) {
e.stopPropagation();
this.setState({
download: !this.state.download
});
}
render() {
return this.state.download ? (
<div>
<img src={Download} width='15%' onClick={(e) => this.props.downloadSong(this.props.song, e)}></img>
</div>
)
: (
<div>
<img src={Dots} width='15%' onClick={(e) => this.toggle(e)} className='image'></img>
</div>
)
}
}
Upvotes: 0
Views: 3295
Reputation: 17598
This is where you lift your state up. Actually, in the first place, you don't need a state in your ActionButton
component. It should be a stateless component. You can keep all your data in the parent component.
Let's assume there is an id
property in the song data. You can track a downloadState
in the parent component and add this song's id
to this state object. Then you can pass this value to your ActionComponent
and use it. Also, you can keep all your functions in your parent component.
const songs = [
{ id: "1", name: "Always Blue", artist: "Chet Baker", duration: "07:33" },
{ id: "2", name: "Feeling Good", artist: "Nina Simone", duration: "02:58" },
{ id: "3", name: "So What", artist: "Miles Davis", duration: "09:23" },
]
class App extends React.Component {
state = {
downloadState: {},
}
toggle = ( e, id ) => {
e.stopPropagation();
this.setState( prevState => ({
downloadState: { ...prevState.downloadState, [id]: !prevState.downloadState[id]}
}))
}
downloadSong = ( e, song ) => {
e.stopPropagation();
alert( song.name );
}
resetDownloads = () => this.setState({ downloadState: {}});
render() {
return (
<div onClick={this.resetDownloads}>
<table>
<tbody>
{
songs.map((song, index) => (
<tr key={index}>
<td>{song.name}</td>
<td>{song.artist}</td>
<td>{song.duration}</td>
<td>
<ActionButton
toggle={this.toggle}
song={song}
downloadState={this.state.downloadState}
downloadSong={this.downloadSong}
/>
</td>
</tr>
))
}
</tbody>
</table>
</div>
)
}
}
const ActionButton = props => {
const { downloadState, downloadSong, song, toggle } = props;
const handleToggle = e => toggle(e, song.id);
const handleDownload = e => downloadSong( e, song );
const renderImages = () => {
let images = "";
if ( downloadState[song.id] ) {
images = <p onClick={handleDownload}>Download</p>;
} else {
images = <p onClick={handleToggle}>Dots</p>;
}
return images;
}
return (
<div>{renderImages()}</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
table, th, td {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
If there isn't any id
property then you can set up the same logic with indexes but I think every data should have an id
:) Maybe instead of using indexes same logic can be used with song names since they are almost unique. Who knows :)
Upvotes: 2