Setting state value in react native - reactjs

Here is what I am trying to do and examples I have found don't seem to set the value either. So I am sure it is something simple I am over-looking.
setViewedSubmission(subId: string) {
logger.log("pre:", this.state.position.candidates.find((c)=> {
return c.submissionId == subId
}).viewedSubmission) //returns original value
this.state.position.candidates.find((c)=> {
return c.submissionId == subId
}).viewedSubmission = true //expect the value to update
this.forceUpdate()
logger.log("post:", this.state.position.candidates.find((c)=> {
return c.submissionId == subId
}).viewedSubmission) //returns original value still
}
This is what I actually started with but also doesn't work: Based on the responses, this is closer to what I should be doing but still doesn't. What is the issue here?
let pos = Object.assign({}, this.state.position)
pos.candidates.find((c) => {
return c.submissionId == subId
}).viewedSubmission = true;
this.setState({ position: pos })

The only way to update the state and get the screen re-rendered is to use the setState method. Here a look:
let candidates = this.state.position.candidates;
candidates = candidates.map(candidate => {
if(candidate.submissionId === subId) candidate.viewedSubmission = true;
return candidate;
});
this.setState(prevState => ({
position: {
...prevState.position,
candidates
}
}));

I think you're looking at this backwards. React is basically a state machine which means the UI reflects the current status of the state.
Without delving too deep into your code you probably want to structure things something similar to the following:
class Demo extends React.Component {
constructor(props){
super(props)
this.state = {
viewed: false,
}
}
someBusinessLogic(viewed) {
this.setState({
...this.state,
viewed: viewed,
})
}
render() {
const { viewed } = this.state
if (viewed ) {
return <div>VIEWED</dive>
} else {
return <div>UNVIEWED</div>
}
}
}
...and somewhere else
...//this is terrible code, just an example of using the business logic
<Demo viewed='true' ref={r => this.demo1 = r} />
<Demo viewed='false'/>
<button onClick={e => { this.demo1.someBusinessLogic('viewed') }}>Set Viewed = 'viewed'</button>
...
Structuring the component this way means that you reflect upon the state rather than trying to manage when updates occur. setState(newState) will re-render if the state is different.
My guess is that the internal state of your component is the same.
If I misread your question and you are not seeing updates passed to the component make sure you are using the key and that it is updating or override shouldComponentUpdate which is where forceUpdate starts to be useful.
Hopefully this helps, you might consider providing more details about your implementation so the community can be more specific.

You can't alter state directly. It is immutable (If you plan on working with React, you should read more about that). When you say this.state.value = 1, it won't update. Instead, you should do this.setState({ value: 1 }). And, if there are more itens in state, you should do this.setState(p => ({ ...p, value: 1 }), since when you update state, you must return the entire new state object.

Related

Render an array as list with onClick buttons

I'm new at ReactJs development, and I'm trying to render a list below the buttons I created with mapping my BE of graphQl query. I don't know what I'm doing wrong (the code has a lot of testing on it that I tried to solve the issue, but no success.)
The buttons rendered at getCategories() need to do the render below them using their ID as filter, which I use another function to filter buildFilteredCategoryProducts(categoryParam).
I tried to look on some others questions to solve this but no success. Code below, if need some more info, please let me know!
FYK: I need to do using Class component.
import React, { Fragment } from "react";
import { getProductsId } from "../services/product";
import { getCategoriesList } from "../services/categories";
//import styled from "styled-components";
class ProductListing extends React.Component {
constructor(props) {
super(props);
this.state = {
category: { data: { categories: [] } },
product: { data: { categories: [] } },
filteredProduct: { data: { categories: [] } },
};
this.handleEvent = this.handleEvent.bind(this);
}
async handleEvent(event) {
var prodArr = [];
const testName = event.target.id;
const testTwo = this.buildFilteredCategoryProducts(testName);
await this.setState({ filteredProduct: { data: testTwo } });
this.state.filteredProduct.data.map((item) => {
prodArr.push(item.key);
});
console.log(prodArr);
return prodArr;
}
async componentDidMount() {
const categoriesResponse = await getCategoriesList();
const productsResponse = await getProductsId();
this.setState({ category: { data: categoriesResponse } });
this.setState({ product: { data: productsResponse } });
}
getCategories() {
return this.state.category.data.categories.map((element) => {
const elName = element.name;
return (
<button id={elName} key={elName} onClick={this.handleEvent}>
{elName.toUpperCase()}
</button>
);
});
}
buildFilteredCategoryProducts(categoryParam) {
const filteredCategories = this.state.product.data.categories.filter(
(fil) => fil.name === categoryParam
);
let categoryProducts = [];
filteredCategories.forEach((category) => {
category.products.forEach((product) => {
const categoryProduct = (
<div key={product.id}>{`${category.name} ${product.id}`}</div>
);
categoryProducts.push(categoryProduct);
});
});
return categoryProducts;
}
buildCategoryProducts() {
const filteredCategories = this.state.product.data.categories;
let categoryProducts = [];
filteredCategories.forEach((category) => {
category.products.forEach((product) => {
const categoryProduct = (
<div key={product.id}>{`${category.name} ${product.id}`}</div>
);
categoryProducts.push(categoryProduct);
});
});
return categoryProducts;
}
buildProductArr() {
for (let i = 0; i <= this.state.filteredProduct.data.length; i++) {
return this.state.filteredProduct.data[i];
}
}
render() {
return (
<Fragment>
<div>{this.getCategories()}</div>
<div>{this.buildProductArr()}</div>
</Fragment>
);
}
}
export default ProductListing;
Ok, so this won't necessarily directly solve your problem,
but I will give you some pointers that would definitely improve some of your code and hopefully will strengthen your knowledge regarding how state works in React.
So first of all, I see that you tried to use await before a certain setState.
I understand the confusion, as setting the state in React works like an async function, but it operates differently and using await won't really do anything here.
So basically, what we want to do in-order to act upon a change of a certain piece of state, is to use the componentDidUpdate function, which automatically runs every time the component re-renders (i.e. - whenever there is a change in the value of the state or props of the component).
Note: this is different for function components, but that's a different topic.
It should look like this:
componentDidUpdate() {
// Whatever we want to happen when the component re-renders.
}
Secondly, and this is implied from the previous point.
Since setState acts like an async function, doing setState and console.log(this.state) right after it, will likely print the value of the previous state snapshot, as the state actually hasn't finished setting by the time the console.log runs.
Next up, and this is an important one.
Whenever you set the state, you should spread the current state value into it.
Becuase what you're doing right now, is overwriting the value of the state everytime you set it.
Example:
this.setState({
...this.state, // adds the entire current value of the state.
filteredProduct: { // changes only filteredProduct.
...filteredProduct, // adds the current value of filteredProduct.
data: testTwo
},
});
Now obviously if filteredProduct doesn't contain any more keys besides data then you don't really have to spread it, as the result would be the same.
But IMO it's a good practice to spread it anyway, in-case you add more keys to that object structure at some point, because then you would have to refactor your entire code and fix it accordingly.
Final tip, and this one is purely aesthetic becuase React implements a technique called "batching", in-which it tries to combine multiple setState calls into one.
But still, instead of this:
this.setState({ category: { data: categoriesResponse } });
this.setState({ product: { data: productsResponse } });
You can do this:
this.setState({
...this.state,
category: {
...this.state.category,
data: categoriesResponse,
}
product: {
...this.state.product,
data: productsResponse,
},
})
Edit:
Forgot to mention two important things.
The first is that componentDidUpdate actually has built-in params, which could be useful in many cases.
The params are prevProps (props before re-render) and prevState (state before re-render).
Can be used like so:
componentDidUpdate(prevProps, prevState) {
if (prevState.text !== this.state.text) {
// Write logic here.
}
}
Secondly, you don't actually have to use componentDidUpdate in cases like these, because setState actually accepts a second param that is a callback that runs specifically after the state finished updating.
Example:
this.setState({
...this.state,
filteredProduct: {
...this.state.filteredProduct,
data: testTwo
}
}, () => {
// Whatever we want to do after this setState has finished.
});

React: update state with props change - handleChange and getDerivedStateFromProps

I'm trying to make some component which data output depends on some external API.
So I have this snippet:
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
somethingFromAPI: ''
}
}
componentDidMount() {
/*
something on axios.get() which updates this.state.somethingFromAPI
which normally can have some time delay till executed
*/
}
render() {
return (
<Child value={this.state.somethingFromAPI} />
)
}
}
class Child extends Component {
constructor(props) {
super(props)
this.state = {
value: this.props.value || ''
}
}
handleChange(event) {
this.setState({
value: event.target.value
})
}
static getDerivedStateFromProps(props, state) {
// if difference
return {
value: props.value
}
}
render() {
return (
<div>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
</div>
)
}
}
ReactDOM.render(
<Parent />
document.getElementById('app')
);
Seems like this works fine, initializing component, and getting API data, after that, input value seems to be updated, which is what I expect.
Problem that hurts me a lot is if I type something inside input, that will call handleChange, but will also trigger this getDerivedStateFromProps and will replace newer inputed value with that "old" from API.
Is this good way of doing this, maybe I made mistake at start with understanding of how it should be done? Guide me in right direction.
I'm yet pretty new to React.
Generally, need to make form which I can use for new input, or updating existing data (like some posts, etc.), so I can load API data.
Best regards.
Did you consider using shouldComponentUpdate instead if using getDerivedStateFromProps
something like this may solve your problem:
shouldComponentUpdate(nextProps, nextState) {
const { value: nextPropsValue } = nextProps;
const { value: propsValue } = this.props;
const { value } = this.state;
if (nextPropsValue !== propsValue && nextPropsValue !== value) {
this.setState({
value: nextPropsValue
});
}
return value !== nextState.value;
}
Update the answer adding comparison with current props value
I think using getDerivedStateFromProps here may be unnecessary. If you want to prevent a render in certain cases, consider using shouldComponentUpdate https://reactjs.org/docs/react-component.html#shouldcomponentupdate. But it sounds like you basically just need to use your input change handler to keep the state of the input, which you're already doing.
You should also check this article out on why someone shouldn't use getDerivedStateFromProps. It's very informative.

ReactJS change value of nested state

I have a form in which inputs have an initial value (which is different for each input). I set the value by saving the data in a state array, like so:
function createData(header, content) {
id += 1;
return {header, content, id};
}
class ExampleForm extends React.Component {
constructor(props) {
super(props);
this.state = {
data : [
createData( Header1, Content1),
createData( Header2, Content2),
createData( Header3, Content3),
]
}
Once the user startes typing something into the input field, I need to change the value of the state in in question in the corresponding array, however, I'm having trouble correctly targeting said state.
This is what I'm currently doing:
handleInputChange = (value, target) => {
const selectedArray = {...this.state.data[target]};
selArray.header = value;
this.setState(selArray);
};
}
(Note: value is the new value, target is the index)
However, it doesn't work because when I console.log() the new value, it still returns the old value.
I also read several other questions on here (with the help of with I wrote the code for what I'm doing right now in the function that changes the value), however, that did not work.
Other questions I read include React: Update nested state? , however, I could not figure out where they got getPersonsData() from, for instance. Does anyone know what I'm doing wrong in my current code? How do I fix it?
Update the state like this:
handleInputChange = (value, target) => {
const data = [...this.state.data];
data[target].header = value;
this.setState({ data });
};
To check the updated state value, use callback method, like this:
handleInputChange = (value, target) => {
const data = [...this.state.data];
data[target].header = value;
this.setState(
{ data },
() => console.log('updated state', this.state.data)
);
};
For more details about asyn behaviour of setState check this answer: Why calling setState method doesn't mutate the state immediately?

adding row to a table in react, row is added, but data is not

I have a table of about 500 items, all I'm trying to do is push data to the this.state.data and re-render the table. What's happening is that the row is added, but the data is not shown in the row. If I do a this.forceUpdate(), after a short time, then the data magically appears in the row. I'm assuming my re-render is occurring before the state is updated, how do I get around this? Here's the code that's adding to this.state.data and re-rendering:
// the format we expect
scrubData: function(rawData, cb) {
const scrubbedData = rawData.map((object, idx) => {
let { mediaserver = [] } = object.Relations;
let { latitude = [0], longitude = [0] } = object.Properties;
return {
label: object.Label || '',
name: object.Name || '',
mediaserver: mediaserver[0] || '',
geo: `${latitude[0]}, ${longitude[0]}`,
status: '',
event: '',
}
});
if (cb) {
cb(scrubbedData);
return;
}
return scrubbedData;
},
// push one item to the data array, so we don't have to call
// the entire data set all over again
addData: function(rawData) {
const scrubbedData = this.scrubData([rawData], (scrubbedData) => {
this.state.data.unshift(scrubbedData);
this.setState({
data: this.state.data,
});
});
},
It's because you are using unshift. With react, you should always mutate state by providing updated state, not by changing the current state.
Instead of using unshift, you can use something like concat, or the spread operator.
Using concat:
this.state.data.concat([scrubbedData]);
this.setState({
data: this.state.data,
});
Or using the spread operator:
this.setState({
data: [...this.state.data, scrubbedData]
});
I recommend checking out this stackoverflow post which lists all the mutating methods for arrays.
React works internally when doing the state diff by comparing references to objects/arrays, and since you are mutating the array, the reference is still the same, so React does not detect that a change has been made.
Herein lies the benefit of using an immutable library with React, as changes will always produce a copy, so you can remove an entire class of bugs such as this.
EDIT:
You are calling this.scrubData before adding the new row, and the result is that the new row has none of the additional data that you want appended to it. Try adding the new row to the array first, and then calling that function to append data to each row.
Okay, so I finally got this to work. I had to use componentWillUpdate. Not sure if this is correct, but it works now.
export default class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
tableHeight: "50vh",
data: [],
}
}
componentWillUpdate(nextProps, nextState) {
this.state.data = nextProps.data;
}
render() {
const tableRow = this.state.data.map((object, idx) => {
return (
<TableRow key={idx} data={object} />
)
})
return (
<div className="table">
<tbody>
{tableRow}
</tbody>
</table>
</div>
</div>
)
}
}

How to know if all the setState updates have been applied to the state in a React component?

I was reading the documentation about React setState, which says:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
Now I have a component like this:
class NoteScreenComponent extends React.Component {
constructor() {
super();
this.state = { note: Note.newNote() }
}
componentWillMount() {
this.setState({ note: this.props.note });
}
noteComponent_change = (propName, propValue) => {
this.setState((prevState, props) => {
let note = Object.assign({}, prevState.note);
note[propName] = propValue;
return { note: note }
});
}
title_changeText = (text) => {
this.noteComponent_change('title', text);
}
body_changeText = (text) => {
this.noteComponent_change('body', text);
}
saveNoteButton_press = () => {
// Save note to SQL database
Note.save(this.state.note)
}
render() {
return (
<View>
<TextInput value={this.state.note.title} onChangeText={this.title_changeText} />
<TextInput value={this.state.note.body} onChangeText={this.body_changeText} />
<Button title="Save note" onPress={this.saveNoteButton_press} />
</View>
);
}
}
What I'm wondering is, since setState does not update the state immediately, how can I know if the note I'm saving in saveNoteButton_press is the current version of the state? Is there some callback or something that I could poll to know if state has been fully updated?
What they are warning against is trying to do something in the same event loop.
method = () => {
this.setState({ note: 'A' })
saveNote(this.state.note) // <-- this.state.note will not have been updated yet.
}
or to setState using previous state:
method = () => {
let note = this.state.note // possible that `this.state.note` is scheduled to change
this.setState({ note: note + 'B' })
}
Since your user is going to be pushing the button after the setState scheduling, the state will have already been updated.
..but for theory's sake, let's imagine that somehow the input event and button happen in the exact same moment.. what would be the correct solution? If it was a single function call you probably wouldn't be using the new state since you already have the new note and the previous state.
method = (text) => {
let noteToSave = this.state.note + text // old state + new value
saveNote(noteToSave) // maybe this will fail
.then(response => this.setState({ note: noteToSave }))
.catch(err => this.setState({ error: 'something went wrong' }))
// optimistically update the ui
this.setState({ note: noteToSave })
}
but probably the most likely solution is to just pass what you want as an argument where you use it, rather than trying to access state which might be in a race condition, since render will happen after any state transitions.
method = (note) => {
noteToSave(note)
}
render() {
return (
<Button onPress={() => this.method(this.state.note)} /> <-- must be up to date here
)
}

Resources