Search code examples
reactjsreact-routercreate-react-appreact-router-dom

How to pass properties from parent to child in Redirect


I am learning ReactJS and now doing a small project for myself to manage my recipes.

I create my project using npx create-react-app <my-app>. I display a gallery of my recipe, each recipe has an image, name, description and duration of preparation. By clicking a recipe another page will open with the details of the recipe with a list of the ingredients and preparation steps. I have the gallery page running and when clicking a recipe the details page is opening, but it doesn't get the property of the recipe that I pass to it.

My application/project components: App.js

    import React from 'react';
    import logo from './logo.svg';
    import './App.css';
    import { Switch, Route } from 'react-router-dom'
    import HomePage from './pages/HomePage';
    import LoginPage from './pages/LoginPage';
    import RecipesPage from './pages/RecipesPage';
    import RecipeDetailsPage from './pages/RecipeDetailsPage';
    import PlannedDinnerPage from './pages/PlannedDinnerPage';
    import ShoppingListPage from './pages/ShoppingListPage';
    import jsonUsers from './data/users'
    import jsonKitchens from './data/Kitchens'
    import jsonDishTypes from './data/DishTypes'
    import jsonIngredients from './data/Ingredient'
    import jsonRecipes from './data/recipes'
    import jsonRecipeIngredients from './data/RecipeIngrediaent'
    import jsonRecipesPreperationSteps from './data/RecipePreperationStep'

    class App extends React.Component {

      constructor(props) {
         super(props);
         this.state = {
            recipeId: null,
            activeUser: null,
            allUsers: jsonUsers,
            allKitchens: jsonKitchens,
            allDishTypes: jsonDishTypes,
            allIngredients: jsonIngredients,
            allRecipes: jsonRecipes,
            allRecipesIngredients: jsonRecipeIngredients,
            allRecipesPreperationSteps: jsonRecipesPreperationSteps,
            activeUserRecipes: []
            // hack for starting with my recipes
            // activeUserRecipes: jsonRecipes.filter(recipe => recipe.userId === 1)
         }

         this.handleLogout = this.handleLogout.bind(this);
         this.handleLogin = this.handleLogin.bind(this);
         this.addRecipe = this.addRecipe.bind(this);

         console.log(this.state.allRecipes);
      }

      handleLogout() {
         this.setState({ activeUser: null });
      }

      handleLogin(activeUser) {
         const activeUserRecipes = this.state.allRecipes.filter(recipe => recipe.userId === activeUser.id)
         this.setState({ activeUser, activeUserRecipes });
      }

      addRecipe(newRecipe) {
         //const {activeUser, allRecipes, activeUserRecipes} this.state.activeUser
         // 1) add id and user to the recipe
         newRecipe.userId = this.state.activeUser.id;
         newRecipe.id = this.state.allRecipes[this.state.allRecipes.length - 1].id + 1;

         // 2) update all recipes and active user recipes
         const allRecipes = this.state.allRecipes.concat(newRecipe);
         const activeUserRecipes = this.state.activeUserRecipes.concat(newRecipe);

         this.setState({ allRecipes, activeUserRecipes });
      }

      render() {

         // const activeUser = this.state.activeUser;
         const { recipeId, activeUser, allUsers,
            allRecipes,
            allKitchens,
            allDishTypes,
            allIngredients,
            allRecipesIngredients,
            allRecipesPreperationSteps,
            activeUserRecipes } = this.state;

         return (
            <Switch>
              <Route exact path="/">
                 <HomePage activeUser={activeUser} handleLogout={this.handleLogout} />
              </Route>
              <Route path="/login">
                 <LoginPage users={allUsers} handleLogin={this.handleLogin} />
              </Route>
              <Route exact path="/recipes">
                 <RecipesPage activeUser={activeUser} addRecipe={this.addRecipe} allRecipes={allRecipes} handleLogout={this.handleLogout} userRecipes={activeUserRecipes} />
              </Route>
              <Route path="/recipes/:id">
                 <RecipeDetailsPage activeUser={activeUser} addRecipe={this.addRecipe} allRecipes={allRecipes} handleLogout={this.handleLogout} userRecipes={activeUserRecipes} />
              </Route>
              <Route path="/dinner">
                 <PlannedDinnerPage activeUser={activeUser} addRecipe={this.addRecipe} allRecipes={activeUserRecipes} handleLogout={this.handleLogout} />
              </Route>
              <Route path="/shopping">
                 <ShoppingListPage activeUser={activeUser} addRecipe={this.addRecipe} allRecipes={allRecipes} handleLogout={this.handleLogout} userRecipes={activeUserRecipes} />
              </Route>
            </Switch>
         );
      }
    }

    export default App;

RecipesPage.js import React from 'react' import RecipesNavbar from '../components/RecipesNavbar' import { Container, Row, Col, Button, Modal, Form } from 'react-bootstrap' import { Redirect } from 'react-router-dom' import RecipeCard from '../components/RecipeCard'

    class RecipesPage extends React.Component {
         constructor(props) {
              super(props);
              this.state = {
                    navigateToRecipeId: null,
                    showModal: false
              }

              this.openModal = this.openModal.bind(this);
              this.closeModal = this.closeModal.bind(this);
              this.createRecipe = this.createRecipe.bind(this);
              this.openRecipeDetails = this.openRecipeDetails.bind(this);

              this.nameInput = React.createRef();
              this.descInput = React.createRef();
              this.imgInput = React.createRef();
         }

         openRecipeDetails() {
              let navigateToRecipeId = this.props.recipeData.id;
              this.setState({ navigateToRecipeId });
         }

         openModal() {
              this.setState({ showModal: true })
         }

         closeModal() {
              this.setState({ showModal: false })
         }

         createRecipe() {
              const newRecipe = {
                    name: this.nameInput.current.value,
                    desc: this.descInput.current.value,
                    img: this.imgInput.current.value,
              }

              this.props.addRecipe(newRecipe);
              this.closeModal();
         }

         /*
              activeUser={activeUser} 
              addRecipe={this.addRecipe} 
              allRecipes={allRecipes} 
              handleLogout={this.handleLogout} 
              recipe={recipe} 
              recipeId={recipeId} 
              userRecipes={activeUserRecipes} 
          */
         render() {
              const { activeUser, handleLogout, allRecipes } = this.props;
              const { recipeId, userRecipes } = this.props;
              const { showModal } = this.state;

              if (!activeUser) {
                    return <Redirect to="/" />
              }

              const recipesCards = allRecipes.map(recipe =>
                    <Col key={recipe.id} lg="3" md="6">
                         <RecipeCard activeUser={activeUser} recipe={recipe} recipeData={recipe} />
                    </Col>
              );

              return (
                    <div>
                         <RecipesNavbar activeUser={activeUser} handleLogout={handleLogout} />
                         <Container>
                              <div className="recipes-header">
                                    <h1>{activeUser.fname}'s Recipes</h1>
                                    <Button variant="primary" onClick={this.openModal}>New Recipe</Button>
                              </div>
                              <Row>
                                    {recipesCards}
                              </Row>
                         </Container>


                         <Modal show={showModal} onHide={this.closeModal} size="lg">
                              <Modal.Header closeButton>
                                    <Modal.Title>New Recipe</Modal.Title>
                              </Modal.Header>
                              <Modal.Body>
                                    <Form>
                                         <Form.Group as={Row} controlId="formHorizontalEmail">
                                              <Form.Label column sm={2}>
                                                    Name
                                              </Form.Label>
                                              <Col sm={10}>
                                                    <Form.Control ref={this.nameInput} type="text" placeholder="Recipe name" />
                                              </Col>
                                         </Form.Group>

                                         <Form.Group as={Row} controlId="formHorizontalPassword">
                                              <Form.Label column sm={2}>
                                                    Description
                                              </Form.Label>
                                              <Col sm={10}>
                                                    <Form.Control ref={this.descInput} type="text" placeholder="Recipe description" />
                                              </Col>
                                         </Form.Group>

                                         <Form.Group as={Row} controlId="formHorizontalPassword">
                                              <Form.Label column sm={2}>
                                                    Image URL
                                              </Form.Label>
                                              <Col sm={10}>
                                                    <Form.Control ref={this.imgInput} type="text" placeholder="Recipe image URL" />
                                              </Col>
                                         </Form.Group>

                                    </Form>
                              </Modal.Body>
                              <Modal.Footer>
                                    <Button variant="secondary" onClick={this.closeModal}>
                                         Close
                                    </Button>
                                    <Button variant="primary" onClick={this.createRecipe}>
                                         Create Recipe
                                    </Button>
                              </Modal.Footer>
                         </Modal>

                    </div>
              );
         }
    }

    export default RecipesPage;

RecipeCard.js

    import React from 'react'
    import { Card } from 'react-bootstrap'
    import { Redirect } from 'react-router-dom'

    class RecipeCard extends React.Component {
         constructor(props) {
              super(props);
              this.state = {
                    navigateToRecipeId: null,
                    recipeId: null,
                    recipe: null,
                    recipeData: null,
              }
              this.openRecipeDetails = this.openRecipeDetails.bind(this);
         }

         openRecipeDetails() {
              console.log("openRecipeDetails - RecipeId: " + this.props.recipe.id);
              let { navigateToRecipeId, recipe, recipeData } = this.state;
              navigateToRecipeId = this.props.recipe.id;
              recipe = this.props.recipe;
              recipeData = this.props.recipe;
              // this.state.recipeDetails = from data files / database;
              this.setState({ navigateToRecipeId, recipeData });
         }

         render() {
              const { activeUser, recipe, activeUserRecipes, recipeData } = this.props;
              const { navigateToRecipeId } = this.state;
              /*
                    activeUser={activeUser} 
                    addRecipe={this.addRecipe}
                    allRecipes={allRecipes} 
                    handleLogout={this.handleLogout} 
                    recipe={recipe} 
                    recipeId={recipeId} 
                    userRecipes={activeUserRecipes} 
              */
              if (this.state.navigateToRecipeId != null) {
                    const { navigateToRecipeId } = this.state;
                    return (
                         <Redirect to={'/recipes/' + this.state.navigateToRecipeId} activeUser={activeUser} recipe={recipeData} recipeData={recipeData} recipeId={navigateToRecipeId} recipe={recipe} userRecipes={activeUserRecipes}  />
                    );
              } else {
                    return (
                         <Card className="recipe" >
                              <Card.Img onClick={this.openRecipeDetails} variant="top" src={recipeData.img} />
                              <Card.Body>
                                    <Card.Title>{recipeData.name}</Card.Title>
                                    <Card.Subtitle>{recipeData.desc}</Card.Subtitle>
                                    <Card.Text>Cooking Time: {recipeData.duration} min</Card.Text>
                              </Card.Body>
                         </Card>
                    );
              }
         }
    }

    export default RecipeCard;

RecipeDetailsPage.js

    import React from 'react'
    import { Jumbotron, Button, Container } from 'react-bootstrap'
    import RecipesNavbar from '../components/RecipesNavbar';

    class RecipeDetailsPage extends React.Component {
         constructor(props) {
              super(props);
              this.state = {
                    navigateToRecipeId: null,
                    recipeId: null,
                    recipe: null,
                    recipeData: null,
              }
         }
    /*
         activeUser={activeUser} 
         allRecipes={allRecipes} 
         userRecipes={activeUserRecipes} 
         recipeId={recipeId} 
         handleLogout={this.handleLogout} 
         addRecipe={this.addRecipe}
     */
         componentDidMount() {
              console.log("RecipeDetailsPage.componentDidMount() -- recipeId = ?");
              console.log("RecipeDetailsPage.componentDidMount() -- recipeId: " + this.props.recipe.id);
              let recipeId = this.props.match.params.id;
         }

         render() {
              const { activeUser, handleLogout } = this.props;

              return (
                    <div>
                         <RecipesNavbar activeUser={activeUser} handleLogout={handleLogout} />
                         <Jumbotron>
                              <Container>
                                    <h1 className="display-3">Show Details of Selected Recipe</h1>
                                    <div>Name: &lt; R e c i p e   N a m e &gt;</div>
                                    <div>Description:  &lt; R e c i p e   D e s c r i p t i o n &gt;</div>
                                    <div>Duration:  &lt; (nnn) min &gt;</div>
                                    <div>Kitchen:  &lt; K i t c h e n   i t   B e l o n g s   T o &gt;</div>
                                    <div>Type:  &lt; R e c i p e   T y p e &gt;</div>
                              </Container>
                         </Jumbotron>
                    </div>
              );
         }
    }

    export default RecipeDetailsPage;

In RecipeCard.js at the Redirect I see the values of the properties I want to send, but in RecipeDetailsPage.js in the constructor immediatlly after invoking super(props) I don't see the properties I sent in the redirect, only those I have in the App.js in Route of the RecipeDetailsPage component.

I am looking in google for a hint, but all I found thus far is about seding properties with Route (I am still searching).

What am I doing wrong? What am I missing? Is my practice correct or should I do it in a different way?


Solution

  • Thank you John Ruddell and Dennis Martinez, you gave me very useful tips which led me to the solution I needed.

    I don't use Redux in this project as it's the next thing for me to learn, in this exercise I want to practice react-router and react-router-dom. I use context API in another part of my application and the same goes for using the Link component.

    Here is the solution I found that works:

    App.js

        {/* Commenting the old code, followed by the code that works;
        <Route path="/recipes/:id">
            <RecipeDetailsPage activeUser={activeUser} addRecipe={this.addRecipe} allRecipes={allRecipes} handleLogout={this.handleLogout} userRecipes={activeUserRecipes} />
        </Route> 
        */}
        <Route path="/recipes/:id" component={RecipeDetailsPage} activeUser={activeUser} allRecipes={allRecipes} />
    

    When using <Route path=".." component={..}... /> in the RecipeDetailsPage I get the :id in this.props.match.params.id and can retrieve the rest of the details I need from the database using a REST API GET request. That's what I needed.