I have built a react application which shows items from JSON file, it consists of 4 main components, Items.js which created the item itself alongside its props (title and image), Itemlist.js which shows items from items.js and other functions.
MenuCat maps and shows each category from the json, and whenever the category is clicked it returns a number(props) to Itemlist.js adding it to the mapping function of items as ( children[{props here}].children[]....) so that it maps all items of each clicked category by using its json index.
Whenever an item is clicked, it shows more JSON properties such as promos inside a pop up called drawer, the content of this pop up is from Modifiers.js.
I need a function similar to MenuCat.js's category clicks but with items instead, where it shows the exact children of the item (e.g Burger 1 shows desert promo 1 on click, and burger 2 shows desert promo 2). The way its set up now, it only shows desert promo 1 for all the items.
Sandbox (current view on app.js): https://codesandbox.io/embed/8j5mmrjk2?fontsize=14&moduleview=1
One problem I noticed with your components was that you were passing the key attribute. This is anti-pattern as React does not pass down ref or key props.
If you need to access the same value within the child component, you should pass it as a different prop (ex: <ListItemWrapper key={result.id} id={result.id} />).
Just pass it down as a different attribute such as myKey.
I've done some other tweaks which you can check in the forked sandbox. Most notably, I added a state value to track the child index that was clicked.
this.state = {
itemSelected: 0
}
...
{items.children[selected].children[itemSelected].children.map(
(item, index) => (
<Modifiers
key={index}
title={item.name}
myKey={index}
childp={item.children[index].name}
>
{" "}
</Modifiers>
)
)}
Related
I have a list of Todos:
item1
item2
item3
Every item has a delete button with the mutation to delete a single item.
And every item has spinner with loading from deleteMutation
The problem:
Once I press the delete button on a single item, the spinner turns on for every item on the list.
I know the problem here, but don't know how to solve it properly.
We can add state for every item of list, and logic for turning on spinner.
But I'm looking for the proper solution here.
Maybe is there a way to know, with what params the mutation was called, and I'll add simple code
todos.map(({ id, title }) => (
<div>
<p>{title}</p>
<button onClick={() => deleteMutation({ variables: { id } })}>
{loading && id === deletedId ? <Spinner color="white" size="sm" /> : Delete}
</button>
</div>
))
Problem is using/sharing the same mutation instance, it's state - loading. Next problem - deleting two items in the same time probably impossible.
Possible solutions:
if each item has its own mutation - make it a component (cheap abstraction in react), define mutation inside. A bit redundant, of course - but suitable for more complex items or individual actions.
... or you have to use some flag (state) f.e. itemBeingDeleted before calling mutation (clear in 'onCompleted') to block (disable) other items delete button and display loading in one of them.
Playing aroud with vue with a todo list.
At this stage, I am able to show the list within and with an edit button, the text will be an input so I can edit the todo items. But for example if I have 10 items, and I clicked edit for item 2 and item 4 and item 5, then all these 3 items will change into input.
What I am thinking is if I click on item 4, item 2 will change back to text, so only one item will show input if edit clicked.
I have my template code as below
<td class="col-8">
<!-- if editable is false, show the checkbox and todo item -->
<div class="custom-control custom-checkbox" v-if="!editable">
{{ todo.item }}
</div>
<!-- if editable is true, turn item into an input so user can enter new value -->
<div v-else>
<input ref="editItem" type="text" class="form-control" :value="todo.item" #input="onInput">
</div>
</td>
<td class="col-2">
<button
class="btn btn-sm"
:class="editable ? 'btn-outline-success' : 'btn-outline-info'"
#click.prevent="editable ? onSave() : onEdit()">{{ editable ? 'save' : 'edit' }}
</button>
</td>
As I open up the vue inspect in chrome, I can see all items have their different data value.
How can I change others siblings' value?
Thanks in advance for any sugguestins and advices.
I wouldn't recommend you to change the value of another sibling, since you cannot be sure of the side-effects it can cause, obviously when you look at a todo list, it would feel like there are no side-effects but in general practice, a node should change elements that are below it in the parentage, neither above nor the siblings.
If you want to change elements above, its always drive via events. You let the parent know that something needs to be changed rather than changing it yourself.
This is what you can do here:
Rather using editable as a data property, use it as a prop to the TodoListItem component.
props: {
editable: {
type: Boolean,
default: false
},
}
When the Save/Edit button is clicked in the child component, rather than changing the value there itself, use events like so.
/** You should keep a deepCopy of the props if you are changing the state
of that object and then send the updated local data prop to the parent
as well for updation rather than changing the prop object.
NEVER CHANGE THE PROP OBJECT DIRECTLY. -> this.todoItem =
this.deepCopy(this.todo);
Do this in before mount hook of the child component.
**/
this.$emit('todo-item-action', action, todoItem);
Once this event is emitted, in the parent TodoList component, catch this event and change the state of the items like so:
<todo-list-item
v-for="todo in todos"
:key="todo['id']"
:todo="todo"
:editable="todo['editable']"
#todo-item-action="handleItemAction"
>
or if you don't want to mutate the todo object. here you can make sure editableItem is null if none is selected or only the active item ID is referred so that the prop will change and automatically other list item will become un-editable.
<todo-list-item
v-for="todo in todos"
:key="todo['id']"
:todo="todo"
:editable="editableItem === todo['id']"
#todo-item-action="handleItemAction"
>
The more you drive a component via events and props, the more re-usable it becomes. Always have an orchestrator component which manipulates these stateless components by handling events propogated by them and sending state via props.
This is the approach I have always followed and it always allow me to refactor cleanly and quickly.
Regards.
Codesandbox link.
Okay, so the high level functionality of what I want this app to do:
Upon load, you're presented with the characters on the left, and on the right an intro screen.
When a user clicks on the first character (Cloud) for example, then the intro screen is replaced with Cloud's stats. If a user clicks on the second, third, etc character, same thing. The intro screen on the right is replaced with that character's specific stats.
That's basically it. I'm finding it really tough to do, for some reason.
For the /components/ folder breakdown (I'll go over the important ones):
/components/content/ - this is where the characters information lives, housed in the State. Each character is uniquely identified by an 'id'.
/components/sidebar/ - this is the sidebar component
/components/card-overview/ - this is a single component, repeated a few times, that houses the 'overview' of each character (image, character name), that you see in the sidebar itself
/components/card-detailed/ - this is where it grabs the array of objects from /components/content/, filters through and maps the character you're currently seeing to the DOM. Problem is, on line 13 of this component, you'll see I've hard-coded in the id #. Obviously, I want this to change to whatever character the user clicks, it then loads THAT character's info to the DOM. I just don't know how to do that.
In short, what I'm trying to do:
When a character on the left is clicked, hide whatever is on the right and replace it with that character's information.
I made your intention working here: https://codesandbox.io/s/l2lqm76z0q
As Chris pointed out, the way to go is using react state.
Therefore, I introduced a state with the default selected item 4 and created a handleClick function that should fire once you click on one item in your sidebar. Both of them got created in App.js:
state = {
selected: 4
};
handleClick = id => {
this.setState({ selected: id });
};
Now, I transferred the handleClick function where it is needed, in this case in Sidebar.js:
<Sidebar onClick={this.handleClick} />
And the currently selected item needs to be transferred to your content (again, where it is needed).
<Content selected={this.state.selected} />
In the sidebar, I send the handleClick function again to the it's child, CardOverview, with the related id(*):
<CardOverview
image={cloudImage}
name="Cloud Strife"
onClick={() => this.props.onClick(1)}
/>
And in CardOverview, finally, we use that function we passed down the road:
<section className="card-overview" onClick={this.props.onClick}>
...
</section>
(Whether it is working or not, we see in the console - when clicking on on item, it should show the state with the currently selected item id.)
Now, in content.js, we use the selected value and pass it down to CardDetailed where we use it in the following way:
// Grab the 'characters' object from App.js, and assign it to 'this.props'
const { characters, selected } = this.props;
// Filter the chracters and return only whose 'id' belongs to that of the selected one
const filteredCharacters = characters
.filter(character => character.id === selected)
.map(character => (
<div className="characters" key={character.id}>
<p>Name: {character.Name}</p>
<p>Job: {character.Job}</p>
<p>Age: {character.Age}</p>
<p>Weapon: {character.Weapon}</p>
<p>Height: {character.Height}</p>
<p>Birthdate: {character.Birthdate}</p>
<p>Birthplace: {character.Birthplace}</p>
<p>Bloodtype: {character.Bloodtype}</p>
<p>Description: {character.Description}</p>
</div>
));
...
That`s it.
(*) Be aware, I did not improve your code, I just made it working. I see several issues that you should tackle. E.g. it is not needed to list all the characters in your sidebar manually. I'd suggest to create a file that contains your list of characters and use it in sidebar and in content. This way, you will reduce your code and maintain your characters list (and all belonging information like id) in one file.
This question may sound a little complicated. But i am stuck here, so i am throwing it here.
I am rendering a child component multiple times in parent with different properties and an array.
Parent contains an array of headings, i am passing it to child components and mapping select options with that array. If an heading is selected from any of the child components, that heading should display as disabled in other child components.
Child:
{
this.props.xarray.map((heading, index) => {
if (heading.headingis.toLowerCase().indexOf(this.state.field) != -1) {
this.setcheckstate(); //ignore this function
this.props.actionis(index, 'disabled'); //Using this function (passed from parent) to disable selected option
return <option value={heading.headingis} key={index} selected disabled={heading.disabled}>{heading.headingis}</option>
}else{
return <option value={heading.headingis} key={index} disabled={heading.disabled}>{heading.headingis}</option>
}
})
}
Parent's actionis function:
handlerHeading(index, disabled) {
xarray[index]['disabled'] = disabled;
}
It's working somehow BUT problem is
If first component is rendered, it will disable that value
Then 2nd component is rendered, it will disable that value
along with previous one.
and so on...
But in first component, only one value is disabled, in 2nd component 2 values are disabled and in 3rd component 3 values are disabled. And so on...
But i want if one value is disabled in component 6, it should get disabled in all previous components.
Have a look at following images for example.
Component #1 Rendered:
Component #4 Rendered:
I have resolved this, by changing xarray to state of parent.
This:
state = {xarray: xarray}
And:
handlerHeading(index, disabled) {
xarray[index]['disabled'] = disabled;
this.setState({ xarray: xarray });
}
For some reason you cannot change Props as you desire. See This Question
I'm currently trying to highlight the top element in a react native ListView.
Is it possible to for ListView identify which row component is located at the top of the view on scroll?
If not, how would I go about selecting the lowest integer rowID of the visible rows?
renderRow(rowData, sectionID, rowID) {
return (
<MyRow
{...rowData}
key={rowData.detailID}
onDetailPress={() => this.onDetailPress(rowData, rowID)}
/>
);
}
You can use onChangeVisibleRows which gives you a list of the visible rows and the rows that have changed their visibility. Something like this should do:
<MyRow
{...rowData}
key={rowData.detailID}
onDetailPress={() => this.onDetailPress(rowData, rowID)}
onChangeVisibleRows={(visible, changed) => this.highlightRow(visible[0])}
/>
You would probably need to update the data source with the item you want to be highlighted containing a state that will indicate it.