Hello my question is simple. How can I save a single value to a localStorage; what data type should it be before it is stored. After I read 2 articles on localstorage with react and 4 answers on StackOverflow I still can't assimilate myself because the examples where on how to persist the whole state. Didn't find a documentation for react and localstorage also if someone give me a link it will be great. I wrote this code just for the example. Thank you in advance.
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
}
}
componentDidMount() {
this.setState({
name: localStorage.getItem('name')
})
}
changeName = e => {
this.setState(prevState => ({
name: 'someName'
}));
localStorage.setItem('name', this.state.name);
}
render() {
return (
<Container>
<Row>
<Col sm={12}>
<div>{this.state.name}</div>
<button onClick={this.changeName}>change</button>
</Col>
</Row>
</Container>
)
}
}
export default List
There are a few NPM packages like:
https://www.npmjs.com/package/reactjs-localstorage
and
https://www.npmjs.com/package/react-use-localstorage
These should help.
In your changeName function you are setting state and after that setting the same state in localStorage which won't work in your case. This is because setState is async function, so it will take some time to execute and in that time your next code will get execute. In this scenario your localStorage will get empty value (first time) / previous value.
To make it correct you can use callback in setState like,
this.setState({name: 'someName'},
() => {localStorage.setItem('name', this.state.name)} //callback
);
Now you can have update value in localStorage every time.
Related
I want to use the result of a server method that does a count on a collection to show it in the render method of a react component. I understand that I must do it from the callback function but it doesn't work for me.
Server Method:
export const analysis = new ValidatedMethod({
name: "analysis",
validate: new SimpleSchema({
codigo: {
type: String
},
rta: {
type: String
}
}).validator(),
run(one) {
const rta = Respuesta.find({
codigo: one.codigo,
rtatexto: one.rta,
activo: true
}).count();
console.log(rta);
return Number(rta);
}
});
Call from client:
export default class AnalysisFila extends Component {
constructor(props) {
super(props);
}
render() {
const one = { codigo: this.props.codigo, rta: this.props.opcion };
const rta = analysis.call(one, (err, res) => {
return (
<Table.Row>
<Table.Cell> {this.props.opcion} </Table.Cell>
<Table.Cell> {res} </Table.Cell>
</Table.Row>
);
});
}
}
How do I use the value of res in my component's render method?
Asynchronous Functions
Before I answer the question, it is important to understand the difference between synchronous and asynchronous functions in JavaScript.
Therefore, if you are new to JavaScript or asynchronous behaviours, I recommend you read up on this excellent answer and explanation about the topic
Explanation for the Problem
The problem here is that React's render() method is a synchronous method, meaning React expects a synchronous return value with jsx (or a React.Element for that matter).
For React, your current render function actually returns nothing or undefined, as you do not a have a synchronous return statement. And so nothing will get rendered.
React has no way of knowing when your callback is called or what it returns, as it is executed completely out of Reacts context.
Solution to the Problem
The way to go here is to use Reacts state which is exactly for these kind of scenarios.
By using state and the method setState(), you can asynchrounously trigger the render-method to be called again as soon as the response from your API delivered the data. Also, with this.state, you can bring the response-data into the react-context and let it know about it.
A full example could look like this:
export default class AnalysisFila extends Component {
constructor(props) {
super(props);
this.state = {
loaded: false
opcion: {}
};
}
componentDidMount() {
const one = { codigo: this.props.codigo, rta: this.props.opcion };
const rta = analysis.call(one, (err, res) => {
this.setState({
loaded: true,
opcion: res
}
});
}
render() {
const content = this.state.loaded ? this.state.opcion : 'Loading...';
return (
<Table.Row>
<Table.Cell> {this.props.opcion} </Table.Cell>
<Table.Cell> {content} </Table.Cell>
</Table.Row>
);
}
}
I also used the componentDidMount-Lifecycle-Method of React, as it is the recommended way to trigger API Calls. You can read more about Lifecycles in the official lifecycle documentation
The Life-Cycle of this Component will be like the following:
Component will get created and constructor will set an initial value for this.state
render() method is called with this.state.loading === false - thus the content will default to the string 'loading...'
The componentDidMount() function gets called and will trigger your API-Call
Your callback is called and executes setState(), putting the response-data into this.state
Now that react knows the state was changed, it calls the render() method again. But this time, this.state.loading === true and there is some value for this.state.opcion - thus the content will be the value from your response.
I am new to React Native but have been searching for days so if this is easy please let me off the hook.
I have this component:
<DatePicker
signInDate={this.state.signInDate}
mode="date"
placeholder="select date"
format="DD-MM-YYYY"
minDate="01-01-1950"
maxDate="01-01-2050"
androidMode="spinner"
showIcon={false}
onDateChange={this.onDateChange('signInDate')}
/>
Which uses this class:
export default class CPT extends Component {
constructor(props) {
super(props);
this.state = {
userName: '',
isModalVisible: false,
signInDate: "01-01-2000",
startPump: "01-01-2000",
endPump: "01-01-2000",
signOut: "01-01-2000",
equipmentUsed: '',
}
}
onDateChange = (state) => (event, value) => {
this.setState({
state: value });
}
Whenever I update the date the value does change however the display value does not and I can't figure out what is going wrong.
I am stuck please, if you can point me in the right direction or tell me what I am doing wrong that would be greatly appreciated.
Basically I am going to have multiple DatePickers in this popup and will submit them to a DB or a web service when I am done when they hit submit.
I can post the rest of the code if needed.
First suggestion is that try using React-Native-Community-Date-Picker
and seems like there are some mismatch between your DatePicker props and your state attributes.
for example if you want to change signInDate in your state then your setState should look something like this this.setState({ signInDate: value }); and onDateChange would look like onDateChange={this.onDateChange}
I noticed I cannot access to a nested js object.
this.DB.get(2) returns an object in the form
{
id:1,
name: "my title",
Obj:{en:"english",fr:"french"}
}
This is the component
export default class Item extends Component {
state = {
Item:{}
}
constructor(props) {
super(props);
}
componentDidMount(){
this.DB = $DB()
this.setState({
Item: this.DB.get(2)
})
}
render() {
const { params } = this.props.navigation.state;
const id = params ? params.id : null;
const Item = this.state.Item
return (
<View style={{flex:1}}>
Number field: {Item.id} |
String field: {Item.name} |
Object field: <FlatList data={Object.entries(Item.Obj)} />
</View>
);
}
}
The problem: I cannot access to Item.Obj.
I got it, setState is async, I'm not sure this is causing the issue though. Anyway: is there a clean way for render the component in setState callback?
edit: I've just rebuilt (still in debug mode) and now it works, I do not changed anything, just added some console.log() around.
Anyway, I don't feel so safe. setState() is async and still I see around the web this function used wildly as it would be sync.
If the data I want to render are in the state, is there a way to render the component always after the state update?
Another doubt: you see the Component, it just does an access to a big JS object and shows some data and that's all. Do I really need to pass through the component state?
What do you think if I would move those 2 lines inside the render() method?
const DB = $DB()
const Item = DB.get(2)
I'm pretty new to react native and async programming, and trying to understand how to "sync" redux state values and local state values.
For example, I have a text field "aboutMe" stored server side, and using mapStateToProps to place it into props:
const mapStateToProps = (state) => {
return { aboutMe: state.aboutMe };
}
In render, I have a TextInput I'm using so that the user can edit this field, and I would like to default to what is saved on the server side:
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
Basically, somewhere I need to call
this.setState({ aboutMe: this.props.aboutMe });
Where is the right place to this? I was trying to use componentWillReceiveProps, but that lifecycle method is not called on constructor, so I would need to setState twice (in constructor and in componentWillReceiveProps).
Is there another way to do this? I feel like this is a pretty generic problem that a lot of react native developers have solved but I couldn't find a generally accepted way online.
Thanks!
Edit:
I have alot of TextInputs, so I have a separate button to call the action to save the variables:
<Button onPress={()=>{
this.props.saveUserInput(this.state.aboutMe,
this.state.name, this.state.address, ....}}>
<Text> Save changes </Text>
</Button>
From the comments, I understand that it's possible to call the save action onChangeText... but is that too much traffic back and forth? Would it be better to save all of the variables locally to state and then call a save for everything at once? Also, what if the user would like to "cancel" instead of save? The changes would have been already saved and we will not be able to discard changes?
1) If your component is a controlled component (you need state in it) and the request is asynchronous indeed you have to set the state in the componentWillReceiveProps like this:
class ExampleComp extends Component {
constructor(props) {
super(props);
this.state = {
aboutMe: ""
}
}
componentWillReceiveProps(nextProps) {
this.setState({
aboutMe: nextProps.aboutMe,
});
}
render() {
return (
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
);
}
}
Keep in mind the key here is that the state must remain the single source of truth from now on.
2) The other option would be, you can wait until the request is finished in the parent component and then set the aboutMe in your constructor, this way you can avoid componentWillReceiveProps. For example:
class ParentComp extends Component {
render() {
return (
<div>
{this.props.aboutMe && <ExampleComp/>}
</div>
);
}
}
class ExampleComp extends Component {
constructor(props) {
super(props);
this.state = {
aboutMe: props.aboutMe
}
}
render() {
return (
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
);
}
}
The downside of this is that the text input won't be shown until the request is finished.
Since you have edited your question, it is more clear what you want to achieve, so I want to address that.
You could keep the state of your controlled input elements in the component, then use the redux store to store persistent data and to populate the default values.
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
aboutMe: props.aboutMe,
... // other data
}
}
handleSubmit = (event) => {
event.preventDefault() // To prevent redirect
// Dispatch the save user input action
this.props.dispatch(saveUserInput(this.state))
}
render() {
return (
<form onSubmit={this.handleSubmit} />
<TextInput onTextChange={text => this.setState({...this.state, aboutMe: text}) />
... // input fields for other data
// Clicking this fill trigger the submit event for the form
<button type="submit">Save</button>
</form>
)
}
}
Why does it display only the last value of the array, and not the whole?.
When you update the value in the database, When you update the value in the database, it outputs all
constructor(props) {
super(props);
this.name = this.props.name;
this.state = {[this.name] : []};
}
componentDidMount() {
let cardQuantity =
firebase.database().ref("Users").child(this.name);
cardQuantity.on('value',snap => {
snap.forEach((childSnapshot)=> {
let card = {text: childSnapshot.val(), id: childSnapshot.key};
this.setState({[this.name] :[card].concat(this.state[this.name])});
});
});
}
render(){
return (
this.state[this.name].map( card => <h2 key={card.id}>{card.text}</h2>)
);
}
setState() is async so you have to use callback form of setState like below:
this.setState(prevState => ({
[this.name]: [card].concat(prevState[this.name])
}));
Demo: https://codesandbox.io/s/zrq0xxq2px
It shows 2 versions based on your code:
fixed version that is using callback form of setState and displaying the whole list
unfixed version based on your code that is showing only the last element of an array
From the official React's documentation:
If the next state depends on the previous state, we recommend using
the updater function form, instead...