Reputation: 1030
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: < R e c i p e N a m e ></div>
<div>Description: < R e c i p e D e s c r i p t i o n ></div>
<div>Duration: < (nnn) min ></div>
<div>Kitchen: < K i t c h e n i t B e l o n g s T o ></div>
<div>Type: < R e c i p e T y p e ></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?
Upvotes: 1
Views: 243
Reputation: 1030
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.
Upvotes: 1