//It's working now - updated code
I'm working on my own autocomplete component because I have problem with passing firebase data to a ready one.
The whole mechanism is working good but I have problem with passing values after getting user input
I'm setting initial state with those values
const INITIAL_STATE = {
allChars: [],
suggestions: [],
value: ""
};
Then in autocomplete class i'm loading all users from database
loadData(){
let self = this;
let characters = firebase.firestore().collection("users");
characters.get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
let document = doc.data();
self.setState(({allChars})=>({
allChars: [
...allChars,
document
]
}))
});
});
}
Here is my getSuggestions function. It is firing on input change
getSuggestions = event => {
const {value, suggestions} = event.target;
this.setState({
value: value,
suggestions: []
})
let suggest = [];
this.state.allChars.map((allChars) => {
if(value.length > 1 && allChars.name.toLowerCase().includes(value.toLowerCase())){
suggest.push (
allChars.name
);
}
})
this.setState({
suggestions: suggest
})
}
In render I just put {sugestions}
But in {suggestions} I get rendered only one name.
one
But when I console.log it - I get two names
two
There should be two.
I tried to set state in this function like in loadData(), but I still get only one value.
Is there other way to get both values into DOM
Full code can be found here: https://github.com/Ilierette/react-planner/blob/master/src/component/elements/Autocomplete.js
I think the reason you are just seeing one element each time your components re-render is that in your map function on your allChars array, when you want to update the suggestions in your state, you are setting just the name each time as a new array while you should update the existing array in your state, so your code should be:
this.setState({
suggestions: [...this.state.suggestions, allChars.name]
})
Related
Within my function, through interaction from the user, I aim slowly build up an array of responses which I then pass off to an API. However, different approaches to append to the array, simply return a single position array (overwrite).
My current code as follows:
const contribution: Array = [];
const handlePress = () => {
var col = {
response,
user: 1,
update: update.id,
question: q.id,
};
contribution = [...contribution, col];
}
My understanding is that contribution = [...contribution, col] is the correct way to add to the array.
What is the best practice approach for doing this inside a function called each time the user interacts?
Although it is not clear from the question, I suspect, this code is inside a component. If so, then a new contribution array is created on every render. You need to use useState to store this array so that a new array is not created on every render.
const [contribution, setContribution] = React.useState([]);
const handlePress = () => {
var col = {
response,
user: 1,
update: update.id,
question: q.id,
};
setContribution([...contribution, col]);
}
Below I am trying to take a copy of the current state of the users data, along with a copy of a new invoice template pulled from a json file that has the key value pairs all blank.
I then change the templates invoiceID to be 1 more than the idx (this is a number value sent from a child component that is the length of all the invoices in state).
Lastly, I take the copy of the users data and add on the new template, then save that new users data back into state so it can be updated within my list.
createInvoice = idx => {
let newUserData = this.state.userData;
let template = UsersJSON[0].invoices[0];
template.invoiceID = idx + 1;
newUserData.invoices.push(template);
this.setState({
userData: newUserData
});
}
This is the current state of all the data when I log in:
After I click New Invoice + once:
The problem starts happening after I click New Invoice + more than once:
ONLY all of the NEW Invoice ID's keep being updated to the latest and greatest IDs. I seriously have no clue why this is happening. Any help would be appreciated!
A link to my project on github (look on the invoices branch, not master):
https://github.com/Brent-W-Anderson/invoice-pdf/tree/invoices
Issues
You are not correctly creating a new array reference for state and react reconciliation.
You are also mutating your template reference object.
Code
createInvoice = idx => {
let newUserData = this.state.userData; // <-- saved state reference
let template = UsersJSON[0].invoices[0];
template.invoiceID = idx + 1; // <-- template mutation
newUserData.invoices.push(template); // <-- mutated state
this.setState({
userData: newUserData // <-- saved state reference back into state
});
}
Solution
Create a shallow copy of all state you intend to update.
createInvoice = idx => {
let newInvoices = [...this.state.userData.invoices]; // <-- create a new array reference
let template = {
...UsersJSON[0].invoices[0], // <-- create new template object reference
};
template.invoiceID = idx + 1;
newInvoices.push(template);
this.setState({
userData: {
...state.userData,
invoices: newInvoices,
}
});
}
A slightly more react-y way to add to state is to map the data from the previous state and spread in the template so you are also not mutating it.
createInvoice = idx => {
this.setState(prevState => ({
userData: {
...prevState.userData,
invoices: [
...prevState.userData.invoices,
{
...UsersJSON[0].invoices[0],
invoiceID: idx + 1,
},
],
},
}));
}
You want to copy a state object (and in JS arrays are objects) before updating it. Maybe let newUserData = [...this.state.userData] would be the way to avoid this bug, but you might need a 'deep copy.'
See https://dev.to/andyrewlee/cheat-sheet-for-updating-objects-and-arrays-in-react-state-48np for more on this topic.
I am new with Firebase and React Native. I have created a database and now that when a certain number is equal to a number in 1 of the objects in firebase, it stores this data in an array.
I have the following code for that:
scanOutput = '0';
getUserData = () => {
console.log(this.scanOutput);
let ref = firebase.database().ref('Questions/1R1/NL');
ref.on('value' , snapshot =>{
var state = snapshot.val();
console.log(state);
if('1' === this.scanOutput){
this.setState({questions: state});
}
})
}
componentDidMount(){
this.scanOutput = this.props.navigation.getParam('output');
this.getUserData();
}
the database looks like this:
Currently the if statement contains a "1" hardcoded, what I want to achieve is that when this.scanOutput (in this case "1") equals "question_number" from the database, it places all data in the state.
Your code is reading all data under Questions/1R1/NL. Since there may be multiple questions under there, the snapshot may contain multiple child nodes. Your callback needs to handle those by looping over snapshot.forEach.
Something like this:
let ref = firebase.database().ref('Questions/1R1/NL');
ref.on('value' , snapshot =>{
snapshot.forEach((question) => {
var state = question.val();
if(state.question_number === this.scanOutput){
this.setState({questions: state});
}
})
})
If snapshot returns you an object then you can destructure it:
const { question_number } = snapshot.val();
console.log(question_number);
then you can have a check like:
if(question_number === this.scanOutput){
I have some list of names that I take from the array using the Fetch method. Now I'm using the method of searchHandler at the click of a button, I enter the input data into the console:
https://codesandbox.io/s/jovial-lovelace-z659k
But I need to enter the input "First name", and click on the button, only a line with that name was displayed. But I don't know how to make the filter myself.
I found the solution on the internet, but unfortunately I can't integrate it into my code.Here it is:
getFilteredData() {
if (!this.state.search){
return this.state.data
}
return this.state.data.filter(item=>{
return item["firstName"].toLowerCase().includes(this.state.search.toLowerCase())
});
}
How to integrate it into my code? And what to write in the render method?
You are in the right direction there. The correct code (with comments explaining the changes) should be:
searchHandler = search => {
// This if checks if search is empty. In that case, it reset the data to print the initial list again
if (search) {
// This 'arr' variable is a copy of what you do in your Table.js
const arr = this.state.data.group && this.state.data.group.first ? this.state.data.group.first : this.state.data;
const filteredArr = arr.filter((item) => {
// Here you compare with 'search' instead of 'state.search', since you didn't updated the state to include the search term
return item["firstName"].toLowerCase().includes(search.toLowerCase())
})
// This will update your state, which also updates the table
this.setState({data: filteredArr})
} else {
// As explained, if search was empty, return everything
this.resetData();
}
};
// This is a copy of what you have in componentDidMount
async resetData() {
const response = await fetch("/data/mass.json");
const data = await response.json();
this.setState({
data
});
}
Note:
includes is not supported by all browsers, as you can see here. If you need a more reliable solution, you could use indexOf, as explained here.
Since your fetched data is an array of objects, and you basically want to filter out the objects which match the serach criteria, heres how you can write your search handler:
searchHandler = search => {
const { data } = this.state;
const filteredData = {
group: {
first: Object.values(data.group.first).filter(item => item.firstName.includes(search)),
}
}
this.setState({ data: filteredData });
};
Basically, what its doing is taking the array of objects out of dataand filter out only those objects which have the name you search for. and sets the filtered array of objects in the same structure as your original data object is and there you go!!
Also you don't have to make any changes to the render method now. Since render method is already working with the state which has data in it. and as soon as you make a search state, data will be updated and available in the render.
I'm trying to pass a property, that is inside the first position of an array of objects, to another module so I can use this value later. I've tried to pass it as module(args), but it keeps reading the default value which is 0. Is there a way to do this?
I tried to implement some React.context but the Bot framework Emulator is refusing it.
/////////////////Module that ll acquire the value/////////////////////////////
getCard(bot, builder, params) {
let configValues = { ...params[0] }
bot.dialog(`${configValues.path}`, function (session) {
var msg = new builder.Message(session);
const cardItem = (obj) => {
return (new builder.HeroCard(session)
.title(`${obj.title}`)
.text(`R$ ${obj.price}`)
.images([builder.CardImage.create(session, `${obj.img}`)])
.buttons([
builder.CardAction.imBack(session, `${obj.price} Item adicionado!`, 'add to cart')
// !onClick event must add the current obj.price to
// the configValues.total(Ex: configValues.total += obj.price)!
])
)
}
msg.attachmentLayout(builder.AttachmentLayout.carousel)
msg.attachments(
eval(params.map(obj => cardItem(obj)))
);
//!in here before end the dialog is where i want to update
// the configValues.total so i can show it in the -> Checkout module
session.send(msg).endDialog()
}).triggerAction({ matches: configValues.regex });
}
}
//////////////CheckOut.Module///////////////////////////////
{...}
let configValues = { ...params[0] }
let state = {
nome: "",
endereco: "",
pagamento: "",
total: configValues.total // this is the value to be read
}
bot.dialog('/intent', [
{...},
(session, results) => {
state.pagamento = results.response
session.send(
JSON.stringify(state) // here is the place to be printed
)
{...}
]
).triggerAction({ matches: /^(finalizar|checar|encerrar|confirmar pedido|terminar)/i })
Since you solved your original problem, I'll answer the one in your comment.
Your problem is here:
cartId.map((obj, i , arr) => {
// if (!obj.total) {
// obj.total.reduce(i => i += i)
// }
const newtotal = new total
newtotal.getTotals(bot, builder, obj, arr)
})
cartId contains the totals for each of your items. When you call map on it, you're passing each item individually to getTotals, which passes each item to checkout()
The reason you can't sum all of the totals and can only sum one item's total is that you pass cartId to checkout and cartId has been changed to just a single item. Instead, there's a couple of different things you could do:
Pass the whole cartId from cartItems and use something like for (var key in cartItems) in totalConstructor() and checkoutConstructor(). This is probably the easiest, but not very memory efficient.
Use BotBuilder's State Storage to store your totals array in userData, then sum that at the end. This might be more difficult to implement, but would be a much better route to go. Here's a sample that can help you get started.