Basic scenario -
I have an input element for e-mail address. On click of save, the e-mail is saved into the database. When I refresh, I want the saved value from database to show up as the initial value of the input. After that, I want to control the input's value through regular component state.
The problem I am having is setting the initial state value from props.
I thought I can set the state from props in CDU by making a prevProps to currentProps check.
Actual scenario -
The props I am trying to compare is an array of objects. So CDU shallow comparison won't help. How do I go about doing this?
Please note that I am not using or do not want to use any form library as I just have 3 input fields in my applications.
Thanks
You need to get data from the database on the component mount and set the state of your email. Like this below example (I am doing it on edit user) -
componentDidMount() {
var headers = {
"Content-Type": "application/json",
Authorization: `Token ${authToken}`
};
axios
.get(`${serverURL}users/${this.props.match.params.id}`, {
headers: headers
})
.then(res => {
//console.log(res.data);
let userdetails = res.data;
this.setState({
first_name: userdetails.first_name,
last_name: userdetails.last_name,
email: userdetails.email,
});
})
.catch(function(error) {
console.log(error);
});
}
Possible solution for this:
1. Create local state for email e.g
state = {
email: '';
}
dispatch the action in componentDidMount which will fetch the data and store it in redux state
use static getDerivedStateFromProps(props, state) to get updated values from props (ref. link :https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops) like
static getDerivedStateFromProps(nextProps, prevState) {
// do things with nextProps.email and prevState.email
return {
email: nextProps.someProp,
};
}
Related
I have a login component which makes an API call with email and password to Ruby API and sets the isLoggedIn = true if the credentials are correct. When this is true, the navbar shows "logout" link and shows signin/signout when false. However, the issue is that it only works fine when page is not reloaded since I am not storing the session anywhere.
I am trying to set login state in local storage when the login is successful. It works fine in the login component, but doesn't when I try to read the local storage value in the other component. I assume this is because I am not storing it in shared state. Is it possible to store the value in state?
Login.js
axios
.post(apiUrl, {
user: {
email: email,
password: password,
},
})
.then((response) => {
if ((response.statusText = "ok")) {
setLoginStatus({ props.loginState.isLoggedIn: true });
localStorage.setItem('props.loginState.isLoggedIn', true);
console.log(response)
history.push("/");
}
})
App.js
let data = {
isLoggedIn: false,
user: {},
setSession:null
};
const [loginState, setLoginStatus] = useState(data);
const rememberMe = localStorage.getItem('loginState.isLoggedIn')
When I use console log the below either in App.js or Nav.js the value is null always.
localStorage.getItem('props.loginState.isLoggedIn');
The localStorage items as are saved as key value pairs. This is a string 'props.loginState.isLoggedIn', and will be access the same way from any component. Since you saved it with that key name it can only be accessed with that key name from any component.
I want to change the credentials of state in a separate Javascript file which is a function() called LoginConfirmation.js. The credentials are in a class called login.js:
state = {
credentials: {
username: '',
password: '',
collapseID: '',
logged_in: true,
usernameChecked: LoginConfirmation.usernameChecked,
passwordChecked: LoginConfirmation.passwordChecked
},
isLoginView: true,
userAccount: []
}
I am trying to change the values of usernameChecked and passwordChecked inside LoginConfirmation.js without using any HTML onChange or onClick etc.
I am mapping through my database using:
{props.userAccount.map(userAccount => { ...
And then setting:
let usernameChecked = userAccount.username
Is it possible to set this usernameChecked value to usernameChecked in the credentials in login.js
It's hard to say without seeing the entire set up.
But I guess you could add a public method to the class in login.js that makes the changes you want, something like (assuming it's a React class component, not a js class):
updateUsernameChecked = (newValue) => {
this.setState(state => state.credentials.usernameChecked = newValue);
}
then pass the method to LoginConfirmation.js and call it with the new value.
Hope this helps.
Suppose I have this component with sends a POST request to create some data:
class Home extends React.Component {
constructor(){
this.state = {
firstName: null
}
}
componentDidMount(){
const data = {
firstName: "Puspen"
}
axios.post("url", data)
.then(response => {
//update the state here
})
.catch(error => {
})
}
render() {
return (
<div>
<h1>{this.state.firstName}</h1>
</div>
);
}
}
Now, in the then(), I have three options to update the state, if the status is 201 Created:
Take the firstName from data and update the state
this.setState({ firstName: data.firstName })
The response should contain the created resource and we just update the state from the response data this.setState({ firstName: responseData.firstName })
//response
{
"firstName": "Puspen",
"_links": {
"self": {
"href": "http://localhost:8085/api/v1/user/theCoder"
}
}
}
The response would only contain the self link to the created resource, then the react component would make a request to that link to fetch the created resource.
In my opinion the, 1st one is the best. Now extra bandwidth taken, and no extra API calls
2nd option would cause the response to take extra bandwidth.
3rd option seems worst to me, making an API call to get created data and that also takes extra bandwidth.
How all large websites handle it?
This is the method I'm using, pretty simple.
DailyCountTest: function (){
this.$store.dispatch("DailyCountAction")
let NewPatientTest = this.$store.getters.NewPatientCountGET
console.log(NewPatientTest)
}
The getter gets that data from a simple action that calls a django backend API.
I'm attempting to do some charting with the data so I need to assign them to variables. The only problem is I can't access the variables.
This is what the console looks like
And this is what it looks like expanded.
You can see the contents, but I also see empty brackets. Would anyone know how I could access those values? I've tried a bunch of map.(Object) examples and couldn't get any success with them.
Would anyone have any recommendation on how I can manipulate this array to get the contents?
Thanks!
Here is the Vuex path for the API data
Action:
DailyCountAction ({ commit }) {
axios({
method: "get",
url: "http://127.0.0.1:8000/MonthlyCountByDay/",
auth: {
username: "test",
password: "test"
}
}).then(response => {
commit('DailyCountMutation', response.data)
})
},
Mutation:
DailyCountMutation(state, DailyCount) {
const NewPatientMap = new Map(Object.entries(DailyCount));
NewPatientMap.forEach((value, key) => {
var NewPatientCycle = value['Current_Cycle_Date']
state.DailyCount.push(NewPatientCycle)
});
}
Getter:
NewPatientCountGET : state => {
return state.DailyCount
}
State:
DailyCount: []
This particular description of your problem caught my eye:
The getter gets that data from a simple action that calls a django backend API
That, to me, implies an asynchronous action and you might be getting a race condition. Would you be able to post a sample of your getter function to confirm my suspicion?
If that getter does indeed rely on an action to populate its contents, perhaps something to the effect of the following might do?
DailyCountTest: async () => {
await this.$store.dispatch('DailyCountAction')
await this.$store.dispatch('ActionThatPopulatesNewPatientCount')
let NewPatientTest = this.$store.getters.NewPatientCountGET
// ... do whatever with resulting array
}
You can also try with a computer property. You can import mapGetters
import { mapGetters } from 'vuex'
and later in computed properties:
computed: {
...mapGetters(['NewPatientCountGET'])
}
then you can use your NewPatientCountGET and it will update whenever the value changes in the store. (for example when the api returns a new value)
Hope that makes sense
I'm trying to increment the number I get from this.props.match.params.id to get the DOM to re-render with new API fetched content. I was trying to do that by calling a function on Click and incremeting value by one. However, that's not happening at all, instead of incrementing the value, it concatenates like this:
https://imgur.com/a/dnKScN0
This is my code:
export class Sofa extends React.Component {
constructor(props) {
super(props);
this.state = {
token: {},
isLoaded: false,
model: {}
};
}
addOne = () => {
console.log('addOne');
this.props.match.params.id += 1;
console.log('id: ', this.props.match.params.id);
}
lessOne = () => {
console.log('lessOne');
}
componentDidMount() {
/* fetch to get API token */
fetch(url + '/couch-model/' + this.props.match.params.id + '/', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'JWT ' + (JSON.parse(localStorage.getItem('token')).token)
}
}).then(res => {
if (res.ok) {
return res.json();
} else {
throw Error(res.statusText);
}
}).then(json => {
this.setState({
model: json,
isLoaded: true
}, () => { });
})
}
render() {
const { model, isLoaded } = this.state;
if (!isLoaded) {
return (
<div id="LoadText">
Loading...
</div>
)
} else {
return (
<div id="Sofa">
/* other HTML not important for this matter */
<img src="../../../ic/icon-arrow-left.svg" alt="Arrow pointing to left" id="ArrowLeft"
onClick={this.lessOne}/>
<img src="../../../ic/icon-arrow-right.svg" alt="Arrow pointing to right" id="ArrowRight"
onClick={this.addOne}/>
</div>
);
}
}
}
You can't manipulate props, they are immutable. You have a few options:
Use Redux or some other form of global state management and store the counter there.
Store the counter in the state of the parent, pass it down as a prop to the child along with a callback function allowing the child to increment the counter.
Store the state for the counter directly within the child component and update it there.
As your string is concatenating JS thinks its a string and not a number. Just increment like so Number(this.props...id) + 1
It seems that this.props.match.params.id is string. Adding a value to string variable works as concatination.
You need to parseInt before adding a value like:
this.props.match.params.id = parseInt(this.props.match.params.id) + 1;
Very likely this.props.match.params.id is a string. Doing this: '1'+1 returns '11'.
I guess this is a 'main' component - just 'below' router. You can't modify param from router - you should generate link to next item (pointing the same component) which will get it's own props ('updated' id) from router.
You're sure you will have contiuous range of ids? No deletions in db? Rare ;)
Normally you have a searched/category based list of items. Browsing list ... entering item (by id), back to list, enter another item. Browsing items w/o info about list is a little harder and inc/dec id isn't good idea.
Get list into main component, pass current id to sub-component (able to load data itself and render it). Prepare next/prev buttons in main (modify current in state, loop them?) - changed prop (id) will force rendering component to load next data.
Further ... extract next/prev into sub-components, passing handlers from main (you didn't passed them and even if it won't work for reasons above).
Accessing items only by id is a bit harder, f.e. you can 'ask' api for next/prev items for current one. It can be done async - render next/prev when data will be ready.
Don't make all in one step ;)