how to get react function to update state with user input - javascript

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.

Related

How to test the reaction to a component event in Svelte?

In Svelte, I have a parent component which listens to a component event dispatched by a child component.
I know how to use component.$on to check that the dispatched event does the right thing within the component which is dispatching, like so.
But I can't figure out how to check that the component which receives the dispatch does the right thing in response.
Here's a basic example:
Child.svelte
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
function handleSubmit(event) {
dispatch('results', 'some results')
}
</script>
<form on:submit|preventDefault={ handleSubmit }>
<button type='submit'>Submit</button>
</form>
Parent.svelte
<script>
import Child from './Child.svelte'
let showResults = false
function handleResults(event) {
showResults = true
}
</script>
<Child on:results={ handleResults } />
{ #if showResults }
<p id='results'>Some results.</p>
{ /if }
The idea is to eventually write a test using #testing-library/svelte like:
import { render } from '#testing-library/svelte'
import Parent from './Parent.svelte'
test('shows results when it receives them', () => {
const rendered = render(Parent)
// ***
// Simulate the `results` event from the child component?
// ***
// Check that the results appear.
})
If the parent were reacting to a DOM event, I would use fireEvent.
But I don't know how I would get a hold of the <Child> component in this case, and even if I could I'm guessing that Svelte is using a different mechanism for component events.
(Just to test it out, I used createEvent to fire a custom results event on one of the DOM elements rendered by <Child> but it didn't seem to do anything.)
Anyone have any ideas? Thanks!
If you're already planning on using #testing-library/svelte, I think the easiest way is not to try to manually trigger the Child component's results event, but to use Testing Library to grab the form/submit elements and trigger the submit event (using fireEvent a SubmitEvent on the <form> or their #testing-library/user-event library, or even a vanilla dispatchEvent). Svelte would then dispatch the custom results event that Parent is listening on.
Something like:
test('shows results when it receives them', async () => {
// Arrange
const rendered = render(Parent)
const submitButton = rendered.getByRole('button', {
name: /submit/i
});
const user = userEvent.setup();
// Act
await user.click(submitButton);
// Assert
const results = rendered.queryByText(/some results\./i);
expect(results).not.toBe(null);
});
Hope this is what you had in mind.
Edit:
For mocking Child.svelte, something like this in a __mocks__/Child.svelte should work:
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
function handleSubmit(event) {
dispatch("results", "some results");
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<button type="submit">Test</button>
</form>
Which is the exact same implementation as the actual module (I gave the button a different label just to make it clear it's the mocked version when querying it), but the idea is that this would never need to change and is only used to dispatch a results event. Then you'd just need to tell Jest or whatever you're using that you're mocking it (jest.mock("./Child.svelte");), change the getByRole query to match the new name (or just leave the mock with the original name), then it should just work.
Whether you think that's worth it or not is up to you. I've generally had success testing the UI as a whole rather than mocking sub-components, but I guess it comes down to preference. Yes, you might have to change the test if the Child component changes, but only if you change the label of the button or change the user interaction mechanism.
You don't need to know about the details of the components, you don't even need to know that it's split into a separate Child component, all the test would care about is a general idea of the structure of the UI—that there's a button called "Submit" and that clicking on it should show an additional <p> tag.

React - How to add a component to a string

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.

Multiple submissions in one Textfield overrides the previous value that was saved

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.

Warning: This synthetic event is reused for performance reasons happening with <input type="checkbox" />

I've been working on a simple react-redux todo example for a class and I came across several warning messages that show in the console everytime I check and uncheck a checkbox input.
You can see the warnings in the following images.
I also did a google search for the warning message but couldn't find any solution that works. Also, what stroke my attention was that it looks like it was trying to access every property of the native event, and DOM element.
This is the code for the presentational component that has the input checkbox
class TodoItem extends React.Component {
state = {
isChecked: false
};
handleCheckbox = () => {
this.setState({
isChecked: !this.state.isChecked
});
};
render() {
const { todos, onItemClick } = this.props;
const { isChecked } = this.state;
return (
<div>
<ul>
{todos.map((todo, id) => {
return (
<li key={id} onClick={onItemClick}>
<input
onChange={this.handleCheckbox}
type="checkbox"
checked={isChecked}
/>
<label>
<span />
{todo.textInput}
</label>
</li>
);
})}
</ul>
</div>
);
}
}
export default TodoItem;
I uploaded the example on CodeSandbox as well: https://codesandbox.io/s/k0mlxk1yqv
If you want to replicate this error you need to add an Item to the todo List and click the checkbox to check and uncheck a couple of times.
If anyone has any idea why this warning signs keep appearing and how to disable them I would appreciate your input very much :)
This happened because the event implicitly passed to onItemClick is used in an asynchronous context.
As Andre Lemay said, you should assign your needs to local variables and reference them.
In my case, I had this code:
handleInput = e => { // <-- e = synthetic event
this.setState(state => ({ // <-- asynchronous call
data: {
...state.data,
[e.target.name]: e.target.value // <-- this was causing the warnings (e.target is in an asynchronous context)
}
}));
};
Then I changed it to:
handleInput = e => {
const { name, value } = e.target; // <-- moved outside asynchronous context
this.setState(state => ({
data: {
...state.data,
[name]: value
}
}));
};
I'd suggest trying two solutions:
First change
onChange={this.handleCheckbox}
to
onChange={() => this.handleCheckbox()}
If that won't work, in 'handleCheckbox' add event.persist(); Like this:
handleCheckbox = (event) => {
event.persist();
this.setState({
isChecked: !this.state.isChecked
});
};
This may be a little late, but I just came across the same problem and solved in a way that I think might be better than Adam Orlov's answer. I don't believe either answer is directly applicable to the asked question, but this comes up when googling about synthentic events and checkboxes so it's as good a place as any...
I believe Adam is correct in his belief that React will essentially clear all properties of the SyntheticEvent object (which makes sense, since React is telling us that it's reusing the object).
However, unless you need the entire object, I don't think calling event.persist() is the best solution, as according to the documentation, that will remove the object from the pool (presumably they put it there for a good reason).
If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.
Instead of doing this, if you only need one or two values from the event object, you can just assign those to local variables, and then reference the local variables inside your own function, like this:
<input type="checkbox" onChange={(event) => {
let checked = event.currentTarget.checked; //store whatever values we need from from the event here
this._someOtherFunction(checked)
} />
In this way, you don't have to restructure your code in any way to avoid doing anything async that relies on event data, and you also don't have to worry about potential performance impacts as you allow React to do what it wants with the event pool.
Similar problem here though my setup is, functional component, Material UI <Textform /> input.
The guy above that mentioned event.persist();, thank you that worked for me, but the first suggestion had no noticeable affect, not sure if thats because Im using functional components and not class components. (I dont use class components anymore, only functional with hooks)
Also note the warning info suggested to use event.persist(). My issue was I was capturing form input using onChange and storing input into my state, after about the second or third character it would throw errors and also crash my app.
Before:
const handleChange = (e) => {
setState((form) => ({
...form,
[e.target.name]: e.target.value,
}));
};
After:
const handleChange = (e) => {
e.persist();
setState((form) => ({
...form,
[e.target.name]: e.target.value,
}));
};
So it appears this is the correct solution to a similar issue, while I was not using a checkbox, I was using a form input, a Material-UI <TextField />. I can remove the single line of
e.persist();
and the code will fail again, add it back, everything is good no crashing no warnings.
for the ones that came to this problem with react native.
i face this problem on a navigation
PersonalProductsScreen.navigationOptions=({navigation})=>{
const handleEditButton=navigation.navigate.bind(this,"EditProduct")
return {
headerTitle:"My Products",
headerRight:<CustomHeaderButton
iconName="ios-add"
title="Add"
iconSize={26}
color={colors.bright}
onPress={handleEditButton}
/>
}
}
pay attention to the method i used . I was trying to bind the navigate method.
This is the refactor:
const handleAddButton=()=>navigation.navigate("EditProduct")

What's the correct way of accessing input fields in React-Redux?

So I have this application which uses Redux to store some data. Then I have the Search component, which I originally wanted to be a plain dumb component. However, there is an input field whose value I would like to access and pass on to do something with it. However, I am not sure how to manage the value of this input field, that is, how to access it & where to store the value. I want to use Redux and be consistent, so that's my main problem. I have found the following solution (which works), but this does not look like Redux to me anymore? Am I violating any Redux specific rules with this?
On the other hand, how would I do it with Redux? With a reducer & action etc. just for this one input field in one component? This seems like a bit too much, but please enlighten me!
class Search extends React.Component{
constructor(props) {
super(props);
this.state = {
username: ""
};
this.handleUsernameChange = this.handleUsernameChange.bind(this);
}
handleUsernameChange(evt) {
console.log("Helo" + evt.target.value);
this.setState({
username: evt.target.value
});
}
onSubmit(e) {
e.preventDefault();
console.log("Hello" + e);
/* Do something... */
}
render() {
// Return JSX via render()
return (
<div className="">
<h1>Youtube Link</h1>
<input className="form-control" onChange={this.handleUsernameChange}></input>
<button className="btn btn-large btn-positive" onClick={this.onSubmit}>Download</button>
</div>
);
}
}
// Export Search
export default Search
"I want to use Redux and be consistent, so that's my main problem."
That's a valid reason to use Redux for this use case. But it is also fine to use combination of Redux and local component state in your application. I think this is a perfect example on when local component state is a good solution. I wrote a blog post on this topic. If you want, you can take a look here: http://blog.jakoblind.no/2017/02/08/is-using-a-mix-of-redux-state-and-react-local-component-state-ok/
On the other hand, how would I do it with Redux? With a reducer & action etc. just for this one input field in one component? This seems like a bit too much, but please enlighten me!
Yes, this is how you would do it with Redux. If you need the the username value anywhere else in your app, then this is the approach you should take.
If you don't want to write your own actions and reducer and everything for it, you could look into redux-form to handle it for you, but it might be a bit overkill if there is not much other input required from the user.
If you only need it when you hit that button right below it, then what you have is fine as you can just raise your action with the value from the components state, e.g,
onSubmit(e) {
e.preventDefault();
console.log("Hello" + e);
/* Do something... */
this.props.downloadTheThing(this.state.username)
}
where downloadTheThing is mapped in your mapDispatchToProps function when connecting the component (assuming you are using react-redux).
It is definitely fine to use local state in Redux.
From the code snippet you shared, you don't even need to use a local state.
constructor(props) {
...
this.username = '';
...
}
onSubmit() {
console.log('Hello ' + this.username);
}
render() {
return (
...
<input type="text" onChange={e => this.username = e.target.value} />
...
);
}
<input className="form-control" onChange={this.handleUsernameChange}></input>
that is your input field. first thing todo when you work on input in react is to set the value and then apply onChange. with value we will assign the state of the input to the state of the component so we will get single source of truth.
since you want to work on redux store, I assume you already have your redux store, reducers and actions set in your application. reducers hold the state and you send message to reducers via dispatch(). You access to dispatch() on this.props.dispatch() once you define connect() (). By default, a connected component receives props.dispatch()
import React from "react"; //anywhere you are using jsx, u should import this
import { connect } from "react-redux";
import setInputAction from "relative path" //actions are the messages that you send to reducers. reducers change the state based on the actions.
//this allows you access to state in Redux-store via the props.
const mapStateToProps = state => {
return {
reduxState: state
};
};
export default connect(mapStateToProps)(Search);
this code is kinda configuration of your component to communicate with the redux-store.
Now let's focus on input field. we have to set the value:
<input
type="text"
value={this.props.reduxState.input}// i assume you have `input:""` in the state. u can name anything you want.
onChange={e => {
this.props.dispatch(setInputAction(e.target.value));//we are sending e.target.value to the reducer via dispatch().
}}
/>
setInputAction is just an object that sent to reducer. Reducers are already defined what to do under certain actions. So you just send the message and reducer will change the state accordingly.

Categories

Resources