React / Apollo shared query state - javascript

I have a very simple app, with a list view and a detail view. The list view is wrapped with a graphql query that maps to a data prop containing an array of items. Selecting one item instantiates a detail view, where the id of the item is used to compose another graphql query for that one item.
const ListView = graphql(ALL_ITEMS, ...)(ListViewComponent);
<ListView data={ [....] }>
<Detail>
<Next /> <Prev /> {/* <-- need info from ListView.data */}
</Detail>
{ this.props.data.map(....) }
</ListView>
The trouble I'm having is adding previous/next navigation to that detail view. The detail component is unaware of what is in that data prop containing all the items in the ListView component. In a typical redux application, I would store the items as global application state, and injecting them into a component would be trivial, but Apollo doesn't seem to work that way. Its state tree is not very consumable (e.g. using connect()).
I see a few possible solutions for this, and none of them seem great:
Wrap the prev/next navigation component with a graphql query that looks for currentOffset + 1 and currentOffset -1. This is definitely the most "apollo" solution, but the problem there is that currentOffset is unknown to that component because it doesn't have the data prop. Further, I would have to ensure that any other variables (e.g. filters, sort) on the list view query got passed through, and those are in the obscure apollo state tree, as well.
Listen to the APOLLO_QUERY_RESULT action in my reducer and maintain a second copy of the query results with all the information my other components need.
Use context to share the data prop to children.
Explicitly pass the data prop to children.
Seems like a common scenario. What's the right approach?

Passing data down as a prop would probably be the easiest solution.
One other option you may want to consider is simply wrapping whatever component you want to have access to the same query data with another graphql HOC. If you utilize the same query you use for ListView's HOC (ALL_ITEMS), you can use the data prop in your child just like you do in ListView. The neat thing about this approach is -- as long as you use the default fetchPolicy -- your child component will not trigger a second query against your server; it will simply utilize what's already in the cache.

Related

How to get Vue app container div attribute

I have a page which is being generated by my app in java (SSR) depending on some data (e.g. a publisher for some entity). I would like to create some sort of a reusable Vue component that would call an API method and request some data about the entity that is currently opened. Also in some cases there could be more than one such component on one page.
The only thing I cannot really figure out being a most-of-the-time backend developer - is how to tell a component which entity I'm trying to get. The only solution that comes to my mind is to generate the parent <div class="my-vue-component"><div> with an additional attribute, e.g. <div class="my-vue-component" publisher-id="123"><div>.
But I cannot find if there is a way to access that attribute from inside the Vue instance. (Please note that I don't have a fixed id for this div as there can be many such components on the same page referring to different data).
Any kind of advice is appreciated.
As stated in the previous answer, you will need to use props. Although since you will pass down data to multiple components and the data can change, there should be a way to respond to those changes.
For that, you will have to bind the prop with a reactive variable in your page/parent component.
So your SSR code should look like
<my-vue-component :publisher-id="openId"></blog-post>
And inside your page/parent component will reside the openId, which you can change as needed, and your component will re-render if prop passed to it changes.
export default {
data(){
return {
openId:1
}
}
}
It seems like you are looking for components-props.
You can define a prop like
Vue.component('blog-post', {
// camelCase in JavaScript
props: ['postTitle'],
Your SSR code should then be generating:
<!-- kebab-case in HTML -->
<blog-post post-title="hello!"></blog-post>
Inside the component methods you can access the passed in value using this.postTitle

Does passing data in props duplicate that data?

I'm building a dashboard in React, and currently I'm looking at building a small button component that will allow a user to download data from a chart as a CSV file. Something like this:
// Dashboard
render(){
const data=makeLargeDataset(); // make an array with 1m rows
return (
<DownloadCSV data={data} />
);
}
// Download Button
onClick(){
if (this.props.data){
convertAndDownload(this.props.data);
}
}
Does this duplicate the 1M rows when passing to the button's prop? If I had to pass a prop through several layers, will it keep duplicating? Is there a smarter way to pass props -- or work with large variables and arrays, in general -- so it isn't as wasteful or inefficient?
Nope, It does not.
Any props passed from a higher order component to a child component always references the same props. It's called Single Source of Truth. You can alter one data point in the HO Component and React as the name tells reacts to only the change and nothing else.
A simple way for holding such big data sets would be to use a store like Redux to keep the data safe but the smarter way would be to create an API or a micro service based on your needs and call that rather than loading a million rows on the browser.
You are not passing the data copy, you are passing the reference.
You could think about paginating your data in some way in order to handle lower sized arrays.

How to avoid duplicating logic in react data table

I'm looking for help with structuring an entity data table component in react.
I'm using react-virtualized and am trying to determine how to encapsulate the boilerplate functionality used for server-side sorting/filtering/paging/row deletion/etc.
I started out with a simple, self-contained component which only required an API path, and handled everything by itself with local state.
Needing to be able to update/delete/add/refresh records from outside the table component, I moved the table state/logic into a container component that manages the table, along with some buttons and modal windows.
However, now that all the handlers for sorting/filtering/paging are in this container, it seems they would all have to be duplicated in order to be reused for other tables with slightly different requirements, but the exact same sorting/filtering/paging.
How can I contain the boilerplate table logic within the table component, while also being able to update/delete/add/refresh records from outside the component?
To visualize this, here is a contrived example where all the logic and state is encapsulated in the TableWithFilter component:
<SomeEntityList>
<TableWithFilter apiPath={this.props.apiPath}>
<FilterBar />
<Table />
<TableWithFilter>
</SomeEntityList>
the filter method and state could live in TableWithFilter:
TableWithFilter._handleFilter() {
// get filter, inject filter into api path, get records, update state
}
Now, lets say we need to add a modal so that we can edit the values in a row:
<SomeEntityList>
<EditRowModal />
<TableWithFilter>
<FilterBar />
<Table />
<TableWithFilter>
</SomeEntityList>
The state and filter method then have to be moved up to the SomeEntityList component so that the modal can change the records in the table:
SomeEntityList._handleFilter() {
//get filter, inject filter into api path, get records, update state
}
SomeEntityList._editRow() {
// make update call, update state
}
Assuming I'll have dozens of various SomeEntityListA/SomeEntityListB/SomeEntityListC with various difference (some have edit record modals, some have buttons to add new records, etc) how would I avoid duplicating the filtering logic in dozens of places?
The way I see it you have two options.
If the logic for the reusable filters are functional, you could probably move them out of the components entirely and put them in a utils directory or something.
Use higher-order components.
Edit
Why don't you just pass the filter as a param?
Ok, after some time, I decided to circle back to this to help anyone else out.
I've since moved to incorporation redux in the project (for many reasons).
On this topic specifically, I now have a CreateSearchableTable function that acts a factory for creating namespaced SearchableTable reducers. This allows the reuse of all the fetching/infinite scrolling/paging/filtering/sorting logic. I'm currently working on finding the best way to remove duplicated action creators.

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.

Structuring my React/Redux app for efficiency

Im building an audio workstation app that will display a table of tracks containing clips. Right now I have a table reducer which returns a table object. The table object contains track objects and the track objects contain clip objects. I have a TableContainer which subscribes to the table store. My issue is I believe my app will be inefficient because it will re render the page every time a clip is added or manipulated. In reality only the particular track in which the clip resides would need to be re rendered right? How can I structure my app so not every little change re renders the entire app?
In the mapStateToProps of any component, don't select parent objects as a whole to send to the component. If possible select specific properties all the way to the leaf values. If your TableContainer's render() itself doesn't use the tracks array them make sure only the sibling properties that you do use get passed.
So instead of:
function mapStateToProps(state, props) {
return {
table: state.tables[props.tableId];
}
}
Do:
function mapStateToProps(state, props) {
let table = state.tables[props.tableId];
return {
name: table.name,
type: table.type
};
This allows React Redux to be more discerning when it comes to determining whether your component needs to be rerendered. It will see that even though the table had changed due to a clip change, neither the name, nor the type has changed.
However, since your Tablecomponent likely renders the Track components as well, you're likely not going to be able to avoid render calls. If any property anywhere up the tree gets altered, the tracks array also gets altered.
The solution in this case is to have the tracksarray not contain the entire track object but instead only a list of track IDs. You can then store the tracks alongside the tables and a change in one won't affect the other. Note that this only works if you do not go and fetch and pass the track object in the mapStateToProps of the Table component. You should make theTrack component in such a way that it accepts its ID instead of the actual object as a prop. This way the Table component is not dependent on the contents of the tracks at all.
The power of react is to re-render only what needs to be (by using the virtual DOM to make the comparison and the shouldComponentUpdate function).
I wouldn't look too much into it before it becomes a performance problem.
If it does, I would store the tracks in a separate directory and don't pass it to the app (main) component. In your Clip component's mapStateToProps function (if you use react-redux), fetch the track from the state as you get it's name from the props. This way if the track changes a lot (because of async fetching of slices for example), only the component will update.

Categories

Resources