I'm a total newbie with React and I was trying to build an app with React, but after many hours of trying, I couldn't figure out why the elements that I want to delete onClick aren't getting deleted.
There are 2 similar posts on this, they say that you would need to use independent keys for every element. I tried to do that, I even created a different variable and increment it after every use. It just deletes the top element of the list.
How it works - 1) I have an array which is stored with some names channels, and I get data with that names and save that data into another array renderAll.
2) After that I filter those on how I want to render them, and then I render them with a function renderCards(). It also renders a button which if clicked, should delete the channel from the channel array and the respective data from the renderAll array
3) It also have a input field from which you can add more channels.
What doesn't work - The delete button deletes the top element instead of the element which is clicked.
I have the app running without the delete functionality
var App = React.createClass({
getInitialState() {
return {
status: 2
}
},
changeStatus(i) {
this.setState({
status: i
});
},
render() {
return (
<div>
<header><h1>Twitch Streamers</h1></header>
<Tabs status = {this.changeStatus} />
<Cards status = {this.state.status} />
</div>
);
}
});
const Cards = React.createClass({
getInitialState() {
return {
renderAll: [],
check: this.props.status,
channels: ["freecodecamp", "storbeck", "habathcx","meteos","RobotCaleb","noobs2ninjas","brunofin","comster404","cretetion","sheevergaming","TR7K","OgamingSC2","ESL_SC2"]
};
}, //AJAX REQUEST FUNCTION
getData(last) {
if(last === undefined) {
for(let i =0; i<this.state.channels.length;i++) {
let channel = this.state.channels[i];
$.getJSON("https://api.twitch.tv/kraken/streams/" + channel, (data) => {
$.getJSON("https://api.twitch.tv/kraken/channels/" + channel, (logo) => {
if(data.hasOwnProperty(status) === false) {
if(data.stream === null) {
this.setState({
renderAll: this.state.renderAll.concat([{channel: channel, url: `https://www.twitch.tv/${channel}`, status: 'offline', logo: logo.logo}])
});
} else {
this.setState({
renderAll: this.state.renderAll.concat([{channel: data.stream.channel.name, url: `https://www.twitch.tv/${channel}`, current: data.stream.channel.game + ' - ' + data.stream.channel.status, status: 'online', logo: logo.logo}])
});
}
}
});
})
.fail((jqxhr) => {
this.setState({
renderAll: this.state.renderAll.concat([{channel: channel, status: 'closed'}])
});
});
}
} else {
let channel = this.state.channels[this.state.channels.length - 1];
$.getJSON("https://api.twitch.tv/kraken/streams/" + channel, (data) => {
$.getJSON("https://api.twitch.tv/kraken/channels/" + channel, (logo) => {
if(data.hasOwnProperty(status) === false) {
if(data.stream === null) {
this.setState({
renderAll: this.state.renderAll.concat([{channel: channel, url: `https://www.twitch.tv/${channel}`, status: 'offline', logo: logo.logo}])
});
} else {
this.setState({
renderAll: this.state.renderAll.concat([{channel: data.stream.channel.name, url: `https://www.twitch.tv/${channel}`, current: data.stream.channel.game + ' - ' + data.stream.channel.status, status: 'online', logo: logo.logo}])
});
}
}
});
})
.fail((jqxhr) => {
this.setState({
renderAll: this.state.renderAll.concat([{channel: channel, status: 'closed'}])
});
});
}
},
componentWillMount() {
this.getData();
},
componentWillReceiveProps(prop) {
this.setState({
check: prop
});
}, //DELETE FUNCTION THAT DOESN'T WORK
delete(index) {
let newArr = this.state.channels.slice();
let newArrSecond = this.state.renderAll.slice();
newArr.splice(index, 1);
newArrSecond.splice(index, 1);
this.setState({
channels: newArr,
renderAll: newArrSecond
});
}, //RENDER CARDS FUNCTION
renderCards(i) {
if(i === 0 || i.status === 0) {
let cards = this.state.renderAll.map((item, i) => {
if(item.status === 'online') {
return <div className="online cards" key={i}><img src={item.logo} width="30px" height="30px" /><a target="_blank" href={item.url}><h3>{item.channel}</h3></a><button className="cross" onClick={this.delete}>✕</button><p>{item.current}</p></div>
}
});
return (
cards
)
} else if(i === 1 || i.status === 1) {
let cards = this.state.renderAll.map((item, i) => {
if(item.status === 'offline') {
return <div className="offline cards" key={i}><img src={item.logo} width="30px" height="30px"/><a target="_blank" href={item.url}><h3>{item.channel}</h3></a><button className="cross" onClick={this.delete}>✕</button><p>Channel is offline</p></div>
}
});
return (
cards
)
} else if(i === 2 || i.status === 2) {
let cards = this.state.renderAll.map((item, i) => {
if(item.status === 'offline') {
return <div className="offline cards" key={i}><img src={item.logo} width="30px" height="30px" /><a target="_blank" href={item.url}><h3>{item.channel}</h3></a><button className="cross" onClick={this.delete}>✕</button><p>Channel is offline</p></div>
} else if(item.status === 'online') {
return <div className="online cards" key={i}><img src={item.logo} width="30px" height="30px" /><a target="_blank" href={item.url}><h3>{item.channel}</h3></a><button className="cross" onClick={this.delete}>✕</button><p>{item.current}</p></div>
} else {
return <div className="closed cards" key={i}><h3>{item.channel}</h3><p>Account Closed</p></div>
}
});
return (
cards
)
}
},
newChannel(i) {
if(i.keyCode === 13) {
this.setState({channels: this.state.channels.concat([i.target.value])}, function() {
this.getData(1);
});
}
},
leave(i) {
i.target.value = '';
},
render() {
return (
<div id="cards-inside">
<input type='text' placeholder="+ Channel" onKeyDown={this.newChannel} onBlur={this.leave}/>
<ReactCSSTransitionGroup transitionName="example" transitionEnterTimeout={500} transitionLeaveTimeout={300}>
{this.renderCards(this.state.check)}
</ReactCSSTransitionGroup>
</div>
)
}
});
ReactDOM.render(<App />, document.getElementById("container-second"));
Your index is always 0, because you do not pass it when you call delete.
Therefore it always deletes top element.
Inside the JSX bit where you render your X, you should do:
onClick={this.delete.bind(this, i)}
Or try
OnClick={() => {this.delete(i)} }
to pass the index of the card clicked.
In React, you don't really want to "delete" nodes like you'd do with jQuery.
Let's say you have a list of names in your initial state:
class MyComponent extends React.Component {
state = {
names: ['Foo', 'Bar', 'Git', 'Get']
};
[...]
}
In your render() method, you are rendering them inside a ul:
render() {
const names = this.state.names.map(name => {
return <li key={name} onClick={this.remove.bind(this, name)}>{name}</li>;
});
return <ul>{names}</ul>;
}
In your remove() method you will have:
remove(name) {
this.setState({
names: this.state.names.filter(cur => cur !== name)
});
}
Now, every time you click on a name to remove it, you are removing the name from the names list, the component renders itself again, and the removed name will be removed from the DOM.
Working example:
http://codepen.io/FezVrasta/pen/MeWpzm?editors=0010
Related
I want to explain the use case i have so you can understand it well
I have Cart & Products when the user adds the product (product_id = 1) twice to the cart and the product has the same options (red, xl) I increment the quantity,
if the user adds the same (product_id = 1) but with other options (green, xl), I add this product to the cart as a separate product.
Now this works well!
but when the user after added the above 2 products and add the same product with the same option (red, xl) again, It's added as a separated product!
What I expected if the product options existing before should increment the quantity otherwise added it as a separate.
What i tried
add the option ids into the Product property and check if it exists before or not then handle what I want but it does not work well and added the third product as a separate one!
Screen Record
code snippet
zustand Store
interface CartProductsProp extends State {
cartProducts: ProductProps[];
addToCart: (Products: ProductProps) => void;
updateProductQuantity: (
Product: ProductProps,
updatedQuantity: number,
) => void;
checkProductOptionsExist: (
Product: ProductProps,
updatedQuantity: number,
) => void;
...
}
export const useCartProduct = create<CartProductsProp>(
persist(
(set, get) => ({
cartProducts: [],
addToCart: (product) => {
set((prev) => ({
cartProducts: [...prev.cartProducts, {...product}],
}));
},
checkProductOptionsExist: (
product: ProductProps,
updatedQuantity: number,
) => {
set((prev) => {
console.log('->prev', JSON.stringify(prev.cartProducts));
console.log(
'check==>IDs',
cartProduct.id === product.id &&
product.productOptionIds === cartProduct.productOptionIds,
); // for some reason this run towic when add the third product"with same options as first product "red,xl" true then false
return prev.cartProducts.map((cartProduct) => {
cartProduct.id === product.id &&
product.productOptionIds === cartProduct.productOptionIds
? get().updateProductQuantity(product, updatedQuantity)
: get().addToCart(product);
});
});
},
// To Update the quantity when product exist in cart before
updateProductQuantity: (
product: ProductProps,
updatedQuantity: number,
) => {
set((prev) => {
let currentCart = prev.cartProducts.map((cartProduct) =>
cartProduct.id === product.id &&
areEqual(cartProduct.selectedOptions, product.selectedOptions)
?
{
...product,
quantity: cartProduct?.quantity! + updatedQuantity,
updated: 'yes#',
productTotalPrice:
(cartProduct?.quantity! + updatedQuantity) *
cartProduct.price,
}
: cartProduct,
);
console.log('##currentCart', JSON.stringify(currentCart));
return {
cartProducts: currentCart,
};
});
...
},
}),
{
name: 'cartListProduct-local',
getStorage: () => AsyncStorage,
},
),
);
Product Details
const addProductToCart = () => {
let productOptionIds = allOptions
.map(({id}: {id: number | string}) => id)
.sort()
.join(',');
let currentProduct = {
...item,
id: item.id,
product_id: item.id,
quantity: currentQuantity,
price: updatedPrice,
productTotalPrice: updatedPrice * currentQuantity,
selectedOptions: allOptions,
productOptionIds: productOptionIds,
};
setAddToCartLoading(true);
if (
!cartProductList.some((alreadyExist) => alreadyExist.id === item.id)
) {
addToCart(currentProduct);
Alert.alert(t('addedSuccessfully'));
setAddToCartLoading(false);
} else {
checkProductOptionsExist(currentProduct, currentQuantity);
Alert.alert(t('addedSuccessfully'));
}
};
Utility
export const areEqual = (a: arrayProps = [], b: arrayProps = []): boolean => {
// compare length of arrays
if (a.length !== b.length) {
return false;
}
// get ids set in b
const idsSetInB = new Set(b.map(({id}: {id: number | string}) => id));
console.log('idsSetInB', idsSetInB);
// iterate over a, and check if the id of an item is not in b
for (let {id} of a) {
if (!idsSetInB.has(id)) {
return false;
}
}
// if it passes all the items, return true
return true;
};
I just add the checks in the Product Details, not in the store,
first, get the targeted product from the Cart so I can here check if it exists before or not based on the optionsIDs if the return undefined that's mean the product + options, not in the cart so I add it as a separated product otherwise I update the quantity and it works well.
Maybe I can't do this in the store itself checkProductOptionsExist, (if u have any explanation tell me please)
If u have an any better idea do it please ;)
...
if (
!cartProductList.some((alreadyExist) => alreadyExist.id === item.id)
) {
addToCart(currentProduct);
Alert.alert(t('addedSuccessfully'));
setAddToCartLoading(false);
}
else {
let res = cartProductList.find(
(currentProd) =>
currentProd.product_id === currentProduct.product_id &&
currentProd.productOptionIds === currentProduct.productOptionIds, // or areEqual FC ;)
);
res != null
? updateProductQuantity(currentProduct, currentQuantity)
: addToCart(currentProduct);
setAddToCartLoading(false);
Alert.alert(t('addedSuccessfully'));
}
store
....
updateProductQuantity: (
product: ProductProps,
updatedQuantity: number,
) => {
set((prev) => {
let currentCart = prev.cartProducts.map((cartProduct) =>
cartProduct.id === product.id &&
areEqual(cartProduct.selectedOptions, product.selectedOptions)
? {
...product,
quantity: cartProduct?.quantity! + updatedQuantity,
productTotalPrice:
(cartProduct?.quantity! + updatedQuantity) *
cartProduct.price,
}
: cartProduct,
);
return {
cartProducts: currentCart,
};
});
},
....
I have an array of objects called audioBaby.
When the app launches I check asyncStorage and if any key has value active, I want to update the lock keys in the array.
What I have done is not updating all objects in array but only the last object.
How can I initially update the array from asyncStorage and render the screen?
const [audioBaby, setAudioBaby] = useState([
{
lock: "deactive",
url: "item0.mp3",
},
{
lock: "deactive",
url: "item1.mp3",
},
{
lock: "deactive",
url: "item2.mp3",
},
]);
useEffect(() => {
try {
AsyncStorage.multiGet([
"babyAudio0Status", //value: active
"babyAudio1Status", //value: active
"babyAudio2Status", //value: active
]).then((response) => {
let updatedList = audioBaby;
if (response[0][1] != "null" && response[0][1] == "active") {
updatedList = audioBaby.map((item) => {
if (item.url == "item0.mp3") {
return { ...item, lock: "active" };
}
return item;
});
}
if (response[1][1] != "null" && response[1][1] == "active") {
updatedList = audioBaby.map((item) => {
if (item.url == "item1.mp3") {
return { ...item, lock: "active" };
}
return item;
});
}
if (response[2][1] != "null" && response[2][1] == "active") {
updatedList = audioBaby.map((item) => {
if (item.url == "item2.mp3") {
return { ...item, lock: "active" };
}
return item;
});
}
setAudioBaby(updatedList)
});
} catch (error) {
console.log("error::", error);
}
}, []);
Final array should be like this:
[
{
lock: "active",
url: "item0.mp3",
},
{
lock: "active",
url: "item1.mp3",
},
{
lock: "active",
url: "item2.mp3",
},
]
I moved all ifs to inside of map function and everything is fine.
let updatedList = audioBaby.map((item) => {
if (item.url === 'item0.mp3' && response[0][1] === 'active') {
return { ...item, lock: 'active' }
}
if (item.url === 'item1.mp3' && response[1][1] === 'active') {
return { ...item, lock: 'active' }
}
if (item.url === 'item2.mp3' && response[2][1] === 'active') {
return { ...item, lock: 'active' }
}
return item
})
i'm working on a bot application using react js and botframework webchat. The thing is that i want to clear the text input box (from where msgs are sent) after sending the message - which is selected from the suggestion. The Suggestion list(or autocomplete component) is a custom coded one. And What i mean is that if i type "hr" the suggestion list popup will come and if i click on one option from the suggestion, say 'hr portal', it will be sent, but what i wrote ie "hr" remains there in the input field and i want to clear that. And please note that If i type something and send its working fine. The problem is only when i type something and select something from the suggestion. Everything else is fine. How can i clear that. Any help would be really appreciated.
please find the below image for more understanding.
here's my code;
import React from 'react';
import { DirectLine, ConnectionStatus } from 'botframework-directlinejs';
import ReactWebChat from 'botframework-webchat';
import './ChatComponent.css';
var val;
var apiParameters = [];
var currentFocus = -1;
export default class extends React.Component {
constructor(props) {
super(props);
this.state = {
token: '',
conversationId: '',
directLine: {},
view: false,
feedBack: null,
value: '',
popupContent: '',
storeValue: '',
suggestions: [],
suggestionCallback: '',
suggestionTypedText: "",
typingChecking: "false",
};
this.handleTokenGeneration = this.handleTokenGeneration.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSaveFeedback = this.handleSaveFeedback.bind(this);
this.handleSuggestion = this.handleSuggestion.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleSuggestionClick = this.handleSuggestionClick.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.moveHighlight = this.moveHighlight.bind(this);
this.getSuggestionHtml = this.getSuggestionHtml.bind(this);
}
getSuggestionHtml(suggestion) {
const lowerCaseSuggestion = suggestion.toLowerCase();
return {
__html: lowerCaseSuggestion.includes(this.state.suggestionTypedText) ? lowerCaseSuggestion
.replace(this.state.suggestionTypedText, `<b>${this.state.suggestionTypedText}</b>`) : lowerCaseSuggestion
};
}
handleTokenGeneration = async () => {
console.log("11111");
const response = await fetch(`api/TokenGenerationService/GetToken`);
const data = await response.json();
this.setState({
token: data.categoryObject.token, conversationId:
data.categoryObject.conversationId
});
this.state.directLine = new DirectLine({ token: this.state.token });
this.setState({ view: true });
this.setState({ typingChecking: "true" });
console.log("conversationId");
};
async handleSuggestion(val, store) {
if (val === "") {
this.setState({
suggestions: []
});
}
else {
apiParameters = [];
var valuess = null;
const response = await fetch(`api/TokenGenerationService/GetAzureSearch?myparam1=${val}`);
const data = await response.json();
var values = ["Hello", "yes", "no", "exit", "Welcome", "Thank You", "Approve", "Apply leave", "Reject", "Absence Balance", "Leave Balance", "Upcoming Holidays", "Apply Comp-Off", "Approve Leave", "Raise Incident Tickets", "Project Allocation Info", "Reporting Manager Change", "Reporting Manager Approval", "Approve Isolve Tickets", "My Manager", "My Account Manager", "Generate Salary Certificate", "Isolve Ticket Status", "Internal Job Posting", "My Designation", "My Joining Date", "RM Approval", "RM Change", "Resource Allocation", "ESettlement Approval", "SO Approval", "Cash advance Approval", "Purchase Request Approval", "Referral status", "HR Ticket", "Platinum Support"];
valuess = values.filter(s =>
s.toLocaleLowerCase().startsWith(val.toLowerCase())
);
valuess = valuess.concat(data.az_search);
this.setState({
suggestions: valuess,
suggestionCallback: store,
suggestionTypedText: val.toLowerCase()
});
// alert(data.az_search);
var totCount = data.az_search;
console.log("kkkkkk" + totCount);
}
}
moveHighlight(event, direction) {
event.preventDefault();
const { highlightedIndex, suggestions } = this.state;
if (!suggestions.length) return;
let newIndex = (highlightedIndex + direction + suggestions.length) % suggestions.length;
if (newIndex !== highlightedIndex) {
this.setState({
highlightedIndex: newIndex,
});
}
}
keyDownHandlers = {
ArrowDown(event) {
this.moveHighlight(event, 1);
},
ArrowUp(event) {
this.moveHighlight(event, -1);
},
Enter(event) {
const { suggestions } = this.state;
if (!suggestions.length) {
// menu is closed so there is no selection to accept -> do nothing
return
}
event.preventDefault()
this.applySuggestion(suggestions[this.state.highlightedIndex]);
},
}
handleKeyDown(event) {
// console.log("lokkkkkkkkkkkk")
if (this.keyDownHandlers[event.key])
this.keyDownHandlers[event.key].call(this, event)
}
async handleSuggestionClick(event) {
await this.applySuggestion(event.currentTarget.textContent);
}
async applySuggestion(newValue) {
//newValue = null;
await this.setState({ typingChecking: "false", suggestions: [], highlightedIndex: 0 });
this.state.suggestionCallback.dispatch({
type: 'WEB_CHAT/SEND_MESSAGE',
payload: {
text: newValue
}
});
await this.setState({ typingChecking: "true" });
}
getSuggestionCss(index) {
var HIGHLIGHTED_CSS = "HIGHLIGHTED_CSS";
var SUGGESTION_CSS = "SUGGESTION_CSS";
return index === this.state.highlightedIndex ? HIGHLIGHTED_CSS : SUGGESTION_CSS;
}
handleClose(elmnt) {
var x = document.getElementsByClassName("autocomplete-items");
for (var i = 0; i < x.length; i++) {
if (elmnt !== x[i]) {
x[i].parentNode.removeChild(x[i]);
}
}
}
async componentDidMount() {
try {
await this.handleTokenGeneration();
const store =
window.WebChat.createStore(
{},
({ getState }) => next => action => {
this.state.directLine.connectionStatus$
.subscribe(connectionStatus => {
if (connectionStatus === ConnectionStatus.ExpiredToken) {
console.log("expired");
}
if (action.type === 'WEB_CHAT/SET_SEND_BOX') {
const val = action.payload.text;
if (this.state.typingChecking === "true") {
this.setState({
highlightedIndex: -1,
});
console.log(this.state.typingChecking);
this.handleSuggestion(val, store);
}
}
if (action.type === 'DIRECT_LINE/DISCONNECT_FULFILLED') {
console.log("final" + connectionStatus);
console.log("finalexpired" + ConnectionStatus.ExpiredToken);
console.log("final");
this.handleTokenGeneration();
}
});
return next(action)
}
);
this.setState({ storeValue: store });
} catch (error) {
console.log("error in fetching token");
console.log(error);
}
this.state.directLine.activity$
.filter(activity => activity.type === 'message')
.subscribe(function (activity) {
//console.log("oooooooooooooooooooooo");
}
// message => console.log("received message ", message.text)
);
}
handleSaveFeedback(ans) {
// console.log(this.state.conversationId);
// console.log(this.state.feedBack);
var userID = "C94570";
var feedbackmsg = this.state.value;
var feedbacktype = this.state.feedBack;
var convId = this.state.conversationId;
fetch('api/Feedback/SaveFeedback',
{
method: "POST",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ Uid: userID, FeedbackMessage: feedbackmsg, Convid: convId, FeedbackType: feedbacktype })
}).
then(response => response.text())
.then(data => {
console.log(data.getResult);
});
this.setState({ value: '' });
}
feedback(ans) {
this.setState({ feedBack: ans });
if (ans === "Send") {
this.handleSaveFeedback(ans);
}
else if (ans === "Yes") {
this.setState({ popupContent: "How was your experience?" });
// console.log(this.state.value)
}
else if (ans === "No") {
this.setState({ popupContent: "What went wrong?" });
// console.log(this.state.value)
}
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
styleOptions = {
bubbleBackground: 'rgba(0, 0, 255, .1)',
bubbleFromUserBackground: 'rgba(0, 255, 0, .1)',
botAvatarInitials: 'DIA',
userAvatarInitials: 'ME'
}
render() {
if (!this.state.view) {
return
<div />
} else {
const filteredSuggestions = this.state.suggestions.filter(
suggestion =>
suggestion.toLowerCase().indexOf(this.state.suggestionTypedText.toLowerCase())
> -1
);
// console.log(this.state.view);
return (
<div className="react-container webchat" >
<div onKeyDown={this.handleKeyDown.bind(this)}>
<div >
<ReactWebChat styleOptions={this.styleOptions} directLine={this.state.directLine} webSocket={true} userID='C94570' username='Thomas' store={this.state.storeValue} sendTypingIndicator={true} />
</div>
</div>
<div className="SuggestionParent" id="Suggestion1">
{this.state.suggestions.map((suggestion, index) => (
<div className={this.getSuggestionCss(index)} key={index} onClick={this.handleSuggestionClick} >
{suggestion
.toLowerCase()
.startsWith(this.state.suggestionTypedText) ? (
<div>
<b>{this.state.suggestionTypedText}</b>
{suggestion
.toLowerCase()
.replace(this.state.suggestionTypedText, "")}
</div>
) : (
<div dangerouslySetInnerHTML={this.getSuggestionHtml(suggestion)} />
)}
</div>
))}
</div>
<footer className="chat-footer" >
<div className="foot-footer">
Was I helpful ?
<span className="feedback" onClick={() => this.feedback("Yes")} >Yes</span><span>|</span><span className="feedback" onClick={() => this.feedback("No")}>No</span>
{
this.state.feedBack === "Yes" || this.state.feedBack === "No" ?
(
<div className="dialog" id="myform">
<div id="textfeedback">
<span id="closeFeedback" onClick={() => this.feedback("Close")}>X</span>
<p>{this.state.popupContent}</p>
<input type="text" id="feedbacktxtbox" required name="textfeedback" placeholder="Pleasure to hear from u!"
onChange={this.handleChange}
value={this.state.value} />
<button type="button" id="btnfeedback" onClick={() => this.feedback("Send")}>send</button>
</div>
</div>
) : null
}
</div>
</footer>
</div>
);
}
}
}
The chat input box is called the send box in Web Chat. Clearing the send box is just setting the send box with an empty string. This is done automatically when you click on the send button normally. You can see in the submit send box saga that submitting the send box means performing two actions: sending the message and setting the send box.
if (sendBoxValue) {
yield put(sendMessage(sendBoxValue.trim(), method, { channelData }));
yield put(setSendBox(''));
}
This means that if you use the SUBMIT_SEND_BOX action then the send box will be cleared automatically. Of course, if you want that to work with your autocomplete component then you'll need to set the send box with the autocompleted text before you submit it. Your other option is to just use the SET_SEND_BOX action with an empty string after you send the message.
I am still new to React and have been doing small projects lately to get better. I am currently working on a nutrition webpage that sets calorie goals and obtains foods from an API. This project consists of 2 components FoodItem and Main.
The Main component calculates calories and displays the search results from the API. Here lies the problem. When the search bar first receives a name, it displays nothing. However, it displays the intended search results after a backspace (deleting one letter from the word). This is seen in the screenshots.
Full word:
After deleting one letter:
Here is the function responsible for displaying the Search Results:
updateResult(name) {
console.log(name);
if (name == "") {
this.setState({
foodResult: []
})
return;
}
let result = [];
let url = 'https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=key&ingr=' + name;
fetch(url)
.then(
function(response) {
return response.json();
}
).then(function(jsonData) {
for (let i = 0; i < jsonData.hints.length; i++) {
foods.push({
name: jsonData.hints[i].food.label,
calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL)
})
}
})
console.log(foods);
foods = removeDuplicates(foods);
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
result.push(
<FoodItem name ={foods[i].name} calories ={foods[i].calories} updateFoods = {this.displayEatenFoods} isEaten = {false} checkItem = {this.checkItem}/>)
}
}
console.log(result);
this.setState({
foodResult: result
});
}
Full code:
import React from "react";
import ReactDOM from "react-dom";
//////////////////
let foods = [];
function removeDuplicates(arr) {
var unique = [];
for (let i = 0; i < arr.length; i++) {
let current = arr[i].name;
let add = true;
for (let i = 0; i < unique.length; i++) {
if (current == unique[i].name) add = false;
}
if (add) unique.push(arr[i]);
}
return unique;
}
///////////////////
class FoodItem extends React.Component {
constructor(props) {
super(props);
this.state = { addDone: false, disable: false };
this.addEaten = this.addEaten.bind(this);
}
addEaten() {
if (this.props.updateFoods(this.props.name))
this.setState({ addDone: false, disable: true });
else this.setState({ addDone: true });
}
render() {
if (this.props.isEaten) {
return (
<div>
{this.props.name}
Calories :{this.props.calories}
</div>
);
}
if (!this.state.addDone) {
return (
<div>
{this.props.name}
Calories :{this.props.calories}
<button primary onClick={this.addEaten} disabled={this.state.disable}>
Eat
</button>
</div>
);
}
return null;
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
goal: " ",
remaining: " ",
goalEntered: false,
foodSearch: "",
foodResult: [],
EatensFoods: [],
allowance: " ",
calories: ""
};
this.setGoal = this.setGoal.bind(this);
this.changeGoal = this.changeGoal.bind(this);
this.changeFoodSearch = this.changeFoodSearch.bind(this);
this.displayEatenFoods = this.displayEatenFoods.bind(this);
this.checkItem = this.checkItem.bind(this);
this.changeCalorieSearch = this.changeCalorieSearch.bind(this);
}
changeGoal(event) {
this.setState({ goal: event.target.value });
}
setGoal(event) {
this.setState({ goalEntered: true, remaining: this.state.goal });
event.preventDefault();
}
changeFoodSearch(event) {
this.setState({ foodSearch: event.target.value });
this.updateResult(event.target.value);
}
changeCalorieSearch(event) {
this.setState({ calories: event.target.value });
}
updateResult(name) {
console.log(name);
if (name == "") {
this.setState({ foodResult: [] });
return;
}
let result = [];
let url =
"https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=key&ingr=" +
name;
fetch(url)
.then(function(response) {
return response.json();
})
.then(function(jsonData) {
for (let i = 0; i < jsonData.hints.length; i++) {
foods.push({
name: jsonData.hints[i].food.label,
calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL)
});
}
});
console.log(foods);
foods = removeDuplicates(foods);
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
result.push(
<FoodItem
name={foods[i].name}
calories={foods[i].calories}
updateFoods={this.displayEatenFoods}
isEaten={false}
checkItem={this.checkItem}
/>
);
}
}
console.log(result);
this.setState({ foodResult: result });
}
displayEatenFoods(name) {
let tempEaten = [];
let disableFlag = false;
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase() == name.toUpperCase()) {
if (this.checkItem(foods[i].calories, foods[i].name)) {
tempEaten.push(
<FoodItem
name={foods[i].name}
calories={foods[i].calories}
updateFoods={this.displayEatenFoods}
isEaten={true}
checkItem={this.checkItem}
/>
);
} else {
disableFlag = true;
}
}
}
tempEaten = removeDuplicates(tempEaten);
tempEaten = this.state.EatensFoods.concat(tempEaten);
this.setState({ EatensFoods: tempEaten });
return disableFlag;
}
checkItem(cal, name) {
let newRemainder = this.state.remaining - cal;
if (newRemainder < 0) {
this.setState({ allowance: "You can't eat " + name });
return false;
}
this.setState({
remaining: newRemainder,
allowance: "You can eat " + name
});
return true;
}
render() {
if (!this.state.goalEntered) {
return (
<center>
<form onSubmit={this.setGoal}>
<label>
Please Enter your desired calories
<input
type="text"
value={this.state.goal}
onChange={this.changeGoal}
/>
</label>
<input type="submit" value="OK" />
</form>
</center>
);
}
return (
<div>
<center>
<h1>Maximum Calories:{this.state.goal}</h1>
<h2>Remaining Calories:{this.state.remaining}</h2>
<h3>{this.state.allowance}</h3>
<form>
<label>
Search foods
<input
type="text"
placeholder="Enter Name"
value={this.state.foodSearch}
ref={a => {
this.searchValue = a;
}}
onChange={this.changeFoodSearch}
/>
<input
type="text"
placeholder="Calories,Min+,Max,Min-Max"
value={this.state.calories}
onChange={this.changeCalorieSearch}
/>
</label>
</form>
{this.state.foodResult}
<h2>Eaten Foods:</h2>
{this.state.EatensFoods}
</center>
</div>
);
}
}
ReactDOM.render(<Main />, document.getElementById("root"));
First of all I can't examine all your code if there should be some best practices instead of your logic but your problem is your updateResult function doing an async job but you are not waiting it to finish. This is your main problem. Deleting one word or deleting anything does not trigger the problem. Just type "o" then wait a little bit, then write anything and see the same problem occurs. Make your updateResult function async and put an await before your fetch.
async updateResult(name) {
console.log(name);
if (name == "") {
this.setState({ foodResult: [] })
return;
}
let result = [];
let url = 'https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=somekeyhere&ingr=' + name;
await fetch(url)
.then(
function (response) {
return response.json();
}
).then(function (jsonData) {
for (let i = 0; i < jsonData.hints.length; i++) {
foods.push({ name: jsonData.hints[i].food.label, calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL) })
}
})
console.log(foods);
foods = removeDuplicates(foods);
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
result.push(
<FoodItem name={foods[i].name} calories={foods[i].calories} updateFoods={this.displayEatenFoods} isEaten={false} checkItem={this.checkItem} />)
}
}
console.log(result);
this.setState({ foodResult: result });
}
Instead of making your function an async one, you can continue the code with another .then method or methods. Just do not forget to return what you need for those .then methods from the previous ones.
I'm trying to render the array of text when I load the page:
'values': ['hello', 'world']
but it's giving me an error. In my formatoc.js , I have:
var props = {
'name' : 'form',
'timer' : 1500,
'callback' : function(id, validity, value) {console.log(id, validity, value);},
'values': ['hello', 'world'],
'newParent' : new FormatOC.Parent({
"one": {"__array__":"unique", "__type__":"string","__minimum__":1,"__maximum__":200,"__component__":"Input"}
})
}
React.render(React.createElement(ParentComponent, props), document.getElementById('react-component'));
This is what I have in my Parent.jsx file:
define(['react', 'r_foc_node'], function(React, NodeComponent) {
var ParentComponent = React.createClass({
getInitialState: function() {
if(!this.props.newParent) {
throw "ParentComponent requires newParent property";
}
return {
"values": {}
}
},
recursive : function(level) {
that = this;
console.log(level);
for (keys in level) {
console.log("This is a key: ", keys);
if ((typeof level[keys]) === "object") {
if (level[keys].constructor.name === "Parent") {
console.log('I am a parent');
level = level[keys].details();
//console.log(level[keys].get([key_list[0]]));
this.recursive(level);
//console.log(level[keys].keys());
} else {
console.log('I am a node');
//console.log(level[keys]);
};
};
}
},
childChange: function(name, valid, value) {
this.state.values[name] = value;
this.setState(this.state);
console.log(name,value);
// Call parent callback
this.props.callback(
this.props.name,
this.props.newParent.valid(this.state.values),
this.state.values
);
},
render: function() {
var that = this;
var level = this.props;
return (
<div id = "form">
{level.newParent.keys().map(function(k) {
return (
<div>
{(level.newParent.get(k).constructor.name === "Parent") ?
<ParentComponent
name={k}
key={k}
timer={that.props.timer}
callback={that.childChange}
values={that.props.values[k]}
newParent={that.props.newParent.get(k)}
/>
:
<NodeComponent
name={k}
key={that.props.name + '.' + k}
timer={that.props.timer}
callback={that.childChange}
values={that.props.values[k]}
newNode={that.props.newParent.get(k)}
/>
}
</div>
)
})
}
</div>
)
}
I'm not sure if the error is because of the way I'm trying to access the array values? But I can't seem to get the words hello and world to be rendered.
You are using: level.newParent.keys().map(function(k) {...
this should be: level.values.map(function(k) {...