How to save input default value in ReactJS - reactjs

I'm working on my first project and I found another problem. My professor told me, my edit form can't be empty at start and it has to store default values. And that's what I've made. Sadly I found it problematic. What's wrong?
First thing:
*My default input can't be changed.
Second:
*Even when my default value is visible for me, after POST it sends "" as value.
I've tried a lot of things, like setting value from this.state.userResponse.map, but ofcourse it fails. Even tried to escape React and save default values into LocalStorage only to make it work.
class EditProtege extends Component {
state = {
firstname: "",
userResponse: []
};
getUserByID() {
fetch(`http://localhost:9000/proteges/${utils.i}`)
.then(res => res.json())
.then(res => this.setState({ userResponse: res }))
.catch(err => err);
}
handleNameChange = event => {
this.setState({ firstname: event.target.value });
};
handleSubmit = event => {
event.preventDefault();
axios
.put(`http://localhost:9000/proteges/${utils.i}`, {
firstname: this.state.firstname,
})
.then(res => {
console.log(res);
console.log(res.data);
});
window.location.reload();
};
componentDidMount() {
this.getUserByID();
//this.setDefaultValues();
}
render() {
return (
<div className="creation-form">
{this.state.userResponse.map(resp => (
<div>
<br />
<rb.Form onSubmit={this.handleSubmit}>
<rb.FormGroup>
<rb.Row>
<rb.Col md={12}>
<rb.FormControl
type="text"
name="firstname"
size="md"
onChange={this.handleNameChange}
placeholder="Name"
default={this.userResponse.proteges}
required
/>
</rb.Col>
</rb.FormGroup>
<rb.Button type="submit" variant="dark" size="lg" block>
Zatwierdź edycję
</rb.Button>
</rb.FormGroup>
</rb.Form>
</div>
))}
</div>
);
}
}
export default EditProtege;
Like I've said before, I want to POST default value and to be able to change it if I want to. Now its untouchable and sends empty string.
UPDATE: changed default into defaultValue and it seems like I can edit it again, still, problem with saving this data still occurs.

I would suggest setting your state's firstName to a default value, then use the value attribute on your rb input component and set it to this.state.firstName while still keeping the onChange in place and get rid of the default attribute.
This way, each time you change the input value of your field, the state will be reset effectively, and it will also update the input field value. When you send your post request with the current state data, it should be accurate as well.

Related

ReactJS fetch data from backend, show in a form, and send data back

In my ReactJS frontend I am building a form where the user can view and edit settings. The form is part of a function that does the following:
Call the backend to get the settigs. Store the result from backend in variable data
Map/show the data in text fields
When the user clicks "Submit" then send changes as json back to the backend.
This is a picture of the flow:
This is a picture of the ReactApp:
This is my code so far:
import { useContext, useEffect, useState } from "react";
export function Settings() {
const [data, setData] = useState([]);
// Send general settings
const handleSubmit = async (e) => {
e.preventDefault();
let result = await fetch("https://localhost:5002/api/update_settings", {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: data,
});
let resultJson = await result.json();
let resultMessage = resultJson['message']
let resulData = resultJson['data']
let resultError = resultJson['error']
if (result.status === 200 || result.status === 201) {
document.getElementById("feedback_div").style.display = "block";
document.getElementById("feedback_p").innerHTML = resultMessage;
}
else{
document.getElementById("feedback_div").style.display = "block";
document.getElementById("feedback_p").innerHTML = resultError + " " + resultMessage;
}
};
useEffect(() => {
fetch('https://localhost:5002/api/get_settings')
.then(response => response.json())
.then(json => setData(json))
}, []);
return (
<div>
<h1>Settings</h1>
{/* Feedback */}
<div id="feedback_div" style={{display: "none"}}><p id='feedback_p'>Feedback box is here</p></div>
{/* Form */}
<form onSubmit={handleSubmit}>
<label>
<p>Title</p>
<input type="text" name="inp_title" value={data?.settings_website_title} onChange={(e) => setData(e.target.value)} />
</label>
<label>
<p>Title short</p>
<input type="text" name="inp_title_short" value={data?.settings_website_title_short} onChange={(e) => setData(e.target.value)} />
</label>
<p><button>Submit</button></p>
</form>
</div>
);
}
export default Settings;
Backend get_settings return value:
{
"settings_website_title", "My Website",
"settings_website_title_short", "MyWeb"
}
My problems:
How can I send the data back to the backend? I belive that when the user makes changes into the text box I call onChange={(e) => setData(e.target.value)} but I do not think this is correct? Because data should be the JSON, and not a single value.
Also I get this error on this code:
Warning: A component is changing an uncontrolled input to be
controlled. This is likely caused by the value changing from undefined
to a defined value, which should not happen. Decide between using a
controlled or uncontrolled input element for the lifetime of the
component. More info: https://reactjs.org/link/controlled-components
but I do not think this is correct? Because data should be the JSON, and not a single value.
Indeed. Instead of setting the entirety of state to one value, set it to an updated version of itself in which that one value is changed. For example:
<input
type="text"
name="inp_title"
value={data?.settings_website_title}
onChange={(e) => setData({ ...data, settings_website_title: e.target.value })}
/>
You can extract multiple settings into a single update handler if you align the name of the element with the property being updated. For example:
<input
type="text"
name="settings_website_title"
value={data?.settings_website_title}
onChange={handleChange}
/>
and...
<input
type="text"
name="settings_website_title_short"
value={data?.settings_website_title_short}
onChange={handleChange}
/>
Then your handleChange function can be something like:
const handleChange = e => {
setData({
...data,
[e.target.name]: e.target.value
});
};
In either case, you're replacing the state object with a copy of itself, changing only the one property updated by that <input> value.
Warning: A component is changing an uncontrolled input to be controlled...
This is because the initial values for your inputs are undefined (or possibly null). An empty string would be preferable in this case. Instead of using optional chaining for the values, initialize state to a default. For example:
const [data, setData] = useState({
settings_website_title: '',
settings_website_title_short: ''
});
Then you don't need the optional chaining in the value properties on your <input> elements:
value={data.settings_website_title}
Since data will always be a valid object with the properties you're looking for.
You are in right direction but doing small mistakes. Please follow below instructions to save the data successfully.
// 1. initialize your form inputs here,, it will be used to send data in api request body
const [data, setData] = useState({
settings_website_title: "",
settings_website_title_short: ""
});
// 2. You can't override the data in #data varaible. So your input will look something like this.
<input type="text" name="inp_title" value={data?.settings_website_title} onChange={(e) => setData({...data, settings_website_title: e.target.value})} />
// 3. on click of submit button call the method where you make api call
<button onClick={(e) => {handleSubmit()}}>Submit</button>

Updating state within React Hook Form

I have a very simple submit form that I'm using React-hook-form to implement and I'm running into this strange issue where the global state isn't updated when I submit the first time, but it works the second time. Here's my code:
export default function Enter() {
const { register, handleSubmit, watch, formState: { errors } } = useForm();
const { state, dispatch } = useContext(Store)
const onSubmit = (data) => {
console.log('sending user: ', data.username)
dispatch({
type: 'SET_PLAYER',
payload: data.username
})
console.log('UPDATED CLIENT STATE: ', state)
}
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<p>Enter your name to join lobby</p>
<input {...register("username", { required: true })} />
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
</>
);
}
Here's a picture of how state appears to be lagging behind, essentially:
Dominik's comment was spot-on. Even though updates to state are synchronous, state updates in between function refreshes so you should use data within the function and if you need to do something after that updates, then use useEffect and wait for the state change.

React useState hook - when to use previous state when updating state?

In the code below, the form is not updated correctly unless I pass in previous state to the state setting call ('setForm').
To see it in action, run the code as-is, and the console will print 'true', indicating it worked. That's using 'validate1' function. If you replace that with 'validate2' (which doesn't use previous state), it fails to print 'true'.
It seems like in validate2, the second setForm call overwrites the form.long state of the first setForm call, due to the async nature of these calls. But why does validate1 work? Why does using previous state cause it to work? Any documentation on this behavior would be really helpful.
(I can make validate2 work by having one setForm that sets both fields, but the code below is contrived to show a difference in the two ways setForm can be called, with and without previous state.)
CodeSandbox
const {useState, useEffect} = React;
function LoginForm() {
const [form, setForm] = useState({
username: "",
password: "",
long: null
});
useEffect(() => {
console.log(form.long);
}, [form.long]);
const validate1 = (e) => {
e.preventDefault();
setForm((prevState) => ({
...prevState,
long: form.password.length >=3 ? true : false
}));
setForm((prevState) => ({
...prevState,
username: "*****"
}));
};
const validate2 = (e) => {
e.preventDefault();
setForm({
...form,
long: form.password.length >=3 ? true : false
});
setForm({
...form,
username: "*****"
});
};
const updateField = (e) => {
setForm({
...form,
[e.target.name]: e.target.value
});
};
return (
<form onSubmit={validate1}>
<label>
Username:
<input value={form.username} name="username" onChange={updateField} />
</label>
<br />
<label>
Password:
<input
value={form.password}
name="password"
type="password"
onChange={updateField}
/>
</label>
<br />
<button>Submit</button>
</form>
);
}
// Render it
ReactDOM.render(
<LoginForm/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
In the React documentation, they mention the following,
If the next state depends on the current state, we recommend using the
updater function form, instead:
this.setState((state) => {
return {quantity: state.quantity + 1};
});
That's the reason validate1 works, because the second call to setForm depends on the previous setForm call state, and the state will be up to date if the updater function form is used.
Why does validate1 work though? Why does using previous state cause it to work?
setForm({
...form,
long: form.password.length >=3 ? true : false
});
setForm({
...form,
username: "*****"
});
The value of form in the second setForm is still the old value until the next re-render. It doesn't reflect the updated form from the previous setForm.
React setState and useState does not make changes directly to the state object.
setState and useState create queues for React core to update the state object of a React component.
So the process to update React state is asynchronous for performance reasons. That’s why changes don’t feel immediate.
https://linguinecode.com/post/why-react-setstate-usestate-does-not-update-immediately

Wrong order of selectedItem while using onChange in a dropdown & using material-ui

Well, I have a dropdown and I want to use the onChange() selectedItem to then call an API and render the output using map.
My code looks something like this:
TaskSideBar.js
const taskAPI = 'https://pokeapi.co/api/v2/';
export default class TaskSideBar extends Component {
constructor(props) {
super(props);
this.state = {
pokeData: [],
pokeIndex: null,
isLoading: false,
error: null,
};
this.handleDropdownChange = this.handleDropdownChange.bind(this);
}
componentDidMount() {
}
handleDropdownChange(e) {
this.setState({selectedValue: e.target.value})
fetch(taskAPI + this.state.selectedValue )
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('something is wrong');
}
})
.then (data => this.setState({ pokeData: data.results, pokeIndex: 0, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
render () {
const { pokeData, pokeIndex, isLoading, error, selectedValue, renderRow } = this.state;
const classes = this.props;
return (
<>
<Tabs tabs={['Reports', 'Graphs', 'Sheets']} className={classes.sidebarTabs}>
<>
<h3>Reports</h3>
<List source={reportItems} />
</>
<>
<h3>Graphs</h3>
<Dropdown source={graphItems} />
</>
<>
<h3>Sheets</h3>
<select id="dropdown" onChange={this.handleDropdownChange} className={classes.taskList}>
<option value="">select </option>
<option value="berry">Pokemon Berry</option>
<option value="ability">Pokemon Abilities</option>
<option value="version">Version Info</option>
</select>
</>
</Tabs>
<div>
selected sheet is: {this.state.selectedValue}
{
pokeData.map(hit =>
<div key={hit.name}>
<p> {hit.name} {hit.url} </p>
</div>
)
}
</div>
</>
);
}
}
What is actually happening is, once the page renders and I select 'berry', I get an error: 'https://pokeapi.co/api/v2/undefined': 404. So that means for some reason, the selectedValue was not set to 'berry'. However, if I then go on and select 'ability', it renders the pokeData.map but shows me the results for 'https://pokeapi.co/api/v2/berry' when it should be showing me the data for 'https://pokeapi.co/api/v2/ability' and this keeps happening on each select. It seems like the index is off by -1. I have a follow-up question as well, but I'd appreciate if someone can help me understand this. Thanks.
So, i have a couple of problems here:
Fix the issue with the index on the selectedItem, which is undef for the first selection and is set to index-1 on the next selections.
Perform the same thing using dropdown using material-ui. In which I do something like this:
const dropdownItems: [
'item1',
'item2',
'item3',
];
and the dropdown looks like:
<dropdown> source={dropdownItems} onChange={this.handleDropdownChange} </dropdown>
How do i make this work? It doesn't work as shown above.
The problem is that setState is async. The value is not yet set when you call the fetch and the previous value will be used. For the first time that's undefined.
Either use the value directly in your call from the event or use the setState callback as second parameter to trigger the API call like this.
this.setState({selectedValue: e.target.value}, () => fetch(...)...)
It should also work the same for the drop down.
Hope this helps.
The setState method is async which means that you cannot guarantee it has finished before the fetch method is called and hence the value can sometimes be undefined.
fetch(taskAPI + this.state.selectedValue)
A simple solution would be to use e.target.value in the fetch request too or supply a callback function to the setState that fetches the information once the state has been set.
For more information on setState see - https://reactjs.org/docs/react-component.html#setstate
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
Hope the above clears up the issue!
~Fraz

Updating a selected object when user changes inputs in a form

When a user clicks a square it becomes currentHtmlObject. I want people to be able to update it's properties in the right sidebar.
I have no idea how to take a single input field and update an object's property that I'm holding in a react-redux state and update the main viewing area DrawingCanvas.
I got kinda close where the info I was entering into the form was activating my reducers and actions. But I couldn't figure out how to distinguish between left and top.
// React
import React from 'react'
export class RightSidebar extends React.Component {
constructor(props) {
super(props)
}
handleChange(evt) {
console.log(evt)
this.props.onUpdateCurrentHtmlObject(evt.target.value)
}
render() {
const { currentHtmlObject } = this.props
return (
<form>
{this.props.currentHtmlObject.id}
<div className="right-sidebar">
<div className="form-group">
<label>Position X</label>
<input
type="number"
name="left"
className="form-control"
value={this.props.currentHtmlObject.styles ? this.props.currentHtmlObject.styles.left : ''}
onChange={this.handleChange.bind(this)} />
</div>
<div className="form-group">
<label>Position Y</label>
<input
type="number"
className="form-control"
value={this.props.currentHtmlObject.styles ? this.props.currentHtmlObject.styles.top : ''}
onChange={this.handleChange.bind(this)} />
</div>
</div>
</form>
)
}
}
RightSidebar.defaultProps = {
currentHtmlObject: {
styles: {
left: null,
top: null
}
}
}
There is no need to distinguish between left and top, let's assume you have an action named update and all it does is to update a selected object's property. Here is what the action method may look like:
updateSelectedObj(id, payload){
return {
type: UPDATE_SELECTED_OBJ,
id: id,
payload: payload
}
}
Here is what your event handler might look like in class RightSidebar:
handleChange(evt) {
// since your top and left input fields have a corresponding name property, evt.target.name will return either `left` or `top`
store.dispatch(updateSelectedObj({styles:{evt.target.name:evt.target.value}})
}
Here is your reducer:
[UPDATE_SELECTED_OBJ]: (state, action) => {
// I assume you have a list of objects in the canvas but only one can
// be selected at a time. and I assume the name of the list is objList
let selectedObj = state.objList.filter(obj => obj.id == action.id)[0]
selectedObj = Object.assign({}, selectedObj, action.payload)
return { objList: state.objList.map(obj => obj.id === action.id? Object.assign({}, obj, selectedObj : obj) }
}
I might suggest simplifying the component itself. Sorry for being brief :). I can update w/ more context when I get some time.
This is a stripped down example, but basically thinking of each "number input" as only needing a value and onChange (emits value, not an event).
You would make use of react-redux's connect so that updateObject is a callback accepting the "patch data" to be merged into the currentObject's state.
/**
* #param currentObject
* #param updateObject An already bound action creator that accepts payload to "update object"
*/
function SideBar({currentObject, updateObject}) {
const {styles} = currentObject;
return (
<div>
<NumberInput
value={styles.left}
onChange={left => updateObject({left})}
/>
<NumberInput
value={styles.top}
onChange={top => updateObject({top})}
/>
</div>
)
}
The connect statement might look something like
const SideBarContainer = connect(
(state, {objectId}) => ({
currentObject: _.find(state.objects, {id}),
}),
(dispatch, {objectId}) => ({
updateObject: data => dispatch(
actions.updateObject(objectId, data)
)
})
)(SideBar);
And the actual usage, maybe something like
<SidebarContainer objectId={currentObjectId} />

Resources