I have this form with multiple checkboxes and below it, I also have the others where the user can enter any value. The problem is that if I'll enter a value for the 2nd time, it will remove the previous value entered by the user.
Assuming that I've entered books for my first submit. Now, I want to submit another value for the others again, but this time it will be movies. I want to save in the firestore the both of these values; books and movies. The problem is that if I'll submit movies, this will override the previous one books, meaning it will replace books. How can I avoid that and at the same time display the multiple values entered by the user in the field others?
Below are the codes:
const sample = (props) => {
const [others, setOthers] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
try {
const user = firestore.collection("users").doc(id);
const ref = user.set(
{
1: {
choices,
others
}
},
{ merge: true }
);
console.log(" saved");
} catch (error) {
console.log(error);
}
};
return (
<>
<form onSubmit={handleSubmit}>
<FormGroup>
//codes for the checkboxes here
<TextField
type="text"
label="Others:"
value={others}
onChange={(e) => setOthers(e.target.value)}
multiline
/>
</FormGroup>
<button type="submit">Submit</button>
<br />
</form>
</>
);
};
export default sample;
im going to preface this with im not familiar with react, but i do mess around with firestore alot. so the syntax maybe different for you.
but the first thing i notice is that you're using const ref = user.set to make the document. this is fine for first time creating a document, but if you use '.set' on an existing document it will override all the data in that document with whatever you're attempting to update it with.
you should use const ref = user.update to update fields in the document.
the 2nd bit is lets say you want to update the 'others' field. it would still override the data in that field even if you use '.update'. update is doing just that, its updating the field in question with whatever you're trying to update it with. what you want to do is add to it.
so your 'others' field needs to be an array and in order to add new values into it without overriding the previous data you need to use an arrayUnion.
const handleSubmit = (e) => {
e.preventDefault();
try {
const user = firestore.collection("users").doc(id);
const ref = user.update(
{
1: {
choices,
others: firebase.firestore.FieldValue.arrayUnion(others),
}
},
{ merge: true }
);
console.log(" saved");
} catch (error) {
console.log(error);
}
};
now i dont know how imports work in react but in VUEjs you'd need to import import firebase from "firebase/compat/app"; in the script tag in order to use the that firebase feature.
if you want to remove an item from that others array then use.
const handleSubmit = (e) => {
e.preventDefault();
try {
const user = firestore.collection("users").doc(id);
const ref = user.update(
{
1: {
choices,
others: firebase.firestore.FieldValue.arrayRemove(item), //item = whatever it is you're trying to remove.
}
},
{ merge: true }
);
console.log(" saved");
} catch (error) {
console.log(error);
}
};
From the React docs,
this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument.
prevState is a name to the argument passed to setState callback function. What it holds is the value of state before the setState was triggered by React.
So if multiple setState calls are updating the same state, batching setState calls may lead to incorrect state being set. Consider an example.
Your 'others' field needs to be an array and in order to add new values into it without overriding the previous data you need to use prevState.
If you don’t want to use setState you can use
prevState in useState React Hook with Javascript spread operator to
concatenate the previous values with current values in array
Something like this.
Related
I have written a function in react that, through a rendered submit form, takes a user's input, uses it to query an api and updates p tag content in the form. This is the following code:
var textfield = ""
function Form (){
const [ name, getNFT ] = useState("")
let postName = (e) =>{
let output
async function getInfo(e) {
e.preventDefault()
try {
const resp = await axios.post("/hey_honey", {
name
})
console.log(resp.data)
output = resp.data
return output
} catch (error) {
console.error(error)
}
}
getInfo(e).then(output =>{console.log("output is outside of function scope", output)
})
textfield = output
return textfield
}
return (
<div className="App">
<form onSubmit={postName}>
<input type="text" value={name} onChange={(e) => getNFT(e.target.value)}/>
<button type="submit" >Get Collection's Gallery</button>
<p>{textfield}</p>
</form>
</div>
)
}
I have successfully gotten the JSON string that I wanted. The problem is I seemingly have no way of updating the rendered form because it is either outside of postName's function scope or anything modified by postName even if instantiated outside of form cannot actually change the states.
At one point I thought that this was most likely because this is a more appropriate instance of using a class, I however can't use useState and therefore would have no way of storing the content written out in the submit form. What am I missing here?"
(Edit: after some research there are ways I can do this with a class. I technically don't need to utilize useState for a form but rather I could use setState and simply instantiate setState as the empty string.
It would look something like this, and say for the sake of things we just have a function that simply returns the input as opposed to the async function:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: '', textfield: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
this.setState({textfield: event.target.value})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
<p>{this.state.textfield}</p>
</form>
);
}
}
However we still run into the issue of updating the form itself.)
Edit
Is this a specific issue with forms elements? When someone is posting a comment on a blog is there a different component entirely that I should be using? I was drawn to forms because they take user input and then give you something that you can manipulate but is it maybe that they are immutable once you set them up?
At the time of writing this I am beginning to consider that this is when I should export a stored value, say textfield in my case, and just pass it into an entirely new component. Will update.
The problem is I seemingly have no way of updating the rendered form because it is either outside of postName's function scope or anything modified by postName even if instantiated outside of form cannot actually change the states.
When it comes to React, at the end of the day, if you want to make it re-render you need to update React state. What you're doing here with textfield is updating a JS variable, but React doesn't have a way of knowing that that variable is different, so it won't know to re-render.
Notice what you're doing with your input. Because you've set value, it's a "controlled input" and therefore the only reason you can see the text you type is because you're updating React state and triggering a re-render with every key press.
You can do the same thing with the result of your network request. Once the request finishes, you can update a piece of state, which will trigger the re-render. Note that if you don't use that state (e.g. by rendering out some of it) you won't be able to tell that React has re-rendered just from looking at it the browser.
Here's a minimally different example that makes a network request based on your form input and renders the result. (The variable names don't make a ton of sense for this API, just wanted to keep the diff small).
function Form() {
const [name, getNFT] = useState("");
const [jokes, setJokes] = useState([]);
let postName = (e) => {
let output;
async function getInfo(e) {
e.preventDefault();
try {
// swapped this out to a free public API so we could see a result
const resp = await axios.get(`https://icanhazdadjoke.com/search?term=${name}`, {
headers: {
// you need to tell this API you want the results as JSON, instead of HTML (the default)
'Accept': 'application/json'
}
});
console.log(resp.data);
output = resp.data;
return output;
} catch (error) {
console.error(error);
}
}
getInfo(e).then((output) => {
console.log("output is outside of function scope", output);
// this is the big change!
setJokes(output.results);
});
};
return (
<div className="App">
<form onSubmit={postName}>
<input
type="text"
value={name}
onChange={(e) => getNFT(e.target.value)}
/>
<button type="submit">Get Collection's Gallery</button>
<p>{jokes[0]?.joke}</p>
</form>
</div>
);
}
And here's a version that does the same thing but cleans it up a little bit, just to compare:
// this is a pure js function - no react! It just makes the request and returns the result
async function searchJokes(jokeTerm) {
try {
// swapped this out so we could see a result
const jokesResponse = await axios.get(
`https://icanhazdadjoke.com/search?term=${jokeTerm}`,
{
headers: {
// you need to tell this API you want the results as JSON, instead of HTML (the default)
Accept: "application/json"
}
}
);
console.log("jokes response", jokesResponse);
return jokesResponse.data;
} catch (error) {
console.error(error);
}
}
function Form() {
const [searchTerm, setSearchTerm] = useState("");
const [jokes, setJokes] = useState([]);
return (
<div className="App">
<form
onSubmit={async (e) => {
e.preventDefault();
// whereas the pure js function can just return its request normally, inside here we need
// to take the result and stick it in React state.
const jokesData = await searchJokes(searchTerm);
setJokes(jokesData.results);
}}
>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<button type="submit">Search Jokes</button>
<p>{jokes[0]?.joke}</p>
</form>
</div>
);
}
(Sandbox). To be clear, the above is definitely not perfect!
getInfo(e).then(output =>{console.log("output is outside of function scope", output)
})
textfield = output
return textfield
Setting aside React, one thing to remember is that the code inside the function inside then will run when getInfo(e) is done fetching its information from the internet, but the lines after run right away without waiting, because the getInfo call returns a Promise. In JS, async logic like that is non-blocking! You can see above how you use await to avoid needing a then and the nesting. (But, it's worth remembering that async/await is just fancy syntax for Promises and .then, so anything you can do with one you can do with the other).
Also, because of function scoping, you won't be able to access output outside of the then function. You'd have to do that variable assignment inside that function, or use async await to flatten things a bit.
At one point I thought that this was most likely because this is a more appropriate instance of using a class, I however can't use useState and therefore would have no way of storing the content written out in the submit form. What am I missing here?
In React, it used to be that you needed classes for more complex components, like ones with state, and could only use functions for presentational components. But ever since they introduced hooks, class components and functional components can do the same things in different ways, so what you're doing here is fine! I would stick with function components like you've got while you're learning.
Outside of React, classes are a good option when dealing with state (though you can also do equivalent things with nested functions). But you'd still need some way to map updates to your vanilla JS class state to React state. To start, I would focus mostly on storing state within React, and as you build bigger apps you can either spend some time learning how to tie them into your own custom classes, or bring in a state management library (Redux is probably the most well known) to handle that for you.
Probably it is a classic issue with useState which is not updating.
So there is a tree with some checkboxes, some of them are already checked as they map some data from an endpoint.
The user has the possibility to check/uncheck them. There is a "cancel" button that should reset them to the original form.
Here is the code:
const [originalValues, setOriginalValues] = useState<string[]>([]);
...
const handleCancel = () => {
const originalValues = myData || []; //myData is the original data stored in a const
setOriginalValues(() => [...myData]);
};
...
useEffect(() => {
setOriginalValues(originalValues);
}, [originalValues]);
However, it is not working, the tree is not updating as it should. Is it something wrong here?
Just do the following, no need for ()=> the state will update inside the hook if called, plus change the constant it will cause confusion inside your code and protentional name clash later on, with the current state variable name, and also make sure your data are there and you are not injection empty array !!!! which could be the case as well !.
// Make sure data are available
console.log(myData)
// Then change the state
setOriginalValues([...myData]);
I currently have a local React website (first project, so learning as I go) which has a select field that is pulling the options in from a database. That bit is fine. When I create a click function "onChange" to then get data from the database, this works fine.
My issue is that I want to be able to grab the data from the JSON data and append the data into a component. I currently have the following component set up, which works when I add this onto the page manually:
<QuotePhaseTitle title="Test Title" style="primary" />
So what I basically want to do is within the "onChange" function, get the data (which I can do easily enough) and then pass that to the "title" and "style" props. Once that has been passed, I then need to be able to return that data and input into the page somewhere.
Below is an example of the function so far (I am using WPAPI):
const quoteTypeChange = async (e) => {
e.preventDefault();
const optionValue = e.target.value;
try {
await wp.quoteType().id(optionValue).then((data) => {
const quoteTypeDetails = data;
// Ideall want to pass in the <QuotePhaseTitle title="Test Title" style="primary" /> component, add in the data and then display that on the page //
}).catch((error) => {
// Error //
});
} catch (error) {
// Error //
}
}
How would I go about doing this? Sorry if this is a basic question.
The code itself doesn't "pass values to components", it doesn't really interact with components at all in general. The code updates state. The components use state. So your component might define two state values, for example:
const [title, setTitle] = useState('Test Title');
const [style, setStyle] = useState('primary');
And you would use that state in the JSX:
<QuotePhaseTitle title={title} style={style} />
Then all you need to do is update the state values:
wp.quoteType().id(optionValue).then((data) => {
setTitle(data.someValue);
setStyle(data.someOtherValue);
})
Structurally this is fundamental to React. State drives display, logic updates state.
You need to create a state, so when the data comes from server, you put them on the state for example this.setState({ title: data.title }) o using hooks const [title, setTitle] = useState(); setState(data.title);
And then pass the title value to your component: <QuotePhaseTitle title={this.state.title} style="primary" /> of <QuotePhaseTitle title={title} style="primary" /> if you are using hooks.
Also you can instantiate the hook value or the state with a default value.
I have an array that shows a set of items attached to a user. The array checks it first if the current user is on the item's inventory and then displayed as 'user name' and 'date borrowed' on a table. The adding feature is done in a modal, and suppose to update the table.
The problem is everytime I add, delete or update, the table doesn't update at all. Also this table is an expandend component of another table (react-data-table-component)
Here is the useState, and useEffect of my table:
const InventoryTable= ({
selectedUser,
items,
getItems,
getUsers
}) => {
useEffect(() => {
getItems();
getUsers();
}, []);
const [data, setData] = useState([]);
useEffect(() => {
let data= [];
data= items?.filter((item) =>
item?.users.some(
(user) => parseInt(user?.id) === parseInt(selectedUser?._id)
)
);
setData(data);
}, []);
Note: selectedUser, is the user from the main table that was selected and this current table is to show the itms attached to it.
If I add data on the setData(data); }, []); it crashes.
Adding data, selectedUser, and items on the dependency arrays loads it non-stop that causes to crash the page
useEffect method takes 2 parameters as input.
callback
dependency
If the dependency is empty array it will be called in the similar way as Class Component with componentDidMount and componentWillUnmount lifecycle methods.
if dependency is present then the callback will be called after the UI in painted.
So clearly you have missed required dependency
I'm not sure that i understand the whole concepts of your code because it is just a small part of the code. But useEffect() will run accordingly if you want it to run as ComponenentDidMount you will use the code that you said above however in your case you want to update delete add it means you want to detect the change in data so you need to include data within the brackets like this
`useEffect(() => {
let data= [];
data= items?.filter((item) =>
item?.users.some(
(user) => parseInt(user?.id) === parseInt(selectedUser?._id)
)
);
setData(data);
}, [data,items,selectedUser]);`
Seems you forgot to pass items and selectedUser to effect dependency array.
Maybe I am wrong, but have you tried renaming data property inside useEffect? It should have problem, that you are setting state without set callback.
Try also set useState default value to [] instead of {} (object).
I am trying to figure out redux but I am stuck. I have an input that is in the header section and within the body section, I have a component that receives the data from the parent and renders them as list items. So far so good.
I have wired my input search text to fire a redux action on each key press.
my actions file
export const jobsFilterDisplay = (jobsList, filterValue) => async dispatch => {
if (!filterValue){
console.log("you should call fetch all again");
console.log(filterValue);
jobsFetchAll();
} else {
console.log("original list");
console.log(jobsList); // list of jobs - original and unfiltered
console.log(typeof jobsList); // returns object
console.log("filter value"); //
console.log(filterValue); // my search text input value
//
// I think this is where I am supposed to filter the original list
// and dispatch an event with the filtered object as the payload
// in my reducer, I will assign the payload to my list
// my list component should update automatically and render updated list
// dispatch({ type: JOBS_LIST_FILTERED, payload: filterValue });
}
}
I want to be able to search by customer name or any attribute. Whenever I tried _.filter or anything like that, I always ended up either not a function or undefined and I've been trying for more a long time.
my json example - part of it
{"jobs":[{"id":1,"ticket":{"number":121,"quote":12321,"tech":"Tech 1","date":"06-22-2017","status":1},"customer":{"name":"John","address":"USA"}},{"id":2,"ticket":{"number":1231231,"quote":21123,"tech":"Tech 4","date":"06-22-2017","status":2},"customer":{"name":"Doe","address":"CANADA"}}]}
Any help is appreciated . Thank you!
You can use Array.prototype.filter() for this. Are you using lodash currently? That is also possible. But I recommend Array.prototype.filter() approach. Here's how you can do it.
const payload =
[{"id":1,"ticket":{"number":121,"quote":12321,"tech":"Tech 1","date":"06-22-2017","status":1}, "customer":{"name":"John","address":"USA"}},
{"id":2,"ticket":{"number":1231231,"quote":21123,"tech":"Tech 4","date":"06-22-2017","status":2},"customer":{"name":"Doe","address":"CANADA"}}]
function filterByCustomerName(customerName, payload) {
return payload.filter(job => job.customer.name === customerName);
}
const job = filterByCustomerName('Doe', payload);
console.log(job);
Anyway If you really need to use lodash, make sure you import it like this. Then use the filter from the lodash library. But still I don't see any fair reason for you to go for it.
import _ from 'lodash';
function fetJobByCustomer(customerName, payload) {
return _.filter(payload, job => job.customer.name === customerName);
});
}
Hope this helps. Happy Coding !