I have a text input that I am trying to use to create new tags via hooks. However, when I enter in the value and press enter, nothing happens. I Googled it and was told to make the input type=text but this has not fixed it. Here is the code:
import React, { useState } from 'react';
import AddIcon from '#mui/icons-material/Add';
import MaximizeIcon from '#mui/icons-material/Maximize';
import './styles.css';
const Card = ({ id, pic, name, email, company, skill, average, grades }) => {
const [clicked, setClicked] = useState(false);
const [tags, setTags] = useState([]);
return (
<div className='card' id={id}>
<img src={pic} alt='avatar' className='img' />
<div className='list'>
<h1>{name}</h1>
<div className='properties'>
<p>Email: {email}</p>
<p>Company: {company}</p>
<p>Skill: {skill}</p>
<p>Average: {average}</p>
</div>
<input
type='text'
placeholder='Add a tag'
onSubmit={(e) => {
setTags(...tags, e.target.value);
console.log(tags);
}}
/>
{clicked && (
<div className='grades'>
{grades.map((grade, index) => (
<p>{`Test ${index}: ${grade}%`}</p>
))}
</div>
)}
</div>
<button onClick={() => setClicked(!clicked)}>
{!clicked ? (
<AddIcon fontSize='large' />
) : (
<MaximizeIcon fontSize='large' />
)}
</button>
</div>
);
};
export default Card;
How do I make it submit a new tags to the setTags hook?
You can use keyDown Event
onKeyDown = {
(e) => {
if (e.key === 'Enter') {
setTags(...tags, e.target.value);
}
}
}
Related
So I have a submit form where the user needs to create a task by typing in a task name. I want it to be empty at the beginning and have a placeholder of "you must enter a task" when the user click add without entering anything. Now I can achieve it to display the placeholder but it's either always there or I encounter unreachable code. I know how to clean the submission & return to the add function, just need to be able to display the placeholder conditionally. Here's what my code looks like atm:
import { useState } from "react";
export default function Todos() {
const [todos, setTodos] = useState([{ text: "hey" }]);
const [todoText, setTodoText] = useState("");
const [isEmpty, setEmpty] = useState("false");
const addTodo = (e) => {
e.preventDefault();
if (todoText){
setTodos([...todos, { text: todoText }]);
setTodoText("");
} else {
setEmpty(true)
setTodoText("");
return
}
}
return (
<div>
{todos.map((todo, index) => (
<div key={index}>
<input type="checkbox" />
<label>{todo.text}</label>
</div>
))}
<br />
<form onSubmit={addTodo}>
<input
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
type="text"
></input>
<button type="submit">Add</button>
{isEmpty &&<span style={{ color: "red" }}>Enter a task</span>}
</form>
</div>
);
}
I could change your code with the following:
You need to initialize isEmpty by false instead of string "false".
And you can use this flag on showing placeholder texts.
Note that I renamed isEmpty by showError.
import { useState } from "react";
export default function Todos() {
const [todos, setTodos] = useState([{text: "hey"}]);
const [todoText, setTodoText] = useState("");
const [showError, setShowError] = useState(false);
// #ts-ignore
const addTodo = (e) => {
e.preventDefault();
if (todoText) {
setTodos([...todos, {text: todoText}]);
setTodoText("");
setShowError(false);
} else {
setTodoText("");
setShowError(true);
return
}
}
return (
<div>
{todos.map((todo, index) => (
<div key={index}>
<input type="checkbox"/>
<label>{todo.text}</label>
</div>
))}
<br/>
<form onSubmit={addTodo}>
<input
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
type="text"
></input>
<button type="submit">Add</button>
{(showError && !todoText) && <span style={{color: "red"}}>Enter a task</span>}
</form>
</div>
);
}
I just want to show toggled item. But all map items showing up. Basically this is the result I'm getting from onclick. I think i need to give index or id to each item but i don't know how to do it. i gave id to each question didn't work.
App.js.
import "./App.css";
import React, { useState, useEffect } from "react";
import bg from "./images/bg-pattern-desktop.svg";
import bg1 from "./images/illustration-box-desktop.svg";
import bg2 from "./images/illustration-woman-online-desktop.svg";
import { data } from "./data";
import Faq from "./Faq";
function App() {
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(false);
useEffect(() => {
console.log(db);
}, []);
return (
<>
<div className="container">
<div className="container-md">
<div className="faq">
<img src={bg} className="bg" />
<img src={bg1} className="bg1" />
<img src={bg2} className="bg2" />
<div className="card">
<h1>FAQ</h1>
<div className="info">
{db.map((dat) => (
<Faq
toggle={toggle}
setToggle={setToggle}
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
</div>
</div>
</div>
</div>
</div>
</>
);
}
export default App;
(map coming from simple data.js file that I created. it includes just id title desc.)
Faq.js
import React from "react";
import arrow from "./images/icon-arrow-down.svg";
const Faq = ({ toggle, setToggle, title, desc, id }) => {
return (
<>
{" "}
<div className="question" onClick={() => setToggle(!toggle)}>
<p>{title}</p>
<img src={arrow} className={toggle ? "ikon aktif" : "ikon"} />
</div>
<p className="answer border">{toggle ? <>{desc}</> : ""}</p>
</>
);
};
export default Faq;
You need to store the index value of the toggle item.
You can modify the code with only 2 lines with the existing codebase.
import "./App.css";
import React, { useState, useEffect } from "react";
import bg from "./images/bg-pattern-desktop.svg";
import bg1 from "./images/illustration-box-desktop.svg";
import bg2 from "./images/illustration-woman-online-desktop.svg";
import { data } from "./data";
import Faq from "./Faq";
function App() {
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(-1); //Modify Here
useEffect(() => {
console.log(db);
}, []);
return (
<>
<div className="container">
<div className="container-md">
<div className="faq">
<img src={bg} className="bg" />
<img src={bg1} className="bg1" />
<img src={bg2} className="bg2" />
<div className="card">
<h1>FAQ</h1>
<div className="info">
{db.map((dat, index) => ( //Modify Here
<Faq
toggle={index === toggle} //Modify Here
setToggle={() => setToggle(index)} //Modify Here
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
</div>
</div>
</div>
</div>
</div>
</>
);
}
export default App;
import React from "react";
import arrow from "./images/icon-arrow-down.svg";
const Faq = ({ toggle, setToggle, title, desc, id }) => {
return (
<>
{" "}
<div className="question" onClick={setToggle}>
<p>{title}</p>
<img src={arrow} className={toggle ? "ikon aktif" : "ikon"} />
</div>
<p className="answer border">{toggle ? <>{desc}</> : ""}</p>
</>
);
};
export default Faq;
You will need state for each toggle. Here is a minimal verifiable example. Run the code below and click ⭕️ to toggle an item open. Click ❌ to close it.
function App({ faq = [] }) {
const [toggles, setToggles] = React.useState({})
const getToggle = key =>
Boolean(toggles[key])
const setToggle = key => event =>
setToggles({...toggles, [key]: !getToggle(key) })
return faq.map((props, key) =>
<Faq key={key} {...props} open={getToggle(key)} toggle={setToggle(key)} />
)
}
function Faq({ question, answer, open, toggle }) {
return <div>
<p>
{question}
<button onClick={toggle} children={open ? "❌" : "⭕️"} />
</p>
{open && <p>{answer}</p>}
</div>
}
const faq = [
{question: "hello", answer: "world"},
{question: "eat", answer: "vegetables"}
]
ReactDOM.render(<App faq={faq} />, document.querySelector("#app"))
p { border: 1px solid gray; padding: 0.5rem; }
p ~ p { margin-top: -1rem; }
button { float: right; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Instead of doing this (in App component):
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(false);
you can write an useState hook like below to combine the two hooks and assign an isOpened property for each Faq element:
const [db, setDb] = useState(data.map(value=>{return {...value, isOpened:false}}));
and then right here you can do this (as the child of <div className="info">):
{db.map((dat, index) => (
<Faq
toggle={dat.isOpened}
setToggle={() => toggleById(dat.id)}
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
Also you need to declare toggleById function in App component:
const toggleById = (id) => {
const newDb = db.map(dat=>{
if(dat.id==id){
return {...dat,isOpened:!dat.isOpened}
}
return dat;
});
setDb(newDb);
}
and since setToggle prop of Faq, calls toggleById by its defined parameter, there is no need to do this in Faq component:
<div className="question" onClick={() => setToggle(!toggle)}>
you can simply write:
<div className="question" onClick={setToggle}>
I have a problem because I try to add place._id to another component as props (line 102), but I need to do it after click on button (line 96 and function editPlace - line 37-41) to display information about place inside popup. However I get totally various id's after refreshing the page I know that it's propably connected with rendering but to be honest I don't know how to do it properly. I've tried to add EditPlaceForm to document but ref's are not my strength yet and also I don't know if it's good strategy.
Numbers of lines are placed in comments.
import React, {
Children,
useContext, useRef, useState,
} from 'react';
import { Link } from 'react-router-dom';
import { removePlace, updatePlaceStatus } from '../../actions/FetchData';
import '../../scss/base/_users-list.scss';
import { PlacesContext } from '../../contexts/PlacesContext';
import EditPlaceForm from './EditPlaceForm/EditPlaceForm';
import '../../scss/base/_places-panel.scss';
function PlacesPanel() {
const places = useContext(PlacesContext);
const popupEl = useRef(null);
const [placeToEdit, setPlaceToEdit] = useState(0);
const handleChangeStatus = (event) => {
const changedStatus = event.target.value;
const changedPlaceId = event.target.id;
updatePlaceStatus(changedPlaceId, changedStatus);
};
const removeSelectedPlace = (event) => {
const removedPlaceId = event.target.value;
const fetchMyData = async () => {
await removePlace(removedPlaceId);
};
fetchMyData();
window.location.reload(true);
};
{/* lines 37-42 here */}
const editPlace = (e, placeId) => {
setPlaceToEdit(placeId);
popupEl.current.style.display = 'block';
console.log(placeId, placeToEdit);
};
return (
<>
<div className="page-container">
<h1>Users list</h1>
{places.map((place) => (
<div className="place-list-item" key={place._id}>
<button className="remove-user-button" value={place._id} type="submit" onClick={removeSelectedPlace}>X</button>
<div>
<Link to={place._id}><img className="place-img" src={place.img} alt="place-img" width="100" height="100" /></Link>
</div>
<div className="place-name">
<h4><Link to={place._id}>{place.name}</Link></h4>
</div>
<div className="place-address">
<h5>
{place.city}
,
{' '}
{place.street}
,
{' '}
{place.houseNo}
,
{' '}
{place.postalCode}
</h5>
</div>
<div className="place-category">
<p>
Kategoria:
{place.category}
</p>
</div>
<div className="place-district">
<p>
Dzielnica:
{place.district}
</p>
</div>
<div className="place-status">
<p>
<b>
Status:
{place.statusPlace}
</b>
</p>
</div>
<p>Zmień status: </p>
<select onChange={handleChangeStatus} id={place._id}>
<option selected value=""> </option>
<option value="pending">pending</option>
<option value="added">added</option>
</select>
{/* line 96 here */}
<input className="edit-place-button" value="Edytuj" onClick={() => editPlace(this, place._id)} type="submit" />
</div>
))}
<div className="popup" ref={popupEl}>
<div className="button-popup-container">
<button type="submit" className="button-popup" onClick={() => { popupEl.current.style.display = 'none'; }}>X</button>
{/* line 102 here */}
<EditPlaceForm placeToEdit={placeToEdit} />
</div>
</div>
</div>
</>
);
}
export default PlacesPanel;
you forgot to change at here, at handleChangeStatus,
setPlaceToEdit(changedPlaceId); adding this will resolve issue,
now after changeing options it will send new values, i hope this is what you are looking for,
const handleChangeStatus = (event) => {
const changedStatus = event.target.value;
const changedPlaceId = event.target.id;
setPlaceToEdit(changedPlaceId);
updatePlaceStatus(changedPlaceId, changedStatus);
};
you can also try to re render using useEffect hook, to ensure latest data changes happened and children rendered, useEffect(()=>{},[placeToEdit, showPop ])
const [placeToEdit, setPlaceToEdit] = useState({});
const [showPop, setShowPop] = useState(false);
useEffect(()=>{},[placeToEdit, showPop ])
const editPlace = (place) => {
setPlaceToEdit(place);
setShowPop(true);
popupEl.current.style.display = 'block';
console.log(placeId, placeToEdit);
};
<input className="edit-place-button" value="Edytuj" onClick={() => editPlace(place)} type="submit" />
then,
{ showPop &&
<div className="popup" ref={popupEl}>
<div className="button-popup-container">
<button type="submit" className="button-popup" onClick={() => { popupEl.current.style.display = 'none'; }}>X</button>
{/* line 102 here */}
<EditPlaceForm placeToEdit={placeToEdit} setShowPop={setShowPop}/>
</div>
</div>
}
I am currently making a project over the database I created using Mock API. I created a button, created addToFavorites function. When the button was clicked, I wanted the selected product's information to go to the favorites, but I couldn't. I would be glad if you could help me on how to do this.
(Favorites.js empty now. I got angry and deleted all the codes because I couldn't.)
(
Recipes.js
import React, { useState, useEffect } from "react"
import axios from "axios"
import "./_recipe.scss"
import Card from "../Card"
function Recipes() {
const [recipes, setRecipes] = useState([])
const [favorites, setFavorites] = useState([])
useEffect(() => {
axios
.get("https://5fccb170603c0c0016487102.mockapi.io/api/recipes")
.then((res) => {
setRecipes(res.data)
})
.catch((err) => {
console.log(err)
})
}, [])
const addToFavorites = (recipes) => {
setFavorites([...favorites, recipes])
console.log("its work?")
}
return (
<div className="recipe">
<Card recipes={recipes} addToFavorites={addToFavorites} />
</div>
)
}
export default Recipes
Card.js
import React, { useState } from "react"
import { Link } from "react-router-dom"
import { BsClock, BsBook, BsPerson } from "react-icons/bs"
function Card({ recipes, addToFavorites }) {
const [searchTerm, setSearchTerm] = useState("")
return (
<>
<div className="recipe__search">
<input
type="text"
onChange={(event) => {
setSearchTerm(event.target.value)
}}
/>
</div>
<div className="recipe__list">
{recipes
.filter((recipes) => {
if (searchTerm === "") {
return recipes
} else if (
recipes.title.toLowerCase().includes(searchTerm.toLowerCase())
) {
return recipes
}
})
.map((recipe) => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={350} height={230} />
<h1 className="recipe__card__title">{recipe.title}</h1>
<h3 className="recipe__card__info">
<p className="recipe__card__info--icon">
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h3>
<h3 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h3>
<button type="button" className="recipe__card__cta">
<Link
to={{
pathname: `/recipes/${recipe.id}`,
state: { recipe }
}}
>
View Recipes
</Link>
</button>
<button onClick={() => addToFavorites(recipes)}>
Add to favorites
</button>
</div>
)
})}
</div>
</>
)
}
export default Card
Final Output:
I have implemented the addToFavorite() and removeFavorite() functionality, you can reuse it the way you want.
I have to do bit of modification to the code to demonstrate its working, but underlying functionality of addToFavorite() and removeFavotie() works exactly the way it should:
Here is the Card.js where these both functions are implemented:
import React, { useState } from "react";
import { BsClock, BsBook, BsPerson } from "react-icons/bs";
function Card({ recipes }) {
const [searchTerm, setSearchTerm] = useState("");
const [favorite, setFavorite] = useState([]); // <= this state holds the id's of all favorite reciepies
// following function handles the operation of adding fav recipes's id's
const addToFavorite = id => {
if (!favorite.includes(id)) setFavorite(favorite.concat(id));
console.log(id);
};
// this one does the exact opposite, it removes the favorite recipe id's
const removeFavorite = id => {
let index = favorite.indexOf(id);
console.log(index);
let temp = [...favorite.slice(0, index), ...favorite.slice(index + 1)];
setFavorite(temp);
};
// this variable holds the list of favorite recipes, we will use it to render all the fav ecipes
let findfavorite = recipes.filter(recipe => favorite.includes(recipe.id));
// filtered list of recipes
let filtered = recipes.filter(recipe => {
if (searchTerm === "") {
return recipe;
} else if (recipe.title.toLowerCase().includes(searchTerm.toLowerCase())) {
return recipe;
}
});
return (
<div className="main">
<div className="recipe__search">
<input
type="text"
onChange={event => {
setSearchTerm(event.target.value);
}}
/>
</div>
<div className="recipe-container">
<div className="recipe__list">
<h2>all recipes</h2>
{filtered.map(recipe => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={50} height={50} />
<h2 className="recipe__card__title">{recipe.title}</h2>
<h4 className="recipe__card__info">
<p>
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h4>
<h4 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h4>
<button onClick={() => addToFavorite(recipe.id)}>
add to favorite
</button>
</div>
);
})}
</div>
<div className="favorite__list">
<h2>favorite recipes</h2>
{findfavorite.map(recipe => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={50} height={50} />
<h2 className="recipe__card__title">{recipe.title}</h2>
<h4 className="recipe__card__info">
<p className="recipe__card__info--icon">
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h4>
<h4 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h4>
<button onClick={() => removeFavorite(recipe.id)}>
remove favorite
</button>
</div>
);
})}
</div>
</div>
</div>
);
}
export default Card;
Here is the live working app : stackblitz
You can get the previous favourites recipes and add the new ones.
const addToFavorites = (recipes) => {
setFavorites(prevFavourites => [...prevFavourites, recipes])
console.log("its work?")
}
The use case is there will be add topic button which when clicked should show a form for adding the topic. When user fills the topic form and hits the save button, that topic should be shown in the input box with edit button instead of add. There can be multiple topics. For example, if I have 4 topics already or saved them after adding then they should be displayed with edit button. The way I am doing is not even triggering handleChange.
I have created a sandbox for this and here it is
https://codesandbox.io/s/koqqvz2307
The code
class FieldArray extends React.Component {
state = {
topics: [],
topic: ""
};
handleChange = e => {
console.log("handleChange", e);
this.setState({ topic: { ...this.state.topic, topic: e.target.value } });
};
handleSubmit = e => {
e.preventDefault();
console.log("state of topics array with multiple topics");
};
render() {
return (
<div>
<FieldArrayForm
topics={this.state.topics}
topic={this.state.topic}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
/>
</div>
);
}
}
export default FieldArray;
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
);
const renderTopics = ({
fields,
meta: { error },
handleChange,
handleSubmit,
topic
}) => (
<ul>
<li>
<button type="button" onClick={() => fields.push()}>
Add Topic
</button>
</li>
{fields.map((topicName, index) => (
<li key={index}>
<span>
<Field
name={topicName}
type="text"
onChange={handleChange}
component={renderField}
label={`Topic #${index + 1}`}
/>
<span>
<button
type="button"
title="Remove Hobby"
onClick={() => fields.remove(index)}
>
Remove
</button>
{topic ? (
<button type="button" title="Add" onSubmit={handleSubmit}>
Edit
</button>
) : (
<button type="button" title="Add" onSubmit={handleSubmit}>
Add
</button>
)}
</span>
</span>
</li>
))}
{error && <li className="error">{error}</li>}
</ul>
);
const FieldArraysForm = props => {
const { handleSubmit, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<FieldArray name="topic" component={renderTopics} />
</form>
);
};
export default reduxForm({
form: "fieldArrays", // a unique identifier for this form
validate
})(FieldArraysForm);
How do i save and show multiple topics when using redux-form? I tried to take the concept from fieldarray but i could not do it yet.
Your handleChange is undefined, and this is why your function isn't being called.
If you are willing that renderTopics receive a handleChange function, you should pass the handleChange prop to the FieldArray component (according to redux-form docs):
const FieldArraysForm = props => {
const { handleChange, handleSubmit, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<FieldArray name="topic" component={renderTopics} handleChange={handleChange} />
</form>
);
};
Alternatively, you can simply pass all props from FieldArraysForm to the FieldArray component:
const FieldArraysForm = props => (
<form onSubmit={handleSubmit}>
<FieldArray name="topic" component={renderTopics} {...props} />
</form>
);