help me please! how to pass value from modalAction.js to projectAction.js to change state?
/src/view/projectList.js
const ProjectList = () => {
const dispatch = useDispatch()
const project = useSelector(state => state.project)
const modal = useSelector(state => state.modal)
return (
<div>
<Button
label="Add Project"
onClick={()=> handleOpenProjectModal()}
primary={true}
/>
<Modal
title={modal.title}
modalClass={modal.status ? 'top-10 opacity-100' : '-top-20 opacity-0 pointer-events-none'}
bgClass={modal.status ? 'opacity-75 pointer-events-auto' : 'opacity-0 pointer-events-none'}
content={
<div>
{modal.content}
</div>
}
/>
</div>
/src/store/action/modalAction.js
export const openProjectModal = () => dispatch => {
dispatch({
type: ADD_PROJECT_MODAL,
title: 'Create Project',
content:
<div>
<Input
label="Title"
onChange={() => dispatch(handleProjectPayload())}
name="title"
/>
</div>
/src/store/action/projectAction.js
export const handleProjectPayload = (value) => async dispatch => {
dispatch({
type : CHANGE_PROJECT_PAYLOAD,
payload : value
})
}
You need to pass the event value from input tag in OnChange handler. onChange={(e) => dispatch(handleProjectPayload(e.target.value))}. This might work
Related
I am working through a tutorial for a course I'm taking. The lab I'm working on walks through creating a to-do app. I'm on step 3, which asks us to create a button that deletes a task. I feel ridiculous, because I know I can figure it out but...well, I haven't yet! I will post the code to see if there are any initial issues, and then update with the methods I've already tried. Any help is greatly appreciated. Thank you!
import React, { useState } from "react";
import "./App.css";
const App = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo));
setTodo("");
} else {
alert("Enter Valid Task");
setTodo("");
}
}
const deleteTodo = (id) => {
let updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => <div>ID: {todo.id} Task: {todo.text} {button}</div>)}
</div>
);
};
export default App;
I didn't just copy and paste, so it's possible that I messed something up while typing. I'm expecting the deleteTodo() function to accept a todo.id and filter the list of todos, excluding the one I want to delete. I'm thinking that the issue may be cause by the way I've created the button? Again, I'm not sure why I can't figure it out. TIA.
EDIT: Okay, it works now! Thank you all so much for explaining this. For anyone else that comes across this problem, here's where I mis-stepped:
const button = <button onClick={() => deleteTodo(todo.id)}Delete<button>
#Nicholas Tower's explanation was very clear--creating this outside of .map(...)causes deleteTodo to get the todo state, not the not the todo I want it to delete from the todos array. #Lars Vonk, #0stone0, and #Sudip Shrestha all said this as well. #Sudip Shrestha and #pilchard also helped correct the deleteTodo function. Again, I really appreciate all the help. The code works now. I'll show the updates so people having a similar issue can compare:
import React from "react";
import "./App.css";
const App = () => {
const [todos, setTodos] = React.useState([]);
const [todo, setTodo] = React.useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos(todos.concat(newTodo));
setTodo("");
} else {
alert("Enter a valid task");
setTodo("");
}
}
// update the state using setState, rathar than mutating it directly #Sudip Shrestha
const deleteTodo = id => {
setTodos(prevState => {
return prevState.filter(todo => todo.id !== id)
});
};
// line 51: button placed inside .map(), as per many suggestions below.
return (
<>
<h1>Todo List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task..."
value={todo}
/>
</form>
{todos.map((todo) =>
<div>
ID: {todo.id} Task: {todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>)}
</>
);
};
export default App;
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
You're creating this button element just once, and the todo variable it refers to is the todo state, which is a string (usually an empty string). Since todo is a string, todo.id is undefined, and deleteTodo can't do anything with that.
You need to create separate buttons for each item, so you should move this code down into your .map:
{todos.map((todo) => (
<div>
ID: {todo.id} Task: {todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
))}
Now each item has its own button, with its own onClick function. And in those functions, todo is the item of the array.
The button cannot access which todo it has I think you should put the code from the const button where you are referring to it or by changing it to const button = (todo) => <button onClick={ () => deleteTodo(todo.id); }>Delete</button> and access it by doing {button()}
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
This has the same callBack for each todo, you should move this inside your map so that todo.id refers to the iterator of the map():
{todos.map((todo) => (
<React.Fragment>
<div>ID: {todo.id} Task: {todo.text}</div>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</React.Fragment>
))}
Updated Demo:
const { useState } = React;
const App = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo));
setTodo("");
} else {
alert("Enter Valid Task");
setTodo("");
}
}
const deleteTodo = (id) => {
let updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<React.Fragment>
<div>ID: {todo.id} Task: {todo.text}</div>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</React.Fragment>
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Try this:
const button = (t) => <button onClick={() => deleteTodo(t.id)}>Delete</button>
and then, in the map
{todos.map((todo) => <div>ID: {todo.id} Task: {todo.text} {button(todo)}</div>)}
this way, the "delete todo" button will be bound to the specific todo ID, avoiding being bound to whatever the current value of todo is in the app.
Its better to update the state using setState. Muting the state directly breaks the primary principle of React's data flow (which is made to be unidirectional), making your app very fragile and basically ignoring the whole component lifecycle.
Also You need to change the delete from string to function and pass the id or place the jsx directly inside map function.
import React, { useState } from 'react'
const App = () => {
const [todos, setTodos] = useState([])
const [todo, setTodo] = useState('')
const handleSubmit = e => {
e.preventDefault()
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
}
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo))
setTodo('')
} else {
alert('Enter Valid Task')
setTodo('')
}
}
/*
* Changed Here
*/
const deleteTodo = id => {
setTodos(prevState => {
return prevState.filter(todo => todo?.id != id)
})
}
const button = id => <button onClick={() =>
deleteTodo(id)}>Delete</button>
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={e => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map(todo => (
<div key={todo.id}>
ID: {todo.id} Task: {todo.text} {button(todo.id)}
</div>
))}
</div>
)
}
export default App
Problem in:
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
You can use
const Button = (props) => {
return (
<button
className={`btn ${props.className}`}
title={`${props.title}`}
onClick={props.onClick ? () => props.onClick() : null}
>
{props.children}
</button>
);
};
after that, call it like this
<Button className="delete" title="delete" onClick={()=>deleteTodo(todo.id)}>Delete</Button>
I have following code.
What I'm trying to do is, in the first step, select one of the elements, store it in my state, and in the last step, console.log all my data. Also, the user can go from the last step to the first and change what he chose before. But the problem is that I can't save what the user selects for the first time.
For example, if the user selects the second one, and then on the last step they go back, then the first one is displayed as selected. How can I fix this?
here is my code
App.js
const [current, setCurrent] = useState(0);
const [data, setData] = useState({
firstName: "AAA",
lastName: "BBB",
age: 26
});
const steps = [
{
content: (
<PackageChoose setCurrent={setCurrent} data={data} setData={setData} />
),
id: 0
},
{
content: <LastStep setCurrent={setCurrent} data={data} />,
id: 1
}
];
return (
<div className="App">
<div>{steps[current].content}</div>
</div>
);
packageChoose (or first step)
const PackageChoose = ({ setCurrent, data, setData }) => {
const [selected, setSelected] = useState(1);
const [packageType, setPackageType] = useState(data.package || "choice");
return (
<div>
<div
onClick={() => {
setPackageType("choice");
setData({ ...data, packageType: packageType });
}}
>
<SelectCard
id={1}
selected={selected}
onSelect={setSelected}
text="text 1"
/>
</div>
<div
onClick={() => {
setPackageType("select");
setData({ ...data, packageType: packageType });
}}
>
<SelectCard
id={2}
selected={selected}
onSelect={setSelected}
text="text 2"
/>
</div>
<button onClick={() => setCurrent(1)}>Next</button>
</div>
);
};
Last step
const LastStep = ({ setCurrent, data }) => {
return (
<div>
LastStep
<button
onClick={() => {
setCurrent(0);
}}
>
Previous
</button>
<button onClick={() => console.log("data===>", data)}> submit </button>
</div>
);
};
Selected Card reusable component
const SelectCard = ({ id, selected, onSelect, text }) => {
const myClassName =
id === selected
? Styles.selectCardWrapperActives
: Styles.selectCardWrapper;
return (
<div className={classNames(myClassName)} onClick={() => onSelect(id)}>
<div> {text} </div>
</div>
);
};
Please help me to fix this problem.
You can move the selected state in PackageChoose to App level.
In App.js define the selected state and pass as props.
export default function App() {
const [selected, setSelected] = useState(1);
...
...
<PackageChoose
...
...
selected={selected}
setSelected={setSelected}
/>
}
In PackageChoose use the props passed above and remove the local selected state.
const PackageChoose = ({ setCurrent, data, setData, setSelected, selected }) => {
You need to update the packageType inside onClick handler. Since setState calls are batched and enqueued inside event handler and state updates may be asynchronous. You can't access the packageType state immediately after setting it.
PackageChoose.js
Card 1
onClick={() => setData({ ...data, packageType: "choice" })}
Card 2
onClick={() => setData({ ...data, packageType: "select" })}
set the packageType directly on data.
I use prop drilling to pass down the id value of the document, but every time I click on a document to update it using updateDoc, the same document gets updated(always the latest one added), not the one I clicked on. I don't understand why the unique IDs don't get passed down correctly to the function, or whether that's the problem. I use deleteDoc this way and it's working perfectly. Any help will be appreciated.
This is where I get the id value from
const getPosts = useCallback(async (id) => {
const data = await getDocs(postCollectionRef);
setPosts(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
});
useEffect(() => {
getPosts();
}, [deletePost]);
return (
<div className={classes.home}>
<ul className={classes.list}>
{posts.map((post) => (
<BlogCard
key={post.id}
id={post.id}
title={post.title}
image={post.image}
post={post.post}
date={post.date}
showModal={showModal}
setShowModal={setShowModal}
deletePost={() => {
deletePost(post.id);
}}
showUpdateModal={showUpdateModal}
setShowUpdateModal={setShowUpdateModal}
/>
))}
</ul>
</div>
);
This is where I pass through the id value to the update modal component for each document:
function BlogCard(props) {
const [postIsOpen, setPostIsOpen] = useState(false);
const modalHandler = () => {
props.setShowModal((prevState) => {
return (prevState = !prevState);
});
};
const updateModalHandler = () => {
props.setShowUpdateModal((prevState) => {
return (prevState = !prevState);
});
};
const handleView = () => {
setPostIsOpen((prevState) => {
return (prevState = !prevState);
});
};
return (
<>
{props.showUpdateModal && (
<UpdateModal
showUpdateModal={props.showUpdateModal}
setShowUpdateModal={props.setShowUpdateModal}
id={props.id}
title={props.title}
image={props.image}
post={props.post}
/>
)}
{props.showModal && (
<DeleteModal
showModal={props.showModal}
setShowModal={props.setShowModal}
deletePost={props.deletePost}
/>
)}
<div className={classes.blogCard} id={props.id}>
<div className={classes.head}>
<p className={classes.title}> {props.title}</p>
<div className={classes.buttons}>
<button className={classes.editButton} onClick={updateModalHandler}>
Edit
</button>
<button className={classes.removeButton} onClick={modalHandler}>
Delete
</button>
</div>
</div>
<p className={classes.date}>{props.date}</p>
<img src={props.image} alt="image" />
{!postIsOpen ? (
<p className={classes.viewHandler} onClick={handleView}>
Show More
</p>
) : (
<p className={classes.viewHandler} onClick={handleView}>
Show Less
</p>
)}
{postIsOpen && <p className={classes.article}>{props.post}</p>}
</div>
</>
);
}
export default BlogCard;
Here I create the function to update and add the onclick listener
function UpdateModal(props) {
const [title, setTitle] = useState(props.title);
const [image, setImage] = useState(props.image);
const [post, setPost] = useState(props.post);
const updateModalHandler = (prevState) => {
props.setShowUpdateModal((prevState = !prevState));
};
const updatePost = async (id) => {
const postDocRef = doc(db, "posts", id);
props.setShowUpdateModal(false);
try {
await updateDoc(postDocRef, {
title: title,
image: image,
post: post,
});
} catch (err) {
alert(err);
}
};
return (
<div onClick={updateModalHandler} className={classes.backdrop}>
<form onClick={(e) => e.stopPropagation()} className={classes.form}>
<label htmlFor="title">Title</label>
<input
id="title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<label htmlFor="image">Image(URL)</label>
<input
id="image"
type="text"
value={image}
onChange={(e) => setImage(e.target.value)}
/>
<label htmlFor="post">Post</label>
<textarea
id="post"
cols="30"
rows="30"
value={post}
onChange={(e) => setPost(e.target.value)}
/>
<div className={classes.buttons}>
<button className={classes.cancel} onClick={updateModalHandler}>Cancel</button>
<button className={classes.update} onClick={() => updatePost(props.id)}>Update</button>
</div>
</form>
</div>
);
}
export default UpdateModal;
This is the way my data is structured
firebase
I'm trying to build a CV builder which the user can input information such as the company, date, title, location and it gets saved inside an array when the user presses the save button. Then, that array is rendered in HTML form with a remove button.
I want that remove button to only delete one item of an array. For example, if we create two companies I've worked on it would create a remove button for both of them
I want that when we click the remove button once, not all the items of the array gets deleted which is what my code is currently doing. I can't figure out a logic to perform this and I've only tried this filter method but it deletes all of the items...
import React, { useState } from "react";
import '../styles/style.css'
const Experience = (props) => {
const [toggle, setToggle] = useState(false);
const [input, setInput] = useState({title: "", company: "", date: "", location: "", description: ""})
const [result, setResult] = useState([])
const togglePopup = () => {
setToggle(!toggle);
}
const saveForm = (e) => {
e.preventDefault()
setResult(result.concat(input))
}
const removeItems = (data) => {
setResult(result.filter(todo => todo.title === data.title));
}
console.log(result)
return(
<ul>
<button onClick={togglePopup}>Work Experience</button>
{result.map((data) => (
<>
<p key={data.title}>{data.title}</p>
<p>{data.company}</p>
<p>{data.date}</p>
<p>{data.location}</p>
<p>{data.description}</p>
<button onClick={removeItems}>Remove</button>
</>
))}
{toggle && <form>
<div>
<label>Job Title</label>
</div>
<div>
<input value={input.title} onChange={ e => {setInput({ title: e.target.value }) }}/>
</div>
<div>
<label>Company</label>
</div>
<div>
<input onChange={ e => {setInput({...input, company: e.target.value }) }}/>
</div>
<div>
<label>Date Worked (MM/YYYY - MM/YYYY)</label>
</div>
<div>
<input onChange={ e => {setInput({...input, date: e.target.value }) }}/>
</div>
<div>
<label>Location (e.g. Los Angeles, CA)</label>
</div>
<div>
<input onChange={ e => {setInput({...input, location: e.target.value }) }}/>
</div>
<div>
<label>Description</label>
</div>
<div>
<input onChange={ e => {setInput({...input, description: e.target.value }) }}/>
</div>
<button onClick={saveForm}>Save</button>
<button onClick={togglePopup}>Cancel</button>
</form>}
</ul>
)
}
export default Experience
You need to pass particular object to match and remove like-
<button onClick={() => removeItems(data)}>Remove</button>
Improved:
{result.map((data) => (
<>
<p key={data.title}>{data.title}</p>
<p>{data.company}</p>
<p>{data.date}</p>
<p>{data.location}</p>
<p>{data.description}</p>
<button onClick={() => removeItems(data)}>Remove</button>
</>
))}
function -> remove particular which is clicked.
const removeItems = (data) => {
const updated = result.filter(todo => todo.title !== data.title)
setResult([...updated]);
}
i have a backend api build on nodejs. in the code the api return some categories array.
i ran .map() function on the array to display the array in the JSX.
after that i added a checkBox to each object inside the array.
so what im trying to do is if the checkbox is true its will added another h1 Element (JSX).
Only to the object i clicked on the checkbox.
i tryied to add "status" props and make it false or true and then catch it with onClick e.target.status?
"YES" : "NO"
also, i tried to added checkBox useState and make it true or false . and its work. but not as i want
its display Yes or No to the all objects and not only to the on i clicked on.
const Category = ({ history }) => {
const dispatch = useDispatch()
const user = useSelector((state) => state.waiter)
const selectedCategory = useSelector((state) => state.selectedTable)
const [currectCategory, setCurrectCategory] = useState([])
const [categoryName, setCategoryName] = useState("")
const [categoryIMG, setCategoryIMG] = useState("not found")
const [checkBox, setCheckBox] = useState("false")
useEffect(() => {
if (!user.name) {
history.push('/login')
} else {
(async () => {
const res = await fetch('http://localhost:1000/categories/' + selectedCategory)
const data = await res.json()
setCurrectCategory(data.CurrectCountry.subcategories.map(sc => sc.name))
setCategoryName(data.CurrectCountry.name)
setCategoryIMG(data.CurrectCountry.img)
})()
}
}, [user])
const goBack = () => {
dispatch({
type: 'ALL_CATEGORIES'
})
history.push('/login')
}
const handleCheck = (e) => {
setCheckBox(e.target.checked.toString())
console.log(e.target.checked)
}
return (
<>
<Button className="LogButton" color="secondary" onClick={goBack}>back</Button>
<div className="SingleCategory">
<h1>{categoryName}</h1>
<ListGroup>
{currectCategory.map(category => {
return (
<Row className="Col-padd" key={category}>
<div>
<InputGroup className="mb-3">
<b className="ItemName"> {category} </b>
<img src={categoryIMG} height="100" width="100" ></img>
<FormCheck id={category} className="Checkbox" onChange={handleCheck}></FormCheck>
{checkBox == "true" ? <b>yes</b> : <b>No</b>}
</InputGroup>
</div>
</Row>
)
})}
</ListGroup>
</div>
</>
)
}
Thanks for help !!
You are only creating a single value for the checkbox. If you want to show for all the checkbox, if you have to track the value for each checkbox shown below,
const [checkBox, setCheckBox] = useState({}); // checkBoxName: value
const handleCheck = (e) => {
setCheckBox((prev) => {...prev, [e.target.name]: e.target.value};
}
{!!checkBox['name'] === true ? <b>yes</b> : <b>No</b>}
//change the attribute according to your implementation.
Your problem is that you're just creating a single value for the checkbox and not separating the individual checkboxes. You could solve this in many different ways, but you would be well served by extracting the code for your checkbox to a separate component.
const Checkbox = ({ category, categoryIMG }) => {
const [isChecked, setIsChecked] = useState(false);
const handleCheck = () => {
setIsChecked((prevState) => !prevState);
};
return (
<Row className="Col-padd" key={category}>
<div>
<InputGroup className="mb-3">
<b className="ItemName"> {category} </b>
<img src={categoryIMG} height="100" width="100"></img>
<FormCheck id={category} className="Checkbox" onChange={handleCheck}></FormCheck>
{isChecked == 'true' ? <b>yes</b> : <b>No</b>}
</InputGroup>
</div>
</Row>
);
};
With a separate checkbox component like above you could instantiate it like this in the map:
<ListGroup>
{currectCategory.map((category) => (
<Checkbox category={category} categoryIMG={categoryIMG} />
))}
</ListGroup>