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
Related
Hello everyone and thank you for reading this! Here is my problem that i can't solve:
My application has the following functionality:
There are 2 inputs, then a button, when clicked, 2 more inputs appear and a button to send data from all inputs to the console, however, in the additional field, one input is required. This is where my problem arises: now, if I called additional inputs and filled in all the data, they are transferred to the console, if I didn’t fill in the required field, an error message goes to the console, BUT. I also need, in the event that I did NOT call additional inputs, the data of 2 basic inputs was transferred to the console. At the moment I can't figure it out.
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
import produce from "immer";
const FunctionalBlock = ({
id,
idx,
isDeleted,
toggleBlockState,
additionalValue,
additionalTitle,
setNewBlock,
index,
}) => {
return (
<div
style={{
display: "flex",
maxWidth: "300px",
justifyContent: "space-between",
}}
>
{!isDeleted ? (
<React.Fragment>
<strong>{idx}</strong>
<input
type="text"
value={additionalTitle}
onChange={(e) => {
const additionalTitle = e.target.value;
setNewBlock((currentForm) =>
produce(currentForm, (v) => {
v[index].additionalTitle = additionalTitle;
})
);
}}
/>
<input
type="text"
value={additionalValue}
onChange={(e) => {
const additionalValue = e.target.value;
setNewBlock((currentForm) =>
produce(currentForm, (v) => {
v[index].additionalValue = additionalValue;
})
);
}}
/>
<button onClick={toggleBlockState}>now delete me</button>
</React.Fragment>
) : (
<button onClick={toggleBlockState}>REVIVE BLOCK</button>
)}
</div>
);
};
const Application = () => {
const [newBlock, setNewBlock] = useState([]);
const [firstInput, setFirstInput] = useState("");
const [secondInput, setSecondInput] = useState("");
const getNewBlock = (idx) => ({
id: Date.now(),
idx,
isDeleted: false,
additionalValue: "",
additionalTitle: "",
});
const toggleIsDeletedById = (id, block) => {
if (id !== block.id) return block;
return {
...block,
isDeleted: !block.isDeleted,
};
};
const createOnClick = () => {
const block = getNewBlock(newBlock.length + 1);
setNewBlock([...newBlock, block]);
};
const toggleBlockStateById = (id) => {
setNewBlock(newBlock.map((block) => toggleIsDeletedById(id, block)));
};
const showInputData = () => {
newBlock.map((item) => {
if (item.additionalTitle.length < 3) {
console.log("it is less than 3");
} else if (!item.additionalTitle && !item.additionalValue) {
console.log(firstInput, secondInput);
} else {
console.log(
firstInput,
secondInput,
item.additionalTitle,
item.additionalValue
);
}
});
};
return (
<div>
<div>
<input
type="text"
value={firstInput}
onChange={(e) => {
setFirstInput(e.target.value);
}}
/>
<input
type="text"
value={secondInput}
onChange={(e) => {
setSecondInput(e.target.value);
}}
/>
</div>
<div>
<button onClick={createOnClick}>ADD NEW INPUTS</button>
</div>
<div>
{newBlock.map((block, index) => (
<FunctionalBlock
key={index}
{...block}
toggleBlockState={() => toggleBlockStateById(block.id)}
setNewBlock={setNewBlock}
index={index}
/>
))}
</div>
<button onClick={showInputData}>send data</button>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Application />);
Here is this code on sandbox for those who decided to help me. Thank you!
https://codesandbox.io/s/vigilant-booth-xnef6t
as u see in the code I'm tryin to get the "ProductionName" from the server "getManuficturedProduction" and display that into the input and after that I want to get the input value and post it to the server but idk why my set state doesn't update and still show me the default value.when i log my set state "assignProductToProductionline" i can see that my "ProductionName" did not updated
const [assignProductToProductionline, SetAssignProductToProductionline] =
useState({
Id: "",
ProductionCode: "",
ProductionName: "",
});
useEffect(() => {
loadProductionLine();
}, []);
const [productionLineName, SetProductionLineName] = useState([]);
const loadProductionLine = async () => {
const result = await axios.get(
"url"
);
SetProductionLineName(result.data);
};
const getManuficturedProduction = async () => {
var res = await axios.get(
`url`
);
var GetInfo = res.data.Result;
SetAssignProductToProductionline({
ProductionName: `${GetInfo.Name}`,
});
};
const { Id, ProductionCode, ProductionName } = assignProductToProductionline;
const onInputChange = (e) => {
SetAssignProductToProductionline({
...assignProductToProductionline,
[e.target.name]: e.target.value,
});
};
const onSubmit = async (e) => {
await axios
.post(
"url",
assignProductToProductionline
)
.catch(function (error) {
if (error.response) {
return toast.error(error.response.data);
}
});
navigate("/productionLineProduct");
};
};
return (
<div className="container">
<div className="w-75 mx-auto shadow p-5 mt-5">
<form onSubmit={(e) => onSubmit(e)}>
<div className="form-group">
<select
className="form-control form-control-md mb-2 "
type="text"
name="Id"
value={Id}
onChange={(e) => onInputChange(e)}
autoComplete="off"
>
{productionLineName.map((cs) => (
<option key={cs.Id} value={cs.Id}>
{cs.ProductionLineName}
</option>
))}
</select>
</div>
<div className="form-group mb-2">
<input
id="customeProductionCode"
type="number"
className="form-control form-control-md"
name="ProductionCode"
value={ProductionCode}
onChange={(e) => onInputChange(e)}
autoComplete="off"
onInput={(e) => (e.target.value = e.target.value.slice(0, 9))}
/>
<a
className="btn btn-outline-success px-4"
onClick={(e) => getManuficturedProduction(e)}
>
check it
</a>
<div className="mt-2">
<input
className="text-success w-50"
name="ProductionName"
defaultValue=""
value={ProductionName}
placeholder={ProductionName}
onChange={(e) => onInputChange(e)}
/>
</div>
</div>
<button className="btn btn-primary w-25 ">save</button>
</form>
</div>
</div>
);
};
export default AssignProductToProductionLine;
{
you have to use assignProductToProductionline.ProductionName.
This line
const { Id, ProductionCode, ProductionName } = assignProductToProductionline;
creates a constant that is initialized with the first value and never changed.
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
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'm trying to learn react by coding, here i have come up with this code which works ! but only i need to know how to use if else or maybe ternary operator here. What i want to achieve is this: when user comes to the page this is already there :
{sisalto.map(({ avain, value }, index) => (
<div>
<div>
<IndexBox y={avain} />
</div>
<div>
<ValueBox value={value} />
</div>
</div>
))}
and when user writes something on input then this comes instead of the first one:
{searchResults.map(({ avain, value }, index) => (
<div>
<div>
<IndexBox y={avain} />
</div>
<div>
<ValueBox value={value} />
</div>
</div>
))}
my code :
function App() {
const [data, setData] = useState([])
const [searchResults, setSearchResults] = useState([])
const [searchTerm, setSearchTerm] = useState('')
const [sisalto, setSisalto] = useState([])
const fetchData = () => {
let corsAnywhere = 'https://cors-anywhere.herokuapp.com/'
let something = 'http://ksngfr.com/something.txt'
fetch(corsAnywhere + something)
.then(response => response.text())
.then(result => {
const theDataArr = result.replace(/\n/g, ' ')
const f = theDataArr.split(' ')
setData(f)
})
}
useEffect(() => {
fetchData()
}, [searchTerm])
useEffect(() => {
const mappedResult = data.map(d => {
var propertyK = d.split(':')[0]
var propertyv = d.split(':')[1]
return {
avain: propertyK,
value: propertyv
}
})
setSisalto(mappedResult)
const results = mappedResult.filter(each => each.avain === searchTerm)
setSearchResults(results)
}, [data, searchTerm])
console.log(sisalto)
return (
<div>
<header>
<div>
<h1>something</h1>
<input
type="text"
value={searchTerm}
placeholder="Search..."
onChange={e => setSearchTerm(e.target.value)}
/>
</div>
</header>
<div>
{searchResults.map(({ avain, value }, index) => (
<div>
<div>
<IndexBox y={avain} />
</div>
<div>
<ValueBox value={value} />
</div>
</div>
))}
</div>
</div>
)
}
export default App
data i'm fetching :
/* ------------------------
2005-07-09 03:05
1:74539
2:29734
3:95426
4:35489
------------------------ */
You can use Ternary Operator like this
<div>
{
isTrue
? (<div>YEP</div>)
: (<div>NO</div>)
}
</div>
Now you can use React.Fragment to achieve your goal like this -
<div>
{
isTrue
? (<React.Fragment>{/* map */}</React.Fragment>)
: (<React.Fragment>{/* map */}</React.Fragment>)
}
</div>
// React.Fragment shorthand
<div>
{
isTrue
? (<>{/* map */}</>)
: (<>{/* map */}</>)
}
</div>
If you don't want anyting in else statement just let it be null like this
{ isTrue ? <>YEP</> : null }