I have a class (Cookbook) that passes a click handler "handleOnChange" to a child component (Filter). Filter renders checkboxes. On those checkboxes, I execute the handleOnChange. I want to update the state that exists in Cookbook, but even though handleOnChange is declared in Cookbook, it does not recognize this.state.selectedStaples. The error takes place in the handleOnChange function on the first non-console.log line. How do I update the state of selectedStaples in Cookbook?
Here is Cookbook
class Cookbook extends React.Component {
constructor(){
super();
this.state ={
cookbook: data,
selectedStaples: [],
}
}
getStapleList(recipes){
let stapleList = [];
recipes.forEach(function(recipe){
recipe.ingredient_list.forEach(function(list){
stapleList.push(list.needed_staple);
});
});
let flattenedStapleList = stapleList.reduce(function(a,b){
return a.concat(b);
})
return flattenedStapleList
}
handleOnChange(staple, isChecked, selectedStaples) {
console.log(staple);
console.log(isChecked);
console.log(selectedStaples);
const selectedStaples = this.state.selectedStaples;
// if (isChecked) {
// this.setState({
// selectedStaples: selectedStaples.push(staple)
// })
// } else {
// this.setState({
// selectedStaples: selectedStaples.filter(selectedStaple => selectedStaple !== staple)
// })
// }
}
render(){
const selectedStaples = this.state.selectedState;
const cookbook = this.state.cookbook;
const stapleList = this.getStapleList(cookbook.recipes);
return(
<div className="cookbook">
<div className="filter-section">
<h1>This is the filter section</h1>
<Filter stapleList = {stapleList} onChange={this.handleOnChange}/>
</div>
<div className="recipes">
<h1>This is where the recipes go</h1>
<div className="recipe">
<Recipe cookbook = {cookbook}/>
</div>
</div>
</div>
);
}
}
and Filter
class Filter extends React.Component {
render(){
const stapleList = this.props.stapleList;
const checkboxItems = stapleList.map((staple, index) =>
<div key = {index}>
<label>
<input
type="checkbox"
value="{staple}"
onClick={e => this.props.onChange(staple, e.target.checked)}
/>
{staple}
</label>
</div>
);
return (
<form>
{checkboxItems}
</form>
);
}
}
Related
I was trying to take example from react but seem it doesn't work as I expected.
I am trying to open a modal when user click the button in parent component, but function to open modal is located in the child component.
Parent component just where i try to invoke a modal:
<label class="text-white m-5 p-1">
<input type="checkbox" checked={false} onChange={handleCheck} />
I have read and agree to the <button onClick={}>Privacy</button>
<Portal>
<TermsModal />
</Portal>
</label>
How to do that?
If you want the Modal component to control the state, instead of it being passed with props you can use this pattern:
const { Modal, openModal } = createModal();
return (
<>
<button type="button" onClick={openModal}>
open modal
</button>
<Modal />
</>
);
It may be weird at first, but it's really fun and powerful :)
https://playground.solidjs.com/anonymous/d3a74069-e3d4-4d3e-9fb2-bafc5d6f5bf5
Create a signal inside parent component and pass it to the child component. Bind the visibility of the modal window to the signal's value:
import { Component, createSignal, Show } from 'solid-js';
import { Portal, render } from 'solid-js/web';
const Modal: Component<{ show: boolean }> = (props) => {
return (
<Show when={props.show}>
<div>Modal Window</div>
</Show>
);
};
const App = () => {
const [show, setShow] = createSignal(false);
const toggleShow = () => setShow(prev => !prev);
return (
<div>
<div>Show: {show() ? 'true': 'false'} <button onClick={toggleShow}>Toggle Show</button></div>
<Portal><Modal show={show()} /></Portal>
</div>
)
};
render(() => <App />, document.body);
https://playground.solidjs.com/anonymous/adf9ba7a-3e1b-4ce9-92b2-e78ff3fa55ec
You can further improve your component by creating a close handler and passing it to the child component. Now, modal window can show a close button.
Also you can add an event handler to the document in order to close the window whenever Escape key is pressed:
import { Component, createSignal, onCleanup, onMount, Show } from 'solid-js';
import { Portal, render } from 'solid-js/web';
const Modal: Component<{ show: boolean, close: () => void }> = (props) => {
const handleKeydown = (event) => {
if (event.key === 'Escape') {
props.close();
}
};
onMount(() => {
document.addEventListener('keydown', handleKeydown);
});
onCleanup(() => {
document.removeEventListener('keydown', handleKeydown);
});
return (
<Show when={props.show}>
<div>Modal Window <button onclick={props.close}>close</button></div>
</Show>
);
};
const App = () => {
const [show, setShow] = createSignal(false);
const close = () => setShow(false);
const toggleShow = () => setShow(prev => !prev);
return (
<div>
<div>Show: {show()} <button onClick={toggleShow}>Toggle Show</button></div>
<Portal><Modal show={show()} close={close} /></Portal>
</div>
)
};
render(() => <App />, document.body);
https://playground.solidjs.com/anonymous/0ae98cf1-19d6-487f-80dc-72d3c2e554dc
You can use a state in the parent component to keep track of whether the modal should be open or closed, and pass a function as a prop to the child component that updates that state:
const [isModalOpen, setIsModalOpen] = React.useState(false);
const handleButtonClick = () => {
setIsModalOpen(true);
};
return (
<div>
<label className="text-white m-5 p-1">
<input type="checkbox" checked={false} onChange={handleCheck} />
I have read and agree to the <button onClick={handleButtonClick}>Privacy</button>
<Portal>
<TermsModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
</Portal>
</label>
</div>
);
// Child component
const TermsModal = ({ isOpen, onClose }) => {
if (!isOpen) {
return null;
}
return (
<div className="modal">
{/* Modal content */}
<button onClick={onClose}>Close</button>
</div>
);
};
I am working on my first React app. It is a recipe sort of app. It has an ingredients filter. The main component is the Cookbook component. It has a Filter component and a Recipe component.
The Recipe component displays all recipes on load. The Filter component displays all needed ingredients or staples. This part all works great.
What I want to do now is when a person clicks on a staple, lets say butter, it would then filter the entire recipe list. My assumption is that I need to pass the state of Filter up. How can I do this with these checkboxes?
Here is what I have:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
var data = require('./recipes.json');
function IngredientDetailList(props){
const ingredientList = props.ingredientList;
const listItems = ingredientList.map((ingredient, index) =>
<div key={index}>
{ingredient.measurement_amount}
{ingredient.measurement_unit}
{ingredient.ingredient}
</div>
);
return (
<div>{listItems}</div>
)
}
function Recipe(props){
const recipeList = props.cookbook.recipes
const listItems = recipeList.map((recipe) =>
<div key={recipe.id}>
<h2>{recipe.name}</h2>
<IngredientDetailList ingredientList = {recipe.ingredient_list}/>
</div>
);
return(
<div>{listItems}</div>
)
}
class Filter extends React.Component {
constructor(){
super();
this.state = {
checkedStaples: {},
}
}
render(){
const stapleList = this.props.stapleList;
const checkboxItems = stapleList.map((staple, index) =>
<div key = {index}>
<label>
<input type="checkbox" value="{staple}"/>
{staple}
</label>
</div>
);
return (
<form>
{checkboxItems}
</form>
);
}
}
class Cookbook extends React.Component {
constructor(){
super();
this.state ={
cookbook: data
}
}
getStapleList(recipes){
let stapleList = [];
recipes.forEach(function(recipe){
recipe.ingredient_list.forEach(function(list){
stapleList.push(list.needed_staple);
});
});
let flattenedStapleList = stapleList.reduce(function(a,b){
return a.concat(b);
})
return flattenedStapleList
}
render(){
const cookbook = this.state.cookbook;
const stapleList = this.getStapleList(cookbook.recipes);
return(
<div className="cookbook">
<div className="filter-section">
<h1>This is the filter section</h1>
<Filter stapleList = {stapleList}/>
</div>
<div className="recipes">
<h1>This is where the recipes go</h1>
<div className="recipe">
<Recipe cookbook = {cookbook}/>
</div>
</div>
</div>
);
}
}
ReactDOM.render(
<Cookbook />,
document.getElementById('root')
);
You could try to do something like this:
// ....
function Recipe(props){
const selectedStaples = props.selectedStaples;
const recipeList = props.cookbook.recipes
const listItems = recipeList.map((recipe) => {
const hasStaple = ingredient_list.reduce(
(_hasStaple, staple) => _hasStaple || selectedStaples.includes(staple),
false
);
// only show recipe if it has a staple
if (selectedStaples.length === 0 || hasStaple)
return (
<div key={recipe.id}>
<h2>{recipe.name}</h2>
<IngredientDetailList ingredientList = {recipe.ingredient_list}/>
</div>
);
}
return null;
}
);
return(
<div>{listItems}</div>
)
}
class Filter extends React.Component {
constructor(){
super();
this.state = {
checkedStaples: {},
}
}
render(){
const stapleList = this.props.stapleList;
const checkboxItems = stapleList.map((staple, index) =>
<div key = {index}>
<label>
<input
type="checkbox"
value="{staple}"
onClick={e => this.props.onChange(staple, e.checked)}
/>
{staple}
</label>
</div>
);
return (
<form>
{checkboxItems}
</form>
);
}
}
class Cookbook extends React.Component {
constructor(){
super();
this.state ={
cookbook: data,
selectedStaples: []
}
}
getStapleList(recipes){
let stapleList = [];
recipes.forEach(function(recipe){
recipe.ingredient_list.forEach(function(list){
stapleList.push(list.needed_staple);
});
});
let flattenedStapleList = stapleList.reduce(function(a,b){
return a.concat(b);
})
return flattenedStapleList
}
handleOnChange(staple, isChecked) {
const selectedStaples = this.state.selectedStaples;
if (isChecked) {
this.setState({
selectedStaples: selectedStaples.push(staple);
})
} else {
this.setState({
selectedStaples: selectedStaples.filter(selectedStaple => selectedStaple !== staple);
})
}
}
render(){
const selectedStaples = this.state.selectedStaples;
const cookbook = this.state.cookbook;
const stapleList = this.getStapleList(cookbook.recipes);
return(
<div className="cookbook">
<div className="filter-section">
<h1>This is the filter section</h1>
<Filter stapleList = {stapleList} onChange={this.handleOnChange.bind(this)}/>
</div>
<div className="recipes">
<h1>This is where the recipes go</h1>
<div className="recipe">
<Recipe cookbook = {cookbook} selectedStaples={selectedStaples} />
</div>
</div>
</div>
);
}
}
ReactDOM.render(
<Cookbook />,
document.getElementById('root')
);
It keeps track of the selected stapes in the cookbook component and passes that on to recipe
I have a submit action for my form which basically validates on submit.
It is working as i expect because when i submit the form it renders the errors. But the issue occurs when i do the submit i do not want to do the ajax request as the form is invalid. I notice that on the first submit the emailError is not set (default) but the second submit the state contains the correct emailError set to true.
I understand from the react docs that setState is not available immeditely as it is pending.
How can i get around this issue?
My code is below
import React, { Component } from 'react';
import isEmail from 'validator/lib/isEmail';
class formExample extends Component
{
constructor(props) {
super(props);
this.state = {
email: '',
emailError: false
};
this.register = this.register.bind(this);
this.updateState = this.updateState.bind(this);
}
updateState(e) {
this.setState({ email: e.target.value });
}
validateEmail() {
if (!isEmail(this.state.email)) {
console.log("setting state");
this.setState({ emailError: true });
return;
}
console.log(this.state);
this.setState({ emailError: false });
}
register(event) {
event.preventDefault();
this.validateEmail();
//only if valid email then submit further
}
render() {
return (
<div className="row">
<div className="col-md-2 col-md-offset-4"></div>
<div className="col-lg-6 col-lg-offset-3">
<form role="form" id="subscribe" onSubmit={this.register}>
<div className="form-group">
<input type="text" className="form-control" placeholder="Email..." name="email" value={this.state.email} onChange={this.updateState} />
<div className="errorMessage">
{this.state.emailError ? 'Email address is invalid' : ''}
</div>
</div>
<div className="input-group input-group-md inputPadding">
<span className="input-group-btn">
<button className="btn btn-success btn-lg" type="submit">
Submit
</button>
</span>
</div>
</form>
</div>
</div>
);
}
}
export default formExample;
in register you call validateEmail, but not return anything, so the rest of the function get's called.
setState is async! so you cannot count on it in the rest of register.
Try this:
validateEmail() {
const isEmailError = !isEmail(this.state.email)
this.setState({ emailError: isEmailError });
return isEmailError;
}
register(event) {
if(this.validateEmail()){
//ajax
};
}
other approach will be:
validateEmail(ajaxCb) {
const isEmailError = !isEmail(this.state.email)
this.setState({ emailError: isEmailError }, ajaxCb);
}
register(event) {
function ajaxCb(){...}
this.validateEmail(ajaxCb)
}
In the following code I have two checkboxes. On-click, they change the state of the component to their respective values.
I am building a form that will need over 100 checkboxes and I don't want to write the "onChange" function for each checkbox.
Is there a way that I can write one OnChange function that will take a parameter, then set the state to that parameter?
I've tried many ways but this is still blocking me.
Thank you!
import React from 'react';
export default class InputSearch extends React.Component {
constructor(props) {
super(props);
this.state = {
inputInternship: '',
inputMidLevel: '',
};
this.handleSubmit = this.handleSubmit.bind(this);
this.onChangeInternship = this.onChangeInternship.bind(this);
this.onChangeMidLevel = this.onChangeMidLevel.bind(this);
}
handleSubmit(e) {
e.preventDefault();
this.props.getJobData(this.state);
}
onChangeInternship(e) {
this.setState({
inputInternship: !this.state.inputInternship,
});
this.state.inputInternship == false? this.setState({ inputInternship: e.target.value }) : this.setState({ inputInternship: '' })
}
onChangeMidLevel(e) {
this.setState({
inputMidLevel: !this.state.inputMidLevel,
});
this.state.inputMidLevel == false? this.setState({ inputMidLevel: e.target.value }) : this.setState({ inputMidLevel: '' })
}
render() {
return (
<div className="search-form">
<form onSubmit={this.handleSubmit}>
<input type="checkbox" value="level=Internship&" checked={this.state.inputInternship} onChange={this.onChangeInternship} /> Internship <br />
<input type="checkbox" value="level=Mid+Level&" checked={this.state.inputMidLevel} onChange={this.onChangeMidLevel} /> Mid Level <br />
<div>
<button
type="submit"
>Search
</button>
</div>
</form>
</div>
);
}
}
You need to create a function that would return specific onChange function:
import React from 'react';
export default class InputSearch extends React.Component {
constructor(props) {
...
this.onChange = this.onChange.bind(this);
}
...
onChange(fieldName) {
return (event) => {
this.setState({
[fieldName]: !this.state[fieldName],
});
if (this.state[fieldName]) {
this.setState({ [fieldName]: '' })
} else {
this.setState({ [fieldName]: e.target.value })
}
}
}
...
render() {
return (
<div className="search-form">
<form onSubmit={this.handleSubmit}>
<input type="checkbox" value="level=Internship&" checked={this.state.inputInternship} onChange={this.onChange('inputInternship')} /> Internship <br />
<input type="checkbox" value="level=Mid+Level&" checked={this.state.inputMidLevel} onChange={this.onChange('inputMidLevel')} /> Mid Level <br />
<div>
<button
type="submit"
>Search
</button>
</div>
</form>
</div>
);
}
}
By the way, are what is the point of changing state twice in your onChangeXYZ functions?
{
this.setState({
[fieldName]: !this.state[fieldName],
});
if (this.state[fieldName]) {
this.setState({ [fieldName]: '' })
} else {
this.setState({ [fieldName]: e.target.value })
}
}
My entire application is built on different react classes and displayed like this:
MainLayout = React.createClass({
render() {
return (
<div id="body">
<Header />
<main className="container">{this.props.content}</main>
<Footer />
</div>
);
}
});
All my front-end is built in react classes like the one below:
InsertData = React.createClass({
insertToCollection(event) {
event.preventDefault();
console.log(this.state.message + " state med message");
var content = Posts.find().fetch();
Posts.insert({
Place: $("post1").val(),
Type: $("post2").val(),
dateAdded: new Date(),
});
},
handleChange(event) {
this.setState({
message: event.target.value
})
console.log(this.state + " mer state her");
function insert(event) {
event.preventDefault();
console.log("added stuff");
}
},
render() {
return (
<div>
<form onSubmit={this.insertToCollection}>
<input type='text' placeholder="Select a restaurant" className="input-field"
onChange={this.handleChange} id="post1"/>
<input type='text' placeholder="What type of food they have" className="input-field"
onChange={this.handleChange} id="post2"/>
<button className="waves-effect waves-light btn btn-block" onChange={this.insert}> Submit </button>
</form>
<DisplayData />
</div>
);
}
});
Insert data to my collection works fine. I would like to render the inserted data onto the page from the <DisplayData /> component:
DisplayData = React.createClass({
render(){
var posts = Posts.find().fetch();
var postList = posts.map(function(posts){
return posts;
})
return <p> Your collection </p>
}
});
I'm rather stuck here, and not really sure how to iterate through the collection and render it in a list-structure for example. Here is my collection so far:
Posts = new Mongo.Collection('posts');
Posts.allow({
insert: function(){
return true;
},
update : function(){
return true;
},
remove : function(){
return true;
}
});
Here is a demo of how you can approach this: http://codepen.io/PiotrBerebecki/pen/bwmAvJ
I'm not sure about the format of your posts collection, but assuming that it is just a regular array, for example var posts = ['One', 'Two'];, you can render the individual post as follows:
var DisplayData = React.createClass({
render(){
var posts = ['One', 'Two'];
var renderPosts = posts.map(function(post, index) {
return (
<li key={index}>{post}</li>
);
});
return (
<div>
<p> Your collection </p>
<ul>
{renderPosts}
</ul>
</div>
);
}
});
Here is the full code from my codepen.
var InsertData = React.createClass({
insertToCollection(event) {
event.preventDefault();
console.log(this.state.message + " state med message");
var content = Posts.find().fetch();
Posts.insert({
Place: $("post1").val(),
Type: $("post2").val(),
dateAdded: new Date(),
});
},
handleChange(event) {
this.setState({
message: event.target.value
})
console.log(this.state + " mer state her");
function insert(event) {
event.preventDefault();
console.log("added stuff");
}
},
render() {
return (
<div>
<form onSubmit={this.insertToCollection}>
<input type='text' placeholder="Select a restaurant" className="input-field"
onChange={this.handleChange} id="post1"/>
<input type='text' placeholder="What type of food they have" className="input-field"
onChange={this.handleChange} id="post2"/>
<button className="waves-effect waves-light btn btn-block" onChange={this.insert}> Submit </button>
</form>
<DisplayData />
</div>
);
}
});
var DisplayData = React.createClass({
render(){
var posts = ['One', 'Two'];
var renderPosts = posts.map(function(post, index) {
return (
<li key={index}>{post}</li>
);
});
return (
<div>
<p> Your collection </p>
<ul>
{renderPosts}
</ul>
</div>
);
}
});
ReactDOM.render(
<InsertData />,
document.getElementById('app')
);
You need to pass posts to your view component DisplayData as props, so in this case after you inserted a post, you should update your state in the InsertData component. Actually it would be better if you do the insertion login inside a service rather than the component itself, but for simplicity right now you can check the following code:
InsertData = React.createClass({
getInitialState: function() {
return {posts: []}; // initialize the state of your component
},
insertToCollection(event) {
event.preventDefault();
console.log(this.state.message + " state med message");
var content = Posts.find().fetch();
Posts.insert({
Place: $("post1").val(), // better to retrieve these values from state. You can use `handleChange` method to keep track of user inputs
Type: $("post2").val(),
dateAdded: new Date(),
}, function(err, data){
var posts = this.state.posts || [];
posts.push(data);
this.setState({posts: posts}); //after setting the state the render method will be called again, where the updated posts will be rendered properly
});
},
handleChange(event) {
this.setState({
message: event.target.value
})
console.log(this.state + " mer state her");
function insert(event) {
event.preventDefault();
console.log("added stuff");
}
},
render() {
return (
<div>
<form onSubmit={this.insertToCollection}>
<input type='text' placeholder="Select a restaurant" className="input-field"
onChange={this.handleChange} id="post1"/>
<input type='text' placeholder="What type of food they have" className="input-field"
onChange={this.handleChange} id="post2"/>
<button className="waves-effect waves-light btn btn-block" onChange={this.insert}> Submit </button>
</form>
<DisplayData posts={this.state.posts}/>
</div>
);
}
});
var DisplayData = React.createClass({
render(){
var posts = this.props.posts || [];
var renderPosts = posts.map(function(post, index) {
return (
<li key={index}>{post}</li>
);
});
return (
<div>
<p> Your collection </p>
<ul>
{renderPosts}
</ul>
</div>
);
}
});