Search code examples
javascriptreactjsreact-router-domreact-component

How to pass data from a child component to a parent component in React.js


I want to pass a dynamic pdf file from BooksList component to Link component in react-router-dom so that when I click the BooksList component inside a Link a pdf file will be passed to Link, then Link will forward this pdf file to PDFViewer component where this pdf file will be rendered. If it is impossible can you please suggest ways I can use to achieve this functionality. The example of the code before any data is passed is as follows:

<Link to="/pdf_viewer">
    <BooksList></BooksList>
</Link>

<Route path="/pdf_viewer">
    <PDFViewer></PDFViewer>
</Route>

BooksList

export class BooksList extends Component {
    render() {
        return this.props.books.map((book)=> (
          //Here there is book info rendered from props
          //PDF file is an attribute in book eg. book.pdf_file
          <div>Title: {book.book_title}</div>
        ));
    }
}

export default BooksList

PDFViewer

export class PDFViewer extends Component {
    
    constructor(props){
        super(props);
        const { state: { pdfFile } } = this.props.location;
    }

    render() {
        return (
            <div key={book._id} className="pdfViewer">
                <embed src={ `data:application/pdf;base64, ${this.state}` } width="100%" height="600px"></embed>
            </div>
        )
    }

}

export default PDFViewer

UnLinkedBooksList

export class UnLinkedBooksList extends Component {
    viewPDFHandler = pdfFile => {
        const {history} = this.props;
        history.push({
            pathname: "/pdfviewer",
            state: {
                pdfFile
            }
        });
    }
    render() {
        return this.props.books.map((book)=> (
            <div>
                <button type="button" onClick={()=> this.viewPDFHandler(book.pdf_file_byte["$binary"])}>View PDF</button>
            </div>
        ));
    }
}

const BooksList = withRouter(UnLinkedBooksList);

export default UnLinkedBooksList

App

export class App extends Component {

  state = {
    books: []
  }

  componentDidMount(){
    axios.get("http://127.0.0.1:5000/get_all_books").then(res=>this.setState({books:res.data.all_books}))
  }
  render() {
    return (
      <Router>
        {/* Index Page */}
        <Route exact path="/">
          <div className="App">
            <SearchBar></SearchBar>
            <div className="row p-10">
              <div className="col-lg-6">
                <Link to="/pdf_viewer" className="text-decoration-none">
                  <BooksList books={this.state.books}></BooksList>
                </Link>
              </div>
            </div>        
          </div>
        </Route>

        {/* PDF Viewer Page */}
        <Route path="/pdf_viewer" className="text-decoration-none">
          <PDFViewer></PDFViewer>
        </Route>

       </Router>
    )
  }
}

export default App


Solution

  • Expose the history object from the routing context in BooksList and add a handler for each mapped book to pass the PDF file reference as a route transition payload. Since BooksList is a class-based component and isn't rendered by a Roiute (it probably should be, btw), use the withRouter Higher Order Component to decorate and inject route props, (history, location, match).

    class UnlinkedBooksList extends Component {
      viewPdfHandler = pdfFile => {
        const { history } = this.props;
        history.push({
          pathname: "/pdf_viewer",
          state: {
            pdfFile
          }
        });
      };
    
      render() {
        return this.props.books.map((book)=> (
          //Here there is book info rendered from props
          //PDF file is an attribute in book eg. book.pdf_file
          ...
          <button
            type="button"
            onClick={() => viewPdfHandler(book.pdf_file)}
          >
            View PDF
          </button>
        ));
      }
    }
    
    const BooksList = withRouter(UnlinkedBooksList);
    
    export default BooksList;
    

    In App, default import BooksList, this is the linked component that will have the location prop.

    import BooksList from './path/to/BooksList';
    

    On the receiving end you can extract the route state. Render PDFView on the component prop so it also receives the route props, specifically, location so it can access the route state.

    <Route path="/pdf_viewer" component={PDFViewer} />
    

    In PDFViewer, access pdfFile from the location object:

    const { state: { pdfFile } } = this.props.location;