Ok, working on my second ever React app. In this project, I am trying to use nested components (my last one just had 1 component). I've got a Button component that I am trying to render within the main App component, which is a calculator (Here is my design I am replicating in node/react locally: https://codepen.io/ryanmdoyle/pen/QBvjdw)
So far in my code, the only button I am actually implementing so far is the AC button. Everything else is copy/paste from my codepen design. I know some aspects like the CSS classes and values are getting passed around correctly because it displays the CSS properly, but I am trying to take the value of the Button component and use that within methods in the App component. Particularly in the handleInput method. I know with an input field you can get the event.target.value but that's obviously not working!
import React, { Component } from 'react';
// import './App.css';
const Button = (props) => <button id={props.id} className={props.class} value={props.value} onClick={this.handleInput}>{props.value}</button>
class App extends Component {
constructor(props) {
super(props);
this.state = {
resultDisplay: "",
entryDisplay: "",
}
this.handleInput = this.handleInput.bind(this);
}
handleInput(event) {
console.log(event.target.value);
this.setState({
entryDisplay: this.state.entryDisplay + event.target.value
})
}
render() {
return (
<div className="grid-container">
<div className="display">display</div>
<Button id="clear" class={`button button-secondary`} value="ac" />
<div id="plus-min" className="button button-secondary">+/-</div>
<div id="percent" className="button button-secondary">%</div>
<Button id="one" class="button button-primary" value={1} />
<div id="two" className="button button-primary">2</div>
<div id="three" className="button button-primary">3</div>
<div id="four" className="button button-primary">4</div>
<div id="five" className="button button-primary">5</div>
<div id="six" className="button button-primary">6</div>
<div id="seven" className="button button-primary">7</div>
<div id="eight" className="button button-primary">8</div>
<div id="nine" className="button button-primary">9</div>
<div id="zero" className="button-primary">0</div>
<div id="divide" className="button button-operator">/</div>
<div id="multiply" className="button button-operator">*</div>
<div id="subtract" className="button button-operator">-</div>
<div id="add" className="button button-operator">+</div>
<div id="decimal" className="button button-primary">.</div>
<div id="equals" className="button button-operator">=</div>
</div>
);
}
}
export default App;
const Button = (props) => <button id={props.id} className={props.class} value={props.value} onClick={this.handleInput}>{props.value}</button>
You are trying to add a function that is not local to Button.
For using it you need to pass the function like this
<Button id="clear" class={`button button-secondary`} value="ac" click={this.handleInput}/>
And when you try to use the function in Button use it like this
const Button = (props) => <button id={props.id} className={props.class} value={props.value} onClick={() => props.click()}>{props.value}</button>
One thing to remember this is null in a arrow function by default.
You must know that when ever you are trying to create a component where stateless or stateful doesnot matter its function set, variable must be separate. You cannot use it like you have tried in this case.
class in React is just like classes in other programming language almost.
Currently, in your Button component:
const Button = (props) => <button id={props.id} className={props.class} value={props.value} onClick={this.handleInput}>{props.value}</button>
this.handleInput isn't defined. It is defined in your App component but not in Button. If you try clicking the Button, you will get an error in the console.
What you're looking to do is pass a callback to your Button component from the App component via props:
In App:
<Button handleInput={this.handleInput} />
In Button:
<button onClick={props.handleInput}
Replace these line in your code
Button Component
const Button = (props) => <button id={props.id} className={props.class} value={props.value} onClick={props.onClick}>{props.value}</button>
Pass props in all button Component like this
<Button id="clear" class='button button-secondary' value="ac" onClick={this.handleInput}/>
Remember that you can pass entire functions as props to your components, which is the beauty of JavaScript and React! So, essentially, write the function to clear your screen in your main App component, and then pass a reference down to your button component. Attach the function to the onClick event for your button component.
// App.js
...
clearScreen() => this.setState({ resultDisplay: '', entryDisplay: ''})
...
<Button id="clear" class={`button button-secondary`} value="ac" onClick={clearScreen.bind(this)} />
// ButtonComponent.js
...
<button onClick={this.props.onClick} />
...
You will need to pass a function as a prop down to your child component, which will then allow you to alter the parent's functionality.
For example:
passFunction(value) {
// Modify passed parameter (value) from child and implement in parent
this.setState({value: value});
// or call that method that you want to call.
handleInput(value);
}
<Button passFunction={this.passFunction} ... />
Then in Button.js or wherever the child component is:
this.prop.passfunction(someValueToParent)
Related
I'm using useState in Next.js to render different components based on which button the user clicks. Here's the main page's (called account.js in the app) code:
//getting components and importing react
import react from 'react'
const account = () => {
const [active, setActive] = react.useState("general")
return (
<>
<div className="container my-5">
<div className="container text-center mb-3">
<button onClick={() => setActive("general")} className="btn btn-primary btn-lg mx-3">General Information</button>
<button onClick={() => setActive("education")} className="btn btn-primary btn-lg mx-3">Education History</button>
<button onClick={() => setActive("tests")} className="btn btn-primary btn-lg mx-3">Test Scores</button>
<button onClick={() => setActive("background")} className="btn btn-primary btn-lg mx-3">Background information</button>
<button onClick={() => setActive("documents")} className="btn btn-primary btn-lg mx-3">Upload documents</button>
</div>
</div>
<div className="container my-5">
{
active === "zero" && <p className='text-center'>Select a field to start</p>
}
{
active === "general" && <General></General>
}
{
active === "education" && <Education></Education>
}
{
active === "tests" && <Tests></Tests>
}
{
active === "background" && <Background></Background>
}
{
active === "documents" && <Documents></Documents>
}
</div>
</>
)
}
export default account
Now one of these components, namely Education.js also has a button that when clicked, will show a different component, AddSchool.js. The problem is, when I use useState in Education.js, while it does show the component I want it to when the button is clicked, it redirects back to the first page (account.js) where I used useState.
Code for Education.js:
import AddSchool from './AddSchool.js'
import react from 'react'
const Education = () => {
const [schoolerActive, schoolActive] = react.useState("noSchool")
return (
<form action="">
{/* Some stuff here */}
<div>
<button onClick={() => schoolActive("schooler")} className='btn btn-warning'>Add Schools +</button>
{
schoolerActive === "noSchool" && <></>
}
{
schoolerActive === "schooler" && <AddSchool />
}
</div>
</form>
)
}
export default Education
I'm expecting that when I click the 'Add Schools +' button, it just renders the AddSchool.js component. What it does instead is renders the AddSchool.js component exactly like I want but then redirects to account.js for some reason.
Can someone point out what I'm getting wrong here?
A button without a type property in a form automatically submits the form causing the location of the page to change.
As the other answer mentioned, you need to add a type attribute to specify that a button is a button inside a form.
In HTML, if you put a button inside a form and there's no <input type="button" />, then that will automatically act as the submit button.
To fix this issue simply add type="button" to your button element; that way it won't submit the form unexpectedly.
<button type="submit" onClick={() => {...}}>...</button>
I am working on using modals to accept form inputs on a react project
here is the plan component
plan/index.js
import React, { useState } from "react";
import Pay from '../Pay';
const Plan = () => {
const [payModal, setPayModal] = useState(false);
const [planMode, setPlanMode] = useState(true);
return (
<main class="main">
{payModal && <Pay setOpenPayModal={setPayModal} />}
<div class="plan">
<div>
<a class="plans" onClick={() => {setPayModal(true);}}>plan A</a>
<div class="plan">
<span class="plan">{!planMode ? "$19.99" : "$9.99"}</span>
<span class="plan">{!planMode ? "month" : "year"}</span>
</div>
</div>
<div>
<a class="plans" onClick={() => {setPayModal(true);}}>plan B</a>
<div class="plan">
<span class="plan">{!planMode ? "$29.99" : "$19.99"}</span>
<span class="plan">{!planMode ? "month" : "year"}</span>
</div>
</div>
</div>
</main>
);
};
export default Plan;
as you can see on the line {payModal && <Pay setOpenPayModal={setPayModal} />} where i call the pay modal component from the plan component and it opens up the modal
here is the pay component which is the modal component
pay/index.js
import React, { useState } from "react";
function Pay({ setOpenPayModal }) {
const [billingDetails, setBillingDetails] = useState({
price: "",
: "",
});
return (
<div class="modal">
<div class="modal">
<div class="close">
<button
onClick={() => {
setOpenPayModal(false);
}}
>
</button>
</div>
<div class="modal">
<form class="form" onSubmit={handleSubmit}>
<fieldset class="form">
<Field
label="Price"
id="price"
type="text"
value={billingDetails.price}
onChange={(e) => {
setBillingDetails({ ...billingDetails, price: e.target.value });
}}
/>
<Field
label="Frequency"
id="frequency"
type="text"
value={billingDetails.frequency}
onChange={(e) => {
setBillingDetails({ ...billingDetails, frequency: e.target.value });
}}
/>
</fieldset>
<button class="pay" onClick={handleSubmitPay}>
Pay
</button>
</form>
</div>
</div>
</div>
);
}
export default Pay;
The issue is I want to be able to pass the values price and frequency from the plan component to the pay modal component
for example for plan A is price="$19.99" and frequency="year", so based on the button clicked on the plan component page, those values get passed to the pay modal component in a dynamic way
how do I achieve this using react hooks?
You can use contexts to pass, but in this case I don't think it's the best option. What I really recommend is passing the state variable through the props.
{payModal && <Pay setOpenPayModal={setPayModal} price={price} frequency={frequency} />}
I usually use the Context (useContext) when I need values and various components, for example:
I need to save the user id that is logged in to various components, and instead of getting it from the server every time I need it, I keep it saved in my context that I created, so I can make the call only once.
Documentation-> https://pt-br.reactjs.org/docs/context.html
Long story short; I have a button that involves a function and in that function I want to change the state (in this case a number) from another component that's taking the property from somewhere else.
So to me I made a stats component that takes in the value that I want to change and then the value is coming from the Table component and I want the button that I made that is coming from the Actions
component to change the state from the stats component. So instead of having the button in the Table Component (As seen below) I want to be able to run it from my Actions Component
Attached is the code that kinda works the way I want it but it doesn't look the way I want it (I want to invokve the change state function from the Actions column rather than inside the component where the state lives)
const Button = ({ handleClick, action, btnType }) => {
return (
<div>
<button class={btnType} onClick={handleClick}>
{action}
</button>
</div>
);
};
const Actions = ({ text, value, handleClick }) => {
return (
<td>
<button
type="button"
class="btn btn-primary"
data-toggle="modal"
id="exampleModal"
>
Launch demo modal
</button>
<Button btnType="btn btn-secondary" action="Attack" />{" "}
<Button btnType="btn btn-success" action="Travel" />{" "}
</td>
);
};
const Stats = ({ cash }) => {
return (
<td>
<ul>
<li>
{" "}
<span class="green">Cash: </span>
{cash}
</li>
<li>
<span class="red">HP: </span>100
</li>
<li>
<span class="blue">Energy: </span>100
</li>
</ul>
</td>
);
};
const Table = () => {
const [cash, setCash] = useState(300);
const sellAction = ({}) => {
setCash(cash - 1);
};
return (
<table>
<tr>
<th>Drugs</th>
<th>Quantity</th>
<th>Inventory</th>
<th>Stats</th>
<th>Actions</th>
</tr>
<TableItem />
<QuantItem />
<Inventory />
<Button
btnType="btn btn-danger"
handleClick={sellAction}
action="Sell"
/>{" "}
<Stats cash={cash} />
<Actions />
</table>
);
};
Pass setCash or sellAction function to Actions component as a prop. And then from Actions component, pass it to the Button component as a prop. Similar to what you did for cash.
I've got three forms - sign in, forgot password and sign up. Initial render is for a sign in component which has buttons for forget password or sign up. On click the buttons render their each respective component.
Where I am right now is that I have state assigned to each child component I want rendered and buttons changing the state in the parent component . I want to move these buttons to the child components so that I can change the state from there
Below is the sign in page and two of the three components I've mentioned.
export default function SigninPage() {
const [comp, setView] = useState('signin')
function toggleSignin(comp){
setView('signin')
}
function toggleReset(comp){
setView('reset')
}
function toggleSignup(comp){
setView('signup')
}
return (
<LoginStyles>
{comp ==='signin' &&(
<div>
<SignIn />
</div>
)
}
{comp ==='reset' &&(
<div><RequestReset/></div>
)
}
{comp ==='signup' &&(
<div><SignUp/></div>
)
}
<button onClick={toggleSignin}>Sign In</button>
<button onClick={toggleReset}>Forgot Password</button>
<button onClick={toggleSignup}>Sign Up</button>
</div>
);
}
export default function SignIn() {
return (
<Form>
<h2>Sign Into Your Account</h2>
<button type="submit">Sign In</button>
{/* onClick sets state to 'signup'*/}
<a>Create a New Account</a>
<a>Forgot Password</a>
</Form>
);
}
export default function RequestReset() {
return (
<Form >
{/* onClick sets state to 'signin'*/}
<a href='/signin'>**Go back**</a>
<h2>Request a Password Reset</h2>
<fieldset>
<label htmlFor='email'>
Email
</label>
<button type='submit'>Request Reset</button>
</fieldset>
</Form>
)
}
First off, I believe you should implement this behavior with routing, that approach you are using is definitely messy and hard to follow.
That said, I think the follow will do the trick:
<LoginStyles>
{items.map(({ id, component, isVisible }) => (
const Component = component;
<div key={id} hidden={!isVisible}>
<Component onClickComponent={() => handleClick(id)}
</div>
))}
</LoginStyles>
Of course you need to call onClickComponent later on in the childs, but you will have scope and keep the id.
** This may be a simple answer, I'm new to React, thank you for any help ! **
Back story
I have a modal(bootstrap4) hidden inside the main app with a form inside that, when it rendered, the form is filled out based on the information from the selected recipe (more on that later.)
The recipes are stored locally and with this.state.currentRecipe I know which recipe is being selected (it is used as the index and is set to 0 by default.)
So, using this.state.currentRecipe as the index the first render naturally puts in the first recipe's information.
I attempted to solve this by making a function and passing it down the child components. The recipe-card has all the information and the edit button inside of it. So when the recipe-cards are all rendered by .map() I pass in their index and the function that was passed down in order to change the state of this.state.currentRecipe and re-render the DOM with the form having the new information.
What's wrong
Everything loads however, when I click the edit button the modal pops up with the first recipe always. It will even change this.state.currentRecipe but the DOM doesn't re-render with the proper recipe's information.
How do I get the form's information to update based on which recipe-card I'm in when I click the 'Edit' button?(there is a button in each card).
(and even if it did, would it just hide the modal again?)
Here is the link to the component folder of the repo https://github.com/JeremyWeisener/React-Recipe-box/tree/master/src/components
in case the code below isn't enough information
Here is the inside of the 4 main files I believe matter (cleaned up a bit and put here to make life easier)
app.js
import React, { Component } from 'react';
import RecipeCard from './recipe-card';
import RecipeBook from './recipe-book';
import AddRecipe from './add-recipe';
import RecipeEditer from './edit-recipe';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
currentRecipe: 0,
recipes: JSON.parse(localStorage.getItem('cookBook')),
updateKey: 0,
counter: 0
}
}
changeRecipe = (arg) => {
this.setState({currentRecipe:arg});
}
render() {
return (
<div>
<div>
<RecipeBook changeRecipe={this.changeRecipe.bind(this)} recipes={this.state.recipes} />
</div>
<div id="popUps">
<AddRecipe />
<RecipeEditer index={this.state.currentRecipe} forglory={this.state} />
</div>
<div id="editPopup">
</div>
</div>
);
}
}
recipe-book.js
import React from 'react';
import RecipeCard from './recipe-card';
const RecipeBook = (props) => {
var changeRecipe = props.changeRecipe;
console.log(props);
const DisplayRecipes = props.recipes.map((recipe, index) => {
return <RecipeCard index={index} key={index+1} recipe={recipe} changeRecipe={changeRecipe.bind(this)} />
})
return (
<div id="accordion" role="tablist" aria-multiselectable="true">
{DisplayRecipes}
<div>
<button className="btn btn-primary" type="button" data-toggle="modal" data-target="#addRecipe"> Add Recipe </button>
</div>
</div>
);
}
export default RecipeBook;
recipe-card.js
import React from 'react';
import Ingredients from './recipe-ingredients';
import Steps from './recipe-steps';
import RecipeEditer from './edit-recipe';
const RecipeCard = (props) => {
const changeRecipe = props.changeRecipe;
return (
<div>
{/*Card Start*/}
<div className="recipe card">
{/*Header*/}
<div className="card-header" role="tab" id={`heading${props.index}`}>
<h5 className="mb-0">
<a data-toggle="collapse" data-parent="#accordion" href={`#collapse${props.index}`} aria-expanded="true" aria-controls={`collapse${props.index}`}>
{props.recipe.title}
</a>
</h5>
</div>
{/*End Header*/}
{/*Collapse Div*/}
<div id={`collapse${props.index}`} className="collapse" role="tabpanel" aria-labelledby={`heading${props.index}`}>
{/*Card IMG*/}
<img className="card-img-top" src="./img/Fried Chik'n-edit1.jpg" />
{/*Card Block*/}
<div className="card-block">
<p className="card-text">
</p>
{/* Ingredients */}
<h3>Ingredients</h3>
<Ingredients parts={props.recipe.ingredients} />
{/* Steps */}
<h3>Steps</h3>
<Steps levels={props.recipe.steps} />
Print Recipe
{/*Edit Button is here*/}
<button onClick={() => {changeRecipe(props.index)}} className="btn btn-success" type="button" data-toggle="modal" data-target="#editRecipe"> Edit Recipe </button>
{/*Edit Button is here*/}
Delete Recipe
</div>
{/*End Card Block*/}
</div>
{/*End Collapsable*/}
</div>
{/*End Card*/}
</div>
);
}
export default RecipeCard;
edit-recipe.js
import React from 'react';
const RecipeEditer = (props) => {
var index = props.index;
var cookBook = JSON.parse(localStorage.getItem("cookBook"));
var editMe = cookBook[props.forglory.currentRecipe];
const UpdateRecipe = () => {
var formData = $('#recipeEditer').serializeArray();
var newRecipe = {};
newRecipe.title = formData[0]['value'];
newRecipe.image = formData[1]['value'];
newRecipe.ingredients = formData[2]['value'].split(',');
newRecipe.steps = formData[3]['value'].split(',');
cookBook[index] = newRecipe;
localStorage.setItem("cookBook", JSON.stringify(cookBook));
}
return (
<div className="modal fade" id="editRecipe" tabIndex="-1" role="dialog" aria-labelledby="editRecipeLabel" aria-hidden="false">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
{/* Title */}
<h5 className="modal-title" id="exampleModalLabel">
Edit Recipe
</h5>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="false">×</span>
</button>
</div>
<div className="modal-body">
<form id="recipeEditer" name="editRecipe">
<label htmlFor="name"><h4>Name</h4></label>
<input id="name" className="form-control" name="title" type="text" defaultValue={editMe.title} />
<label htmlFor="image"><h4>Image</h4></label>
<input id="image" className="form-control" name="image" type="text" defaultValue={editMe.image} />
<label htmlFor="ingredients"><h4>Ingredients</h4></label>
<textarea id="ingredients" className="form-control" name="ingredients" rows="4" cols="48" defaultValue={editMe.ingredients}></textarea>
<label htmlFor="steps"><h4>Steps</h4></label><br/>
<textarea id="steps" className="form-control" name="steps" cols="48" rows="4" defaultValue={editMe.steps} ></textarea>
</form>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" data-dismiss="modal">Close</button>
{/*Submit Button*/}
<button onClick={UpdateRecipe} type="button" className="btn btn-success" data-dismiss="modal">Change Recipe</button>
{/*Submit Button*/}
</div>
</div>
</div>
</div>
);
}
export default RecipeEditer;
thank you very much if you even glanced, if there is any more information that can help please don't hesitate to ask !
You are binding changeRecipe to this in recipe-book.js, thus setting its context to RecipeBook.
Try changing
<RecipeCard index={index} key={index+1} recipe={recipe} changeRecipe={changeRecipe.bind(this)} />
to
<RecipeCard index={index} key={index+1} recipe={recipe} changeRecipe={changeRecipe} />
You may not even have to use bind in app.js, since changeRecipe is defined as an arrow function.