Edit 1
Shortened the code to
removeContentNew(i) {
var contents = this.state.templateContent;
contents.splice(i, 1);
this.setState({ templateContent: contents });
}
It might have something to do with this:
componentDidMount() {
this.setState({ templateContent: this.props.template.content });
}
Still removing the wrong one on screen. When I log the state, it does give me the right array though. Maybe something wrong with the map?
--
I'm trying to bug fix this piece of code but I can't seem to find the error.
removeContent(i) {
var $formgroup = false;
const regex = new RegExp('^content.', 'i'),
contents = _.reduce(_.keys(this.refs), (memo, k) => {
if (regex.test(k) && k !== 'content.' + i) {
var $formgroup = $(this.refs[k]);
if (this.props.customer.getSetting('wysiwyg_enabled', true)) {
var html = CKEDITOR.instances['html_' + i].getData();
} else {
var html = $formgroup.find('[name="html"]').val();
}
memo.push({
subject: $formgroup.find('[name="subject"]').val(),
html: html,
text: $formgroup.find('[name="text"]').val(),
language: $formgroup.find('[name="language"]').val()
});
}
return memo;
}, []);
this.setState({ templateContent: contents });
}
i is the ID of the item I want to remove from the array templateContents. Every time I press the remove button of one of the items it always seems to delete the last one and ignores the other ones.
I've been doing some testing with the k variable and that one might be the cause of the problems, but I am not sure at all.
I'm really quite new to the RegExp way of doing things.
Any ideas how I can fix this?
Update your state in the constructor instead of inside componentDidMount method.
constructor(pops) {
super(props);
this.state = {
templateContent: this.props.template.content
}
}
Also calls to setState are async so you don't have any security when the changes is being executed
The issue was in the mapping of the array. I'll leave this here because it helped me solve my issue.
Bad (Usually)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
This is arguably the most common mistake seen when iterating over an
array in React. This approach will work fine unless an element is
added or removed from the rows array. If you are iterating through
something that is static, then this is perfectly valid (e.g an array
of links in your navigation menu). Take a look at this detailed
explanation on the official documentation.
Related
i have this code https://stackblitz.com/edit/react-wc2ons?file=src%2FSection.js
I have sections, and i can add items to those sections. How can i delete some item? I tried
const removeItem = (i) => {
setSections((section) => {
const itemTarget = section.items[i];
const filtered = section.items.filter((item) => item !== itemTarget);
return {
...section,
items: filtered,
};
});
};
But for some reason it doesn't work
The removeItem callback prop you pass into the Section component is the way to go and you should get rid of passing setSections down to it as well.
removeItem={(i) => removeItem(index, i)}
Child components shouldn't do parent's work so you had it right at first, I'm going to help you implement that since I can already see the removeItem handler being there in the App component.
removeItem has already all the info you need, I'm going to rename the arguments so it's more clear.
const removeItem = (sectionIndex, index) => {
const newSections = sections.slice();
const newItems = newSections[sectionIndex].items.slice();
newItems.splice(index, 1);
newSections[sectionIndex].items = newItems;
setSections(newSections);
};
Then get rid of removeItem implementation in the Section component and destructure it from the props.
You are using setSections, but you return a single section instead of an array of sections. You probably need something like this:
// using the `section` variable from the upper scope
const removeItem = (i) => {
setSections((sections) => {
const itemTarget = section.items[i];
const filtered = section.items.filter((item) => item !== itemTarget);
const newSections = [...sections];
newSections[section.id] = {
...section,
items: filtered,
};
return newSections;
});
};
A few tips (you don't have to follow them): TypeScript can prevent such mistakes and give useful error messages. Immer.js can make writing such code simpler.
Your problem is that section is an array. So you are currently accessing the undefined property items on it. You would have to change your function to something like this
const removeItem = (i) => {
setSections((section) /* aqui vc tinha chamado de prev*/ => {
const itemTarget = section[i].items[j];
const filtered = section[i].items.filter((item) => item !== itemTarget);
return [...section, {
...section[i],
items: filtered,
}]
});
};
where i is the section in question and j is the item you want to delete.
here is a crude solution to your problem (i noticed other bugs in the code but this solves your issue with removing items at least), but i would separate the sections and items into separate components that in turn has its own states.
There you can add/remove items withing its parent section much more easily.
Now we have to work around this by looking for which section the code wants to remove the current item in.
https://stackblitz.com/edit/react-xxbvp1?file=src%2FSection.js
I have a snippet of code here where i have an array that may or may not have keys in it. When the user presses on a 'friend' they add them to a list (array) where they might start a chat with them (add 3 friends to the array, then start a chatroom). The users selected might be toggled on or off.
Current Behavior:
i can add/remove one person, but i cant add multiple people to the array at the same time. When i add one person, select another - the first person is 'active', when i remove the first person, the second person automatically becomes active
Expected Behavior:
I would like to be able to add multiple people to the array and then remove any of the selected items from the array
onFriendChatPress = (key) => {
console.log(key) // this is my key 'JFOFK7483JFNRW'
let friendsChat = this.state.friendsChat // this is an empty array initially []
if (friendsChat.length === 0) {
friendsChat.push(key)
} else {
// there are friends/keys in the array loop through all possible items in the array to determine if the key matches any of the keys
for (let i = 0; i < this.state.selGame.friends.length; i++) {
// if the key matches, 'toggle' them out of the array
if (friendsChat[i] === key) {
friendsChat = friendsChat.filter(function (a) { return a !== key })
}
else {
return friendsChat.indexOf(key) === -1 ? friendsChat.push(key) :
}
}
}
}
Help please!
From your code, I was quite confused regarding the difference between this.state.selGame.friends and this.state.friendsChat. Maybe I missed something in your explication. However, I felt that your code seemed a bit too overcomplicated for something relatively simple. Here's my take on that task:
class Game {
state = {
friendsChat: [] as string[],
};
onFriendToggle(key: string) {
const gameRoomMembers = this.state.friendsChat;
if (gameRoomMembers.includes(key)) {
this.state.friendsChat = gameRoomMembers.filter(
(member) => member !== key
);
} else {
this.state.friendsChat = [...gameRoomMembers, key];
}
}
}
I used typescript because it makes things easier to see, but your JS code should probably give you a nice type inference as well. I went for readability over performance, but you can easily optimize the script above once you understand the process.
You should be able to go from what I sent you and tweak it to be according to what you need
this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(item){
item.expand = true;
this.setState({})
}
this.state.myArray.map((item) =>{
return <div onClick={()=>this.clickItem(item)}>{item.name}</div>
})
In React, i have a simple array of objects,
when i click on one of theses object, i want to change their prop and update the state, what is the proper way of doing this.
i feel like there could be a better way
You need to copy your state, update the copied state and the set the state.
this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(key){
let items = this.state.myArray;
items[key].expand = true;
this.setState({items})
}
this.state.myArray.map((key, item) =>{
return <div onClick={()=>this.clickItem(key)}>{item.name}</div>
})
Okay, a couple of things.
You're mutating the state directly which is going to fail silently and you're also missing the key prop on your <div.
This is easily resolved though by using the data you have available to you. I don't know whether each name is unique but you can use that as your key. This helps React decide which DOM elements to actually update when state changes.
To update your item in state, you need a way to find it within the state originally, so if name is unique, you can use Array.prototype.find to update it.
clickItem(item) {
const targetIndex = this.state.items.find(stateItem => stateItem.name === item.name)
if (targetIndex === -1)
// Handle not finding the element
const target = this.state.items[targetIndex]
target.expand = !target.expand // Toggle instead of setting so double clicking works as expected.
this.setState({
items: this.state.items.splice(targetIndex, 1, target) // This replaces 1 item in the target array with the new one.
})
}
This will update state and re-render your app. The code is untested but it should work.
I am creating a questionnaire type form using ReactJs and Ant Design. It is a follow up question of How to create a questionnaire type form using Ant Design?
Now I am succeeded in adding new questions and their respective answers but not in removing them. Let's suppose I have added three questions and when I am trying to remove any one of them, its always removing the last one. The related code for removing is as follows:
remove = k => {
console.log(k);
const { form } = this.props;
// can use data-binding to get
const keys = form.getFieldValue("keys");
// We need at least one passenger
if (keys.length === 1) {
return;
}
keys.splice(k, 1);
// can use data-binding to set
form.setFieldsValue({
keys: keys
});
console.log(keys);
};
The complete code can be found as a demo on codesandbox.io.
I have done something similar in the past. Got rid of the boilerplate of antd's remove and replaced with this. Every time I add a row I push that row (object) to formRows array then removing like this:
remove = key => {
const newRows = this.state.formRows.filter(r => r.key !== key)
this.setState(
prev => ({
formRows: newRows
})
)
}
I've been adopting ReactJS + Redux in my projects for a couple of years. I often end up in asynchronous situations where I need my component to wait for the state to be updated to render. Normally the simple logic !this.props.isFetching ? <Component /> : "Loading..." is enough.
However there are cases where I need to check for the state of an array that is embedded in the state object. In these cases, most of my components end up looking like this:
renderPostAuthor = () => {
if (!!this.props.postDetails.author) {
return this.props.postDetails.author[0].name;
} else {
return (
<div>
<StyledTitle variant="subheading" gutterBottom color="primary">
Loading...
</StyledTitle>
</div>
);
}
};
Is this use of the !! notation a good pattern / practice in ReactJS?
UPDATE: Thanks for the responses, and they are all valid. Perhaps, to clarify my question further, note that this.props.postDetails is a state itself that contains a number of objects and arrays. Therefore the problem is that if I omit the !! and this.props.postDetails isn't instantiated yet, and hence contains no arrays such as author[], I get the undefined error.
This has much more to do with just JavaScript in general than with React.
No, that use of !! isn't particularly useful. This:
if (!!this.props.postDetails.author) {
is the same as this:
if (this.props.postDetails.author) {
Neither of them means that author contains an array with at least one entry, which your next line of code is relying on. To do that, add .length or, with your particular example, probably [0] instead (in case author had an entry, but that entry was a falsy value):
if (this.props.postDetails.author[0]) {
If author may be null or undefined, we need to do two checks:
if (this.props.postDetails.author && this.props.postDetails.author[0]) {
Since we're going to use the result, it may be best to save the result to a variable or constant:
const firstAuthor = this.props.postDetails.author && this.props.postDetails.author[0];
if (firstAuthor) {
return firstAuthor.name;
}
Example of the current code throwing an error:
console.log("Running");
const author = [];
if (!!author) {
console.log(author[0].name);
} else {
console.log("No Author");
}
Example of checking [0] when we know author won't be null/falsy:
console.log("Running");
const author = [];
if (author[0]) {
console.log(author[0].name);
} else {
console.log("No Author");
}
Example of the double-check when author may be null/falsy:
console.log("Running");
const author = null;
if (author && author[0]) {
console.log(author[0].name);
} else {
console.log("No Author");
}
Example of saving and using the result:
function example(author) {
const firstAuthor = author && author[0];
if (firstAuthor) {
return firstAuthor.name;
} else {
return "Loading...";
}
}
console.log(example(null)); // Loading...
console.log(example([])); // Loading...
console.log(example([{name:"Harlan Ellison"}])); // "Harlan Ellison" (RIP)
There are times in react when using the !! is particularly helpful, but this is not the instance as stated above. The most common case I've found is when evaluating whether you're going to render array items or not. Often people will use the length of the array to decide whether to work with it or not since 0 length is a falsey boolean:
render () {
return this.props.array.length && <MyList items={this.props.array} />
}
Unfortunately this will return the 0 which will be rendered on the page. Since false will not render on the page a good alternative would be to use the double bang so that false is returned.
render () {
return !!this.props.array.length && <MyList items={this.props.array} />
}