I'm fairly new to React.
I have selector which returns whatever the user selects.
Code Example:
handleChanged(e){
const { onSelectcountry } = this.props;
onSelectcountry(e.target.value)
}
return (
<div>
<Input type="select" name="select" value={Country} onChange={this.handleChanged.bind(this)}>
{
country.map((item) => {
return (<option value={item._id} key={item._id}> {item.name}</option>);
})
}
</Input>
</div>
);
i dispatch action depend on user select,
import { fetchNews} from '../../actions';
getNews(filterNews) {
const { fetchNews } = this.props;
fetchNews(filterNews);
}
onSelectcountry(country) {
this.setState({ Country: country});
this.getNews({
this.state,
})
}
<CountrySelector onSelectcountry={this.onSelectcountry.bind(this)} Country={Country}/>
The problem is: When the selected value changes, it shows the value of the previous selection.
this is due to the asynchronous nature of setState
You have some options:
use setState's optional callback, it will be invoked after updating state.
onSelectcountry(country) {
this.setState(
{ Country: country},
() => this.getNews({ this.state })
);
}
Call getNews with manually composed arguments
onSelectcountry(country) {
this.setState({ Country: country });
this.getNews({
...this.state,
Country: country
})
}
Call getNews in componentDidUpdate callback, e.g. leave onSelectcountry simple and care only about Country state updates, and handle real state update in expected manner.
componentDidUpdate(prevProps, prevState){
// coundition may vary depending on your requirements
if (this.state.Country !== prevState.Country) {
this.getNews(this.state);
}
}
onSelectcountry(country) {
this.setState({ Country: country});
}
Related
I have a keeper app where I am adding notes and storing them in database. When I make a post request to the server, I am trying to fetch the _id from database, which will eventually help me to later delete the note ( if needed).
Here is my jsx file
function CreateMessage(props) {
const [currentGuest, setCurrentGuest] = useState({
guestName: '',
guestMessage: '',
id:''
});
function handleMessages(event) {
const {name, value} = event.target;
setCurrentGuest(prevGuest => {
return {
...prevGuest,
[name] : value
};
});
}
function submitMessage(event) {
//props.onAdd(currentGuest);
const params = {
guestName: currentGuest.guestName,
guestMessage: currentGuest.guestMessage,
}
axios
.post("http://localhost:8000/notes", params)
.then(res => {
console.log("The response is"+res.data._id);
console.log(res.status);
setCurrentGuest(prevGuest => {
console.log(res.data._id)
return {
...prevGuest,
id: res.data._id
};
});
console.log(currentGuest);
})
event.preventDefault();
}
return (
<div>
<form>
<input
name="guestName"
placeholder="Guest Name"
value={currentGuest.guestName}
onChange={handleMessages}
/>
<textarea
name="guestMessage"
placeholder="Write a Message"
rows="3"
value={currentGuest.guestMessage}
onChange={handleMessages}
/>
<button onClick={submitMessage}>Add</button>
</form>
</div>
);
}
The id is properly being fetched and displayed in ```console.log("The response is"+res.data._id"). But on first submit, the is always empty and stale id gets attached to the currentGuest object on next submit
function submitMessage(event) {
//props.onAdd(currentGuest);
const params = {
guestName: currentGuest.guestName,
guestMessage: currentGuest.guestMessage,
}
axios
.post("http://localhost:8000/notes", params)
.then(res => {
console.log("The response is"+res.data._id);
console.log(res.status);
setCurrentGuest(prevGuest => {
console.log(res.data._id)
return {
...prevGuest,
id: res.data._id
};
});
console.log(currentGuest);
})
event.preventDefault();
}
In the above snippet, after getting the response you're correctly changing the state but the problem is with where you're checking the changed state(console.log(currentGuest)). You're basically logging before the state is changed.
You can use useEffect hook and log the state inside it. This runs every time the currentGuest Changes.
useEffect(() => {
console.log(currentGuest)
}, [currentGuest])
Update
You can use the modified currentGuest inside the useEffect hook:
useEffect(() => {
console.log(currentGuest)
if(currentGuest.id) {
props.onAdd(currentGuest);
// You can also reset the state here as follows
setCurrentGuest({
guestName: '',
guestMessage: '',
id:''
});
}
}, [currentGuest]) // You might need to add the necessary dependencies to this array.
I am getting the error message
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.in Login (created by Context.Consumer`)
I am have tried the most common fix for that issue but it didn't work. The fix where you use _isMounted = false; componentDidMount() { this._isMounted = true;} if (this._isMounted) {this.setState({ data: data});}
I am also using Context.
SessionContex.js
import React, { Component, createContext } from "react";
export const SessionContext = createContext();
class SessionContextProvider extends Component {
_isMounted = false;
state = {
is_logged_in: false,
personid: " ",
firstname: " ",
lastname: " "
};
loggedIn = (loginvalue, personid, firstname, lastname) => {
this.setState({
is_logged_in: loginvalue,
personid: personid,
firstname: firstname,
lastname: lastname
});
};
loggedOut = () => {
this.setState({
is_logged_in: false,
personid: " ",
firstname: " ",
lastname: " "
});
};
componentDidMount = () => {
this._isMounted = true;
const login = localStorage.getItem("is_logged");
const id = localStorage.getItem("personid");
const firstname = localStorage.getItem("firtname");
const lastname = localStorage.getItem("lastname");
console.log(login);
if (this._isMounted) {
this.setState({
is_logged_in: login,
personid: id,
firstname: firstname,
lastname: lastname
});
}
};
componentWillUnmount() {
this._isMounted = false;
}
render() {
return (
<SessionContext.Provider
value={{
...this.state,
loggedIn: this.loggedIn,
loggedOut: this.loggedOut
}}
>
{this.props.children}
</SessionContext.Provider>
);
}
}
export default SessionContextProvider;
Login.jsx
import React, { Component } from "react";
import { SessionContext } from "../context/SessionContext";
import "../Stylesheets/Login.css";
import "..//Stylesheets/global.css";
import { Redirect } from "react-router-dom";
class Login extends Component {
_isMounted = false;
static contextType = SessionContext;
state = {
password: " ",
email: " ",
couldNotfindLogin: true,
redirect: false
};
handleChange = e => {
this.setState({
[e.target.name]: e.target.value,
[e.target.name]: e.target.value
});
};
handleSubmit = async e => {
e.preventDefault();
const data = this.state;
console.log(data);
const response = await fetch("http://localhost:3080/users/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(data)
});
const reply = await response;
if (reply.status === 200) {
const userData = await response.json();
this.context.loggedIn(
userData.isValid,
userData.personid,
userData.firstname,
userData.lastname
);
localStorage.setItem("is_logged", userData.isValid);
localStorage.setItem("personid", userData.personid);
localStorage.setItem("firstname", userData.firstname);
localStorage.setItem("lastname", userData.lastname);
this.setState({
couldNotfindLogin: true,
redirect: true
});
}
if (reply.status !== 200) {
this.context.loggedIn(false);
console.log(this.context);
}
this.setState({
couldNotfindLogin: false
});
};
componentWillUnmount() {
this.mounted = false;
}
render() {
let { couldNotfindLogin } = this.state;
if (this.state.redirect === true) {
return <Redirect to="/" />;
}
return (
<>
<section className="container">
<div className="row">
<div className="col-sm-12 col-md-12 col-lg-12 login-section">
<div className="form login-box">
<form className="form-login Login" onSubmit={this.handleSubmit}>
<label htmlFor="Email">
<p className="login-header">
Login to see your past, present, and future self.
</p>
<input
className="login-input"
id="Email"
type="text"
name="email"
onChange={this.handleChange}
placeholder="Email Address"
required
/>
{!couldNotfindLogin ? (
<p className="FindYou">We could not find your account </p>
) : (
" "
)}
</label>
<label htmlFor="Password">
<input
className="login-input"
id="Password"
type="password"
name="password"
onChange={this.handleChange}
placeholder="Password"
required
/>
</label>
<button className="login-button" type="submit">
Login
</button>
</form>
</div>
</div>
</div>
</section>
</>
);
}
}
I found a few mistakes that are holding you back. I'll try to brief as to not make this a long-winded answer:
Allow the Provider to handle all things related to high-level state and localStorage. Only use local state that's revelant to the component. Also, since setState() is asynchronous, avoid setting it immediately following another asynchronous function(s). While you do have an _isMounted class property, it isn't being checked against when the async function (fetch request) resolves, therefore if you're redirecting the user to another route while React attempts to update the component state, then you'll get these warnings as described above. Put simply, utilize the _isMounted in the async function right before using setState or simply avoid using setState (see example below).
The handleChange class field only needs a single [e.target.name]: e.target.value statement. Having two is unnecessary. You can use object destructuring to simplify this down to: [name]: value (see example below).
Any component provided to a Route has access to some route props. Among the props is a history object that contains several methods -- among them is a push method. You can use this push method to redirect a user to another url: this.props.history.push("url"). Again this is provided, by default, to a component that resides in a react-router-dom Route.
async/await will only work on promises. Therefore, you'll want to await the initial fetch response and then await the fetch response.json() promise. The first promise, returns a Stream object, while the second promise returns a JSON object (this is by design, other libraries, like axios, skip this second promise). Therefore, const reply = await response; expression isn't necessary as it's not waiting for promise a resolve, but instead it's just setting the response's Stream object to a reply variable.
Lastly, only use let for variables if you plan on updating or mutating this variable dynamically within the same execution cycle; otherwise, just use const. This becomes especially important because state and props should always be immutable for React to know when or if they have been updated (via another component or by setState). Simply put, avoid using let for state/prop expressions like: let { password } = this.state.
Working example (I'm mimicking the fetch response promise, but you can play around with it by checking out the src/api/index.js file -- for example you can trigger an error response by submitting a password as invalid):
I am new to react.
For e.g, input value entered 1,2,3,4 and after the event onChange it takes only numbers, then I can
remove 4,3,2 with backspace but not 1. And in HTML DOM also, the 1 cannot be removed.
class House extends Component {
state = {
room: null,
};
componentDidMount() {
if (this.props.house.rent) {
this.setState({ rent: this.props.house.rent });
}
}
onChange = (field, value, mutate) => {
if (field === "houseroom") {
value = parseInt(value.replace(/[#,]/g, ""));
}
mutate({
variables: {
},
});
this.setState({
[field]: value,
});
};
render(){
const {house} = this.props;
<SomeInput
type="text"
value={
(house.room&&
`$${house.room.toLocaleString("en")}`) ||
""
}
onChange={e => {
e.target.placeholder = "Room";
this.onChange("houseroom", e.target.value, mutate);
}}
}
/>
}
It look like a lot of a problem during the update of the state probably due to overrendering elsewhere in the component try to use prevState instead do ensure that state updating can not conflict.
this.setState(prevState => {
[field]:value;
});
Keep me in touch with the result.
Hope this helps someone !
Need to mention "this.state.room" in the input Value and check the prev state using the componentDidUpdate then "this.setState" here and also finally "this.setState" during the event change. Thanks all
I have a form that is per-populated based on user saved information and the user can change those information at any time.
A simple example of how this is structured is:
componentWillMount() {
const { user, getProfile } = this.props;
if (user.profileId) {
getProfile(user.profileId); // this is a redux action that fetch for user profile data updating the props.
}
}
onChangeValue(e) {
const { updateProfileField } = this.props; // this is a redux action to update a single input
const { id, value } = e.target;
updateProfileField(id, value);
}
Inputs looks like this:
<InputField id="screenName" type="text" value={profile.screenName} onChangeValue={this.onChangeValue} placeholder="Your username" />
The InputField component is the following:
class InputField extends Component {
constructor(props) {
super(props);
this.state = {
value: props.value,
};
this.onChangeValue = this.onChangeValue.bind(this);
}
onChangeValue(value) {
this.props.onChangeValue(value);
}
render() {
const {
id, type, parent,
} = this.props;
return (
<input
id={id}
parent-id={parent}
className="form-control"
type={type}
name={id}
value={this.state.value}
onChange={this.onChangeValue}
/>
);
}
}
The Redux action to handle the updateProfileField is:
export function updateProfileField(field, value) {
const payload = { field, value };
return dispatch => dispatch({payload, type: types.UPDATE_PROFILE_FIELD});
}
Finally the Reducer:
const initialState = {
data: {}, // Here are stored the profile information like `screenName`
...
};
export default (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
...
case types.UPDATE_PROFILE_FIELD:
// Here my attempts to update the input value
return {
...state,
data: { ...state.data, [payload.field]: payload.value },
};
// Immutability helper
// return update(state, {
// data: { [payload.field]: { $set: payload.value } },
// });
// return Object.assign({}, state.data, {
// [payload.field]: payload.value,
// });
}
}
The point is that everything works just fine as soon as I load the page and I start to type the information in my form.
If I change something in the code, it reload automatically using HMR, I can see the information typed before in the inputs but if I try to update the information it doesn't work anymore...
I can see the "state" updating just the last letter of the input and the UI seems freeze. (ie.: input value is: 'foo', I type: 'bar' the result is: foob, fooa, foor... and it is not reflected in the UI).
I think I'm doing something wrong in here...
I can't figure out why my input is not updating. Here is my code:
state = {
org: {
orgName: ''
}
};
updateInput = field => event => {
this.setState({
[field]: event.target.value
})
}
render() {
let { org } = this.state
return (
<input
value={org.orgName}
onChange={this.updateInput('orgName')}
/>
)
}
I type data into the input. It calls updateInput and sets the state. When render is called, the org.orgNameis '' again. This should be working.
I have even added a log in the setState callback:
this.setState({
[field]: event.target.value
}, () => console.log(this.state.org))
and it logs out the org info that has been entered into the input
What am I missing? How do I make this work?
You have a nested object in your state - you are updating this.state.orgName instead of this.state.org.orgName
updateInput = field => event => {
this.setState({
[field]: event.target.value
})
}
needs to be
updateInput = field => event => {
this.setState({
org: {
...this.state.org,
[field]: event.target.value
}
})
}
Would recommend you avoid nesting objects in state though going forward. Will prove difficult to optimize later on.