Vue.js component within component, manipulate parent data - javascript

ive seen afew answers that sort of answer my question but not fully, so let me explain what I want to do.
We use a global #app div within the layout of our website, which is a Laravel project. So all pages will be the same main Vue instance, due to this i'm separating key functionality into components.
So, the first example is just a simple Tab component, this either separates any children into tabs, or accepts some data which the single child component then renders.
So below i'm injecting some data from another component, this ajax component literally just does an ajax call, and makes the data available within it's slot.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
#{{ row['example' }}
</div>
</div>
</tabs>
</div>
</ajax>
Now this is all well and good, to a point, but this falls down with the below code. This contains a component which will allow the used to drag and drop elements, it re-arranges them by literally moving the data around and letting Vue handle the DOM changes.
This will of course work fine within it's own data which you have injected in, but when you change the data within the component below this then clears this child component.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
<draggable :injected="row">
<div>
<div v-for="item">
#{{ item }}
</div>
</div>
</draggable>
</div>
</div>
</tabs>
</div>
</ajax>
I need to find a way to make any changes to this data apply to the parent data, rather than the data passed into the child components.
What is the best practice to do this!?
Edit 1
Basically, I need any child component's manipulate the data within the ajax component. The children within ajax could change, or there could be more, so I just need them all to do this without knowing what order or where they are.

It is hard to come up with specifics on this one, but I am going to try to put you in the right direction. There are three ways to share data between components.
1) Passing down data via props, emitting data up via custom events
The passing down of data via props is a one-way street between the parent and child components. Rerendering the parent component will also re-render the child and data will be reset to the original state. See VueJS: Change data within child component and update parent's data.
2) Using a global event-bus
Here you create an event bus and use this to emit the data to different components. All components can subscribe to updates from the event bus and update their local state accordingly. You initiate an event bus like this:
import Vue from 'vue';
export const EventBus = new Vue();
You send events like this:
import { EventBus } from './eventbus.js'
EventBus.$emit('myAwsomeEvent', payload)
And you subscribe to events like this:
import { EventBus } from './eventbus.js'
EventBus.$on('myAwsomeEvent', () => {
console.log('event received)
})
You still need to manage state in the components individually. This is a good start with an Event bus: https://alligator.io/vuejs/global-event-bus/
3) Using Vuex
Using Vuex extracts the component state into the Vuex store. Here you can store global state and mutate this state by committing mutations. You can even do this asynchonously by using actions. I think this is what you need, because your global state is external to any components you might use.
export const state = () => ({
resultOfAjaxCall: {}
})
export const mutations = {
updateAjax (state, payload) {
state.resultOfAjaxCall = payload
}
}
export const actions= {
callAjax ({commit}) {
const ajax = awaitAjax
commit('updateAjax', ajax)
}
}
Using vuex you keep your ajax results separated from your components structure. You can then populate your state with the ajax results and mutate the state from your individual components. This way, it doesn't matter whether you recall ajax, or destroy components since the state will always be there. I think this is what you need. More info on Vuex here: https://vuex.vuejs.org/

Related

React - Change state from external component

I know that I will ask a question that brake some rules about the core/basic way to use React... but maybe with this example, someone helps me to solve the problem that I facing.
This is not the full code of my project, but show exactly the idea of my problem:
https://codesandbox.io/s/change-state-from-external-component-zi79e
The thing is I need to change a state from a child component from the parent component, but I don't want to run a render method in my parent or handle the state in the parent component.
Exists a way to achieve this? In my project, I have a parent that creates multiple generic children and it will be more difficult to handle this request.
And specifically, I need to change the state of one child (MyFirstChild), after another child (SecondChild) read the keystroke and run an API to get some values from my backend; after that, I need to send the change to "MyFirstChild" to change his state.
The parent component has ~50 child components and I blocked the re-render method (With the method shouldComponentUpdate)
The expected answer is: "It's not possible, or, you broke the good use of React"...
But, maybe using forwardRef or ref, or something else that I not see can help me to work around this...
To change the state of a child component from the parent without having to run a render method (in the parent): one possible solution would be to use Redux. With Redux from the parent you can dispatch an Action (execute an Action that changes the state of Redux).
And in the child component, you receive that part of the state that you change (it could be a string, object, etc) as a prop. So when it is changed from the parent, your child component will render again without having to run a render method in the parent.
https://react-redux.js.org/introduction/basic-tutorial
You could also use Context for the same purpose.
However, I saw your code example, I am not sure the exact reason of why you don't want to make a render in the parent, but the easiest solution for what you need would be to send the function that you want to execute in the parent as a prop and also the title.
If you want to change things from the parent, without re-rendering again with Redux, it would be something like this:
In the parent
const changeTitle = (newTitle) => {
this.props.setTitle(newTitle);
}
return (
<div className="App">
<ChildComponent />
</div>
);
const mapDispatchToProps = dispatch => ({
setTitle: newTitle => dispatch(setTitleACTION(newTitle)),
});
In the child
return (
<div>
<h1>
{this.props.title}
</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
const mapStateToProps = ({ title }) => ({
title,
});
export default connect(mapStateToProps, null)(ChildComponent);
Again if you make this with Redux, you can get the "title" prop from the Redux store, and in the parent, you will change that variable (calling to a Redux action) without rendering the parent again.
If you want to fire the event from the child component, you can dispatch the action from the child component or you could call a function (that you receive from props from the parent) and call that function whenever you need.
Can't we use props here for passing data instead of trying to manipulate state from outside of component?
Based on #david paley explanation...
If the only way to achieve this is using Redux, I post my solution (It's the same example, but, implementing Redux)... hope that works for anyone else.
https://codesandbox.io/s/change-state-from-external-component-redux-rmzes?file=/src/App.js

Is there a way to call a child-component's function from another child?

I'm contributing to an open-sourced React/Redux application built using ES6 JavaScript. I'm fairly new to React/Redux so I'm having some trouble.
I have a parent class that's rendering two different React components. The first component contains some input fields regarding events (called NewShift). The second component is a calendar that renders these events (called Index).
Once a user fills out the input fields and presses a button in the first component, I want to be able to re-render the calendar in the second component. If the re-render function is in the calendar component, how do I call it from the input fields component (both children).
React re-renders components whenever component state is changed. Should you be using just React, this would mean passing changed values up to the parent component's state to force a re-render.
However, Redux makes this easier on you, as it's 'Store' functions as a global state. You can force a re-render by changing appropriate variables within the store.
Given your situation: the button should get this within it's onClick attribute:
onClick={() => dispatchNewCalendarInfo(payload)}.
dispatchNewCalenderInfo should also be imported by the component:
import { dispatchNewCalendarInfo } from './redux/path/to/post/actions.js'; and connected to it: export default connect(()=>({}), { dispatchNewCalendarInfo })(Component);. Note, you need to also import connect from 'react-redux' for this.
And, of course, dispatchNewCalendarInfo should be present in the actions.js path, and accepted by the store reducer. This dispatch should alter information that the calendar is connected to, which will force it to update and re-paint.
If you're not using Redux there's another path you can take. Instead of having the function that takes input be in NewShift, have new shift receive the function as a prop from the parent.
So in your NewShift component you would have something like onClick={this.props.submitCalanderInfo()}
The submitCalanderInfo function would be part of the parent component. You would probably want this new info to be saved into the state of the parent component, and then then use that state to update the props on the calendar or Index component. So Index might look something like this:
<Index shiftData={this.state.shiftData} />

How to transfer states among components?

I'm new to React JS, I want to create a ToDo list app. I have App.js which is the main page and so I have created MainBlock component for App.js page which holds left and right side of my layout and in right side property it loads Form component which has input and button and saves input values to an array in state and in left side it loads List component which has a property named allTasks which prints all tasks.
My problem is how can I transfer allTasks state from Form Component to App.js to pass it to List component property?
In App.js :
<MainBlock
left={
<List allTasks={['خرید از فروشگاه', 'تعویض دستگیره درب منزل']} />}
right={
<Form />
} />
You can accomplish this by storing the tasks as state in the App component, and have Form pass up the state through a callback prop. This concept is called "lifting state up". Here's a guide about it: https://reactjs.org/docs/lifting-state-up.html
<MainBlock
left={
<List allTasks={this.state.allTasks} />
}
right={
<Form onSubmit={allTasks => this.setState({ allTasks })} />
}
/>
Let tasks be a state of the MainBlock component. Pass this state down to the List component.
Let the Form component expose a callback property (maybe onTaskCreated) which is invoked when user complete creating a new task.
Let MainBlock intercept onTaskCreated callbacks and update it's internal state with the new task, which causes it to re-render and thus passing down the new task list to the List component.
You are trying to achieve two way binding here. Unfortunately, React is a library that doesn't support two way binding by default.
You can only pass in the values from Parent to Child to Sub-Child. However, the reverse is not true.
In this case, you are trying to load props from Form to App that is from Child to Parent which is not possible.
You should make use of state containers such as Redux that overcomes this limitation by making the state of one component available to the other component.
You can store all your tasks in the state of the MainBlock component and pass functions to update the state by your form. Also, pass the state (tasks) of MainBlock to your List component. Updating your tasks to MainBlock will automatically pass the tasks to List. Later you could use a state management library like MobX and Redux.
It can be confusing passing data between components. Libraries like redux were created to make this "easier", but it can be done with simple React. To pass state information to children, you pass it into their props. To send data the other direction, you pass (from parent to child) a handler function that is called from the child and can affect the state of the parent (or be passed to it's parent) as needed.
I wrote a sample pen to help people wrap their heads around this. Hopefully it helps.
Something like:
<Child1 handleAdd={this.handleAdd.bind(this)}
handleSubtract={this.handleSubtract.bind(this)}
/>
calls in the parent and and is accessed in the child as:
<Button onClick={this.onAdd.bind(this)}>
add
</Button>
<Button onClick={this.props.handleSubtract.bind(this)}>
subtract
</Button>
Then there is a function in the parent called handleAdd that can affect the parent state.

Passing a value to the parent component

How could a child component pass its value to the parent component? Here is my child component:
Javascript:
new Vue({
el: '#table-list',
data: {
tableList: ['Empty!'],
tableSelected: ""
},
methods: {
getTableList() {
axios
.get('/tables')
.then(tableList => {
this.tableList = tableList.data;
})
.catch(e => console.warn('Failed to fetch table list'));
},
selectTable(table) {
this.tableSelected = table;
}
},
mounted() {
this.getTableList();
}
});
HTML:
<div id="table-list">
<p v-for="table in tableList">
<i class="fa fa-table" aria-hidden="true"></i>
<span class="text-primary" v-on:click="selectTable(table)"> {{ table }} </span>
</p>
</div>
When on click, selectTable is called, I want to show the value in its parent component? i.e I need to pass tableSelected property to the parent component. How could I do this?
You should use vue components, specifically events mechanism for what you want to archive.
Props are for pass data from parent to a child components, and events to send messages from child component to parent.
We have learned that the parent can pass data down to the child using props, but how do we communicate back to the parent when something happens? This is where Vue’s custom event system comes in.
Please see this fiddle https://jsfiddle.net/AldoRomo88/sLo1zx5b/
I have changed your selectTable method to emit a custom event
selectTable: function(table) {
this.$emit('item-changed',table);
}
And in your parent component you just need to listen for that event
<div>
{{selectedItem}}
</div>
<table-list #item-changed="newValue => selectedItem = newValue " ></table-list>
Let me know if you need more clarification.
Here is the page that explains how children emit events to listening parents.
Here is the page on managing state.
Remember what you're aiming for, with VUE, is MVVM. You want all your state in a store, where each item of state is stored once, regardless of how many times it's referenced, and how many ways it can be updated.
Your tableSelected is an item of state. You can pass state changes up the chain if you need to, so long as they finish in a store, not in a component or a vue. But you can keep it simple: make tableSelected a property in your store, and declare it directly in the data element of components that need it. If you want to be rigorous, put a changeTableSelected() method on the store.
You need to start worrying about props and events if one component will have many instances, or if a component knows nothing about the page on which it will appear. Until that time, I would prefer using data and the store.

How can a parent component communicate with a child component in Vue.js?

This is what I have:
<div id='vnav-container'>
<input type="text" v-model="searchTerm" v-on:keyup="search" class="vnav-input">
<menu :items="menu"></menu>
</div>
The outer component contains a search-input and a menu component.
When the user performs a search on the outer component, I need to call a method on the menu component, or emit an event, or whatever, as long as I can communicate to the menu component saying it should filter itself based on the new criteria.
I've read somewhere that calling methods on child components is discouraged and that I should use events. I'm looking at the docs right now, but I can only see an example of a child talking to a parent, not the other way around.
How can I communicate to the menu component as the search criteria changes?
EDIT
According to some blog posts, there used to be a $broadcast method intended to talk to child components but the documentation about that just vanished. This used to be the URL: http://vuejs.org/api/#vm-broadcast
The convention is "props down, events up". Data flows from parents to child components via props, so you could add a prop to the menu, maybe:
<menu :items="menu" :searchTerm="searchTerm"></menu>
The filtering system (I'm guessing it's a computed?) would be based on searchTerm, and would update whenever it changed.
When a system of components becomes large, passing the data through many layers of components can be cumbersome, and some sort of central store is generally used.
Yes, $broadcast was deprecated in 2.x. See the Migration guide for some ideas on replacing the functionality (which includes event hubs or Vuex).
Or you can create the kind of simple store for that.
First off, let's create the new file called searchStore.js it would just VanillaJS Object
export default {
searchStore: {
searchTerm: ''
}
}
And then in files where you are using this store you have to import it
import Store from '../storedir/searchStore'
And then in your component, where you want to filter data, you should, create new data object
data() {
return {
shared: Store.searchStore
}
}
About methods - you could put method in your store, like this
doFilter(param) {
// Do some logic here
}
And then again in your component, you can call it like this
methods: {
search() {
Store.doFilter(param)
}
}
And you are right $broadcast and $dispatch are deprecated in VueJS 2.0

Categories

Resources