How to get default value of dropdown input in reactjs - javascript

I am trying to get the default value of dropdown input field as I am using it for both add and update. Below is my code
<BrInput
type="select"
name="altPayableInd"
id="altPayableInd"
options={altPayableIndOpt}
default={payableResp.payable[index] && payableResp.payable[index].altPayableInd.value && this.setDefaultValue(payableResp.payable[index] && payableResp.payable[index].altPayableInd.value, altPayableIndOpt)}
/>
setDefaultValue = (value, options) => {
let matchedLabel = options.find(el => el.value === value);
if (value !== "O") {
this.setState({ viewReason: false })
}
return [{ value, label: matchedLabel.label }]
}
error :
Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.

Ideally, you should use only get methods when you are setting prop values in render.
In setDefaultValue you are using setState, which becomes invalid, thats what your error says. You have to move this to some another logic.
To make this work as it is, use as this: (code is commented here, just to show error part, you can remove this)
setDefaultValue = (value, options) => {
let matchedLabel = options.find(el => el.value === value);
// if (value !== "O") {
// this.setState({ viewReason: false }) // you can't use setState in render
// }
return [{ value, label: matchedLabel.label }]
}

Related

Data not getting updated in an array of object when changed value for one key

In an array of certain records in a table, which are comprised of Date and Text fields, any input is given or changed will set data accordingly. My concern is that when Date input is changed Text value is undefined although we have data for it on table, whereas when Text input is changed Date value is undefined. My intention is that I need to show all data associated to it with that respective id. Please go through the code below:
const handleChange = (data) => {
const { id: newId, textVal: franchise, dateVal: dateData } = data;
setDataNew((prevInfo) => {
const newList = [...prevInfo];
const index = newList.findIndex((datum) => datum.newId === newId);
if (index !== -1) {
if (newId !== undefined) {
newList[index].newId = newId;
}
if (franchise !== undefined) {
newList[index].franchise = franchise;
}
if (dateData !== undefined) {
newList[index].dateData = dateData;
}
} else {
newList.push({ newId, franchise, dateData });
}
return [...newList];
});
};
As you can see from above code, we have data shown only on onChange. How can we show both object data when any either of data is changed.
Please refer to the image as well, when any data changed for date text is undefined and vice-versa for other.
Please also refer to codesandbox link --> https://codesandbox.io/s/dazzling-frost-htyzhm?file=/src/Table.js
In the codesandbox I see that dataNew it's initialized as an empty array, so when you call setDataNew() it creates a new object that has only the properties you're setting in the update function.
You need to initialize dataNew with the data that you are using on the table.
I have looked through your code an i noticed that you are missing to pass those values in your callbacks.
So extend you callbacks in your Grid.js like this:
onChange={(e) => {
textCallback({
textVal: e.target.value,
id: rowData[0].id,
dateVal: rowData[4]
});
}}
and
onChange={(e) => {
dateCallback({
textVal: e.target.value,
id: rowData[0].id,
dateVal: rowData[3]
});
}}

setState hook changing another state

I'm new to React. I'm having the next problem...
At my functional component I have many states, there are 2 that have the sames fields (one is for an auxiliary operation)
const [fieldsToEdit, setFieldsToEdit] = useState({}); // This one get populated after the first render
const [auxFields, setAuxFields] = useState({.....})
Now, I have a button that calls a function, this functions just edits the 'fieldsToEdit', but it is editing the auxFields too! I realized this writing console.logs after and before of the setState call.
const updateEditHandler = (event) => {
event.persist());
setFieldsToEdit((prevState) => {
const { name, value } = event.target;
if(name === "fecha_presentacion")
prevState[name] = value;
else
prevState[name] = Number(value);
return ({
...prevState
});
}
Am I doing it wrong? Hope you can help me.
You should not mutate state. Instead create a new object without modifying the previous one.
prevState[name] = value;
return { ...prevState };
The above first mutates the previous state, then returns a copy of it. Instead return a copy that contains the new value without modifying the previous state.
return { ...prevState, [name]: value };
The above copies the previous state and adds or overrides the (evaluated) name property with value. This is all done without mutating prevState.
Applying this to your actual code you would get the following.
setFieldsToEdit((prevState) => {
const { name, value } = event.target;
if (name == "fecha_presentacion") {
return { ...prevState, [name]: Number(value) };
} else {
return { ...prevState, [name]: value };
}
});
// or (depending on preference)
setFieldsToEdit((prevState) => {
let { name, value } = event.target;
if (name == "fecha_presentacion") value = Number(value);
return { ...prevState, [name]: value };
});
setFieldsToEdit((prevState) => {
const { name, value } = event.target;
if(name === "fecha_presentacion")
prevState[name] = value;
else
prevState[name] = Number(value);
return ({
...prevState
});
In prevState[name] = you mutating the state. You need to clone it and set it with either lodash deepClone or JSON.stringify and then JSON.parse if you are not familiar with lodash.
You are not sharing your code so useState({.....}) does not mean anything.
But from the general picture I am getting from your code I think that fieldsToEdit and auxFields have the same reference so because you prevState[name] = you are changing both
Try to not mutate your state in any framework

How to clear fields after callback from axios?

I have modal component with form. I want to inform fields of this form that form data was successfully sent to database and clear its fields.
Component code:
//ItemModal.js
addItem(e) {
e.preventDefault();
const item = {
id: this.props.itemsStore.length + 1,
image: this.fileInput.files[0] || 'http://via.placeholder.com/350x150',
tags: this.tagInput.value,
place: this.placeInput.value,
details: this.detailsInput.value
}
console.log('addded', item);
this.props.onAddItem(item);
this.fileInput.value = '';
this.tagInput.value = '';
this.placeInput.value = '';
this.detailsInput.value = '';
this.setState({
filled: {
...this.state.filled,
place: false,
tags: false
},
loadingText: 'Loading...'
});
}
...
render() {
return (
<div className="text-center" >
<div className={"text-center form-notification " + ((this.state.loadingText) ? 'form-notification__active' : '' )}>
{(this.state.loadingText) ? ((this.props.loadingState === true) ? 'Item added' : this.state.loadingText) : '' }
</div>
)
}
action.js
export function onAddItem(item) {
axios.post('http://localhost:3001/api/items/', item )
.then(res => {
dispatch({type:"ADD_ITEM", item});
dispatch({type:"ITEM_LOADED", status: true});
})
}
helper.js
else if (action.type === 'ITEM_LOADED') {
const status = action.status;
return {
...state,
isItemLoaded: status
}
}
Currently I have few issues with my code:
1. field are clearing right after click, but they should clear after changing state of loadingState. I tried to check it in separate function on in componentWillReceiveProps whether state is changed and it worked, but I faces another problem, that after closing this modal there were errors, that such fields doesn't exist.
2. loadingText should become '' (empty) after few seconds. Tried same approach with separate function and componentWillReceiveProps as at first issue.
In constructor keep a copy of your initial state in a const as follows:
const stateCopy = Object.create(this.state);
When your ajax request completes, in the sucess callback you can reset the state with this copy as follows:
this.setStae({
...stateCopy
});
One of the few ways to achieve this is to use async await which will resolve the promises and then return the value after that you can clear the values
1st approach using the async await
Here is the example
handleSubmit = async event => {
event.preventDefault();
// Promise is resolved and value is inside of the response const.
const response = await API.delete(`users/${this.state.id}`);
//dispatch your reducers
};
Now in your react component call it
PostData() {
const res = await handleSubmit();
//empty your model and values
}
Second approach is to use the timer to check the value is changed or not
for this we need one variable add this to the service
let timerFinished=false;
one function to check it is changed or not
CheckTimers = () => {
setTimeout(() => {
if (timerFinished) {
//empty your modal and clear the values
} else {
this.CheckTimers();
}
}, 200);
}
on your add item change this variable value
export function onAddItem(item) {
axios.post('http://localhost:3001/api/items/', item)
.then(res => {
timerFinished = true;
dispatch({
type: "ADD_ITEM",
item
});
dispatch({
type: "ITEM_LOADED",
status: true
});
})
}
and here is how we need to call it.
PostData = (items) => {
timerFinished = false;
onAddItem(items);
this.CheckTimers();
}
If you check this what we done is continuously checking the variable change and emptied only once its done.
One thing you need to handle is to when axios failed to post the data you need to change the variable value to something and handle it, you can do it using the different values 'error','failed','success' to the timerFinished variable.

Warning in React js array

I have this function
static getDerivedStateFromProps(nextProps, prevState) {
let { fetching } = nextProps
const { error } = nextProps
if (prevState.fetching !== fetching && !fetching) {
fetching = error
}
const userId =
Object.keys(nextProps.match.params).length > 0
? nextProps.match.params[Object.keys(nextProps.match.params)[0]]
: 'new'
if (userId !== 'new') {
const itemUser = nextProps.usersList.filter(item => {
if (String(item.userid) === userId)
return item
})
return { profileItem: { ...prevState.profileItem, ...itemUser[0] }, index: userId, fetching }
}
return { fetching }
}
It works and does what it is suppoused to do, but I want to get rid of this warning:
Expected to return a value at the end of arrow function array-callback-return
It says the problem is on the line
const itemUser = nextProps.usersList.filter(item => {
Since filter's callback expects you to return a boolean, you can just rewrite that line to:
const itemUser = nextProps.usersList.filter(item => String(item.userid) === userId)
The problem exists, because of this function:
item => {
if (String(item.userid) === userId)
return item
}
If item.userid != userId, you're currently not returning anything, so it implicitly returns undefined. It's good practice to always return something, even if it's null or false. In this case, your function is working as expected, because the filter callback expects a boolean. When you return item, item is truthy and thus the filter includes that item. Additionally, if you don't return anything, it implicitly returns undefined, which is falsy, and thus filters out the item.
In the end, since you're trying to return one item, you should ideally be using .find() instead. This will prevent excess iterations after the item is found, since you're only ever looking for exactly one item:
const itemUser = nextProps.usersList.find(item => String(item.userid) === userId);
return { profileItem: { ...prevState.profileItem, ...itemUser }, index: userId, fetching }

Vue.js computed property not updating

I'm using a Vue.js computed property but am running into an issue: The computed method IS being called at the correct times, but the value returned by the computed method is being ignored!
My method
computed: {
filteredClasses() {
let classes = this.project.classes
const ret = classes && classes.map(klass => {
const klassRet = Object.assign({}, klass)
klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))
return klassRet
})
console.log(JSON.stringify(ret))
return ret
}
}
The values printed out by the console.log statement are correct, but when I use filteredClasses in template, it just uses the first cached value and never updates the template. This is confirmed by Vue chrome devtools (filteredClasses never changes after the initial caching).
Could anyone give me some info as to why this is happening?
Project.vue
<template>
<div>
<div class="card light-blue white-text">
<div class="card-content row">
<div class="col s4 input-field-white inline">
<input type="text" v-model="filter.name" id="filter-name">
<label for="filter-name">Name</label>
</div>
<div class="col s2 input-field-white inline">
<input type="text" v-model="filter.status" id="filter-status">
<label for="filter-status">Status (PASS or FAIL)</label>
</div>
<div class="col s2 input-field-white inline">
<input type="text" v-model="filter.apkVersion" id="filter-apkVersion">
<label for="filter-apkVersion">APK Version</label>
</div>
<div class="col s4 input-field-white inline">
<input type="text" v-model="filter.executionStatus" id="filter-executionStatus">
<label for="filter-executionStatus">Execution Status (RUNNING, QUEUED, or IDLE)</label>
</div>
</div>
</div>
<div v-for="(klass, classIndex) in filteredClasses">
<ClassView :klass-raw="klass"/>
</div>
</div>
</template>
<script>
import ClassView from "./ClassView.vue"
export default {
name: "ProjectView",
props: {
projectId: {
type: String,
default() {
return this.$route.params.id
}
}
},
data() {
return {
project: {},
filter: {
name: "",
status: "",
apkVersion: "",
executionStatus: ""
}
}
},
async created() {
// Get initial data
const res = await this.$lokka.query(`{
project(id: "${this.projectId}") {
name
classes {
name
methods {
id
name
reports
executionStatus
}
}
}
}`)
// Augment this data with latestReport and expanded
const reportPromises = []
const reportMeta = []
for(let i = 0; i < res.project.classes.length; ++i) {
const klass = res.project.classes[i];
for(let j = 0; j < klass.methods.length; ++j) {
res.project.classes[i].methods[j].expanded = false
const meth = klass.methods[j]
if(meth.reports && meth.reports.length) {
reportPromises.push(
this.$lokka.query(`{
report(id: "${meth.reports[meth.reports.length-1]}") {
id
status
apkVersion
steps {
status platform message time
}
}
}`)
.then(res => res.report)
)
reportMeta.push({
classIndex: i,
methodIndex: j
})
}
}
}
// Send all report requests in parallel
const reports = await Promise.all(reportPromises)
for(let i = 0; i < reports.length; ++i) {
const {classIndex, methodIndex} = reportMeta[i]
res.project.classes[classIndex]
.methods[methodIndex]
.latestReport = reports[i]
}
this.project = res.project
// Establish WebSocket connection and set up event handlers
this.registerExecutorSocket()
},
computed: {
filteredClasses() {
let classes = this.project.classes
const ret = classes && classes.map(klass => {
const klassRet = Object.assign({}, klass)
klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))
return klassRet
})
console.log(JSON.stringify(ret))
return ret
}
},
methods: {
isFiltered(method, klass) {
const nameFilter = this.testFilter(
this.filter.name,
klass.name + "." + method.name
)
const statusFilter = this.testFilter(
this.filter.status,
method.latestReport && method.latestReport.status
)
const apkVersionFilter = this.testFilter(
this.filter.apkVersion,
method.latestReport && method.latestReport.apkVersion
)
const executionStatusFilter = this.testFilter(
this.filter.executionStatus,
method.executionStatus
)
return nameFilter && statusFilter && apkVersionFilter && executionStatusFilter
},
testFilter(filter, item) {
item = item || ""
let outerRet = !filter ||
// Split on '&' operator
filter.toLowerCase().split("&").map(x => x.trim()).map(seg =>
// Split on '|' operator
seg.split("|").map(x => x.trim()).map(segment => {
let quoted = false, postOp = x => x
// Check for negation
if(segment.indexOf("!") === 0) {
if(segment.length > 1) {
segment = segment.slice(1, segment.length)
postOp = x => !x
}
}
// Check for quoted
if(segment.indexOf("'") === 0 || segment.indexOf("\"") === 0) {
if(segment[segment.length-1] === segment[0]) {
segment = segment.slice(1, segment.length-1)
quoted = true
}
}
if(!quoted || segment !== "") {
//console.log(`Item: ${item}, Segment: ${segment}`)
//console.log(`Result: ${item.toLowerCase().includes(segment)}`)
//console.log(`Result': ${postOp(item.toLowerCase().includes(segment))}`)
}
let innerRet = quoted && segment === "" ?
postOp(!item) :
postOp(item.toLowerCase().includes(segment))
//console.log(`InnerRet(${filter}, ${item}): ${innerRet}`)
return innerRet
}).reduce((x, y) => x || y, false)
).reduce((x, y) => x && y, true)
//console.log(`OuterRet(${filter}, ${item}): ${outerRet}`)
return outerRet
},
execute(methID, klassI, methI) {
this.project.classes[klassI].methods[methI].executionStatus = "QUEUED"
// Make HTTP request to execute method
this.$http.post("/api/Method/" + methID + "/Execute")
.then(response => {
}, error =>
console.log("Couldn't execute Test: " + JSON.stringify(error))
)
},
registerExecutorSocket() {
const socket = new WebSocket("ws://localhost:4567/api/Executor/")
socket.onmessage = msg => {
const {methodID, report, executionStatus} = JSON.parse(msg.data)
for(let i = 0; i < this.project.classes.length; ++i) {
const klass = this.project.classes[i]
for(let j = 0; j < klass.methods.length; ++j) {
const meth = klass.methods[j]
if(meth.id === methodID) {
if(report)
this.project.classes[i].methods[j].latestReport = report
if(executionStatus)
this.project.classes[i].methods[j].executionStatus = executionStatus
return
}
}
}
}
},
prettyName: function(name) {
const split = name.split(".")
return split[split.length-1]
}
},
components: {
"ClassView": ClassView
}
}
</script>
<style scoped>
</style>
If your intention is for the computed property to update when project.classes.someSubProperty changes, that sub-property has to exist when the computed property is defined. Vue cannot detect property addition or deletion, only changes to existing properties.
This has bitten me when using a Vuex store with en empty state object. My subsequent changes to the state would not result in computed properties that depend on it being re-evaluated. Adding explicit keys with null values to the Veux state solved that problem.
I'm not sure whether explicit keys are feasible in your case but it might help explain why the computed property goes stale.
Vue reactiviy docs, for more info:
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
I've ran into similar issue before and solved it by using a regular method instead of computed property. Just move everything into a method and return your ret.
Official docs.
I had this issue when the value was undefined, then computed cannot detect it changing. I fixed it by giving it an empty initial value.
according to the Vue documentation
I have a workaround for this kind of situations I don't know if you like it. I place an integer property under data() (let's call it trigger) and every time the object that I used in computed property changes, it gets incremented by 1. So, this way, computed property updates every time the object changes.
Example:
export default {
data() {
return {
trigger: 0, // this will increment by 1 every time obj changes
obj: { x: 1 }, // the object used in computed property
};
},
computed: {
objComputed() {
// do anything with this.trigger. I'll log it to the console, just to be using it
console.log(this.trigger);
// thanks to this.trigger being used above, this line will work
return this.obj.y;
},
},
methods: {
updateObj() {
this.trigger += 1;
this.obj.y = true;
},
},
};
here's working a link
If you add console.log before returning, you may be able to see computed value in filteredClasses.
But DOM will not updated for some reason.
Then you need to force to re-render DOM.
The best way to re-render is just adding key as computed value like below.
<div
:key="JSON.stringify(filteredClasses)"
v-for="(klass, classIndex) in filteredClasses"
>
<ClassView
:key="classIndex"
:klass-raw="klass"
/>
</div>
Caution:
Don’t use non-primitive values like objects and arrays as keys. Use string or numeric values instead.
That is why I converted array filteredClasses to string. (There can be other array->string convert methods)
And I also want to say that "It is recommended to provide a key attribute with v-for whenever possible".
You need to assign a unique key value to the list items in the v-for. Like so..
<ClassView :klass-raw="klass" :key="klass.id"/>
Otherwise, Vue doesn't know which items to udpate. Explanation here https://v2.vuejs.org/v2/guide/list.html#key
I have the same problem because the object is not reactivity cause I change the array by this way: arrayA[0] = value. The arrayA changed but the computed value that calculate from arrayA not trigger. Instead of assign value to the arrayA[0], you need to use $set for example.
You can dive deeper by reading the link below
https://v2.vuejs.org/v2/guide/reactivity.html
I also use some trick like adding a cache = false in computed
compouted: {
data1: {
get: () => {
return data.arrayA[0]
},
cache: false
}
}
For anybody else being stuck with this on Vue3, I just resolved it and was able to get rid of all the this.$forceUpdate()-s that I had needed before by wrapping the values I returned from the setup() function [and needed to be reactive] in a reference using the provided ref() function like this:
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'CardDisplay',
props: {
items: {
type: Array,
default: () => []
},
itemComponent: Object,
maxItemWidth: { type: Number, default: 200 },
itemRatio: { type: Number, default: 1.25 },
gapSize: { type: Number, default: 50 },
maxYCount: { type: Number, default: Infinity }
},
setup () {
return {
containerSize: ref({ width: 0, height: 0 }),
count: ref({ x: 0, y: 0 }),
scale: ref(0),
prevScrollTimestamp: 0,
scroll: ref(0),
isTouched: ref(false),
touchStartX: ref(0),
touchCurrentX: ref(0)
}
},
computed: {
touchDeltaX (): number {
return this.touchCurrentX - this.touchStartX
}
},
...
}
After doing this every change to a wrapped value is reflected immediately!
If you are adding properties to your returned object after vue has registered the object for reactivity then it won't know to listen to those new properties when they change. Here's a similar problem:
let classes = [
{
my_prop: 'hello'
},
{
my_prop: 'hello again'
},
]
If I load up this array into my vue instance, vue will add those properties to its reactivity system and be able to listen to them for changes. However, if I add new properties from within my computed function:
computed: {
computed_classes: {
classes.map( entry => entry.new_prop = some_value )
}
}
Any changes to new_prop won't cause vue to recompute the property, as we never actually added classes.new_prop to vues reactivity system.
To answer your question, you'll need to construct your objects with all reactive properties present before passing them to vue - even if they are simply null. Anyone struggling with vues reactivity system really should read this link: https://v2.vuejs.org/v2/guide/reactivity.html

Categories

Resources