Where should state live in nested form component UI using Relay? - reactjs

I have a UI that is basically a form, but it has different components for editing specific pieces of data. I want all of the data on the page to be saved at the same time, when the user clicks the save button. All the the data is contained by in a type, and I have an update mutation for that type. So, while the different children components may be editing different pieces of the object that will be saved, the parent component is going to be responsible for performing a single update mutation that saves all the data.
The problem I am running into is that I am not sure where I should have the state for the child components. No matter what, it seems that the state for them will have to be in the parent so that it has it and can include it in the input object that it passes to the mutation when it performs it. However, these leaves me in one of two places, neither which seems ideal:
Only store the state of the values being edited by the child components in the parent.
Issue: Relay throws a warning, because I am passing a state object to a child RelayContainer, when it expects it to be a fragment fetched by Relay. You can see this warning and an example of this behavior in this playground. Here is the specific warning I am referring to that you see when editing a field in a child component.
Warning: RelayContainer: Expected prop details supplied to [object Object] to be data fetched by Relay. This is likely an error unless you are purposely passing in mock data that conforms to the shape of this component's fragment.
Let the child components maintain their own state, but then also communicate the values to the parent so that it has them for the purposes of handling the save action and performing the mutation.
Issue: Duplication of state. Even though the state that is in the parent is solely being kept for the purposes of performing the mutation, it seems quite wrong to have it there and will cause unnecessary renders for the parent. You can see an example of this variation in this other playground.
Hopefully there is an obvious preferred way to handle this situation, but from my searching around and playing with Relay I have not been able to determine it.

First things first. Very well-written question. Thank you :-)
The common pattern used in React and Relay world is that we have a parent component which acts a thin view controller, fetches data using Relay and passes its state's different parts as props to different child component as necessary. The child components, which receive data as props, usually are dumb and just display passed data.
In your case, the ChildComponent itself edits data. It does receive initial details data from its parent component HelloApp. However, when we edit any of the details fields in UI, the values fetched by Relay and those in UI do not match. While creating a Relay container for child component, if we include fragments and fields, we declare that those data will be fetched by Relay. Now, if we want to modify child components' data(props) ourselves, we just skip that fragment in the Relay container. The props passed by the parent component will be used only for initialState of the child component.
The necessary changes are in HelloApp container. The fields of details are moved from child to parent.
HelloApp = Relay.createContainer(HelloApp, {
fragments: {
person: () => Relay.QL`
fragment on Person {
name,
details {
hairColor,
nickName
}
}
`,
}
});
BUT I still feel uncomfortable with the above solution. Because it clearly shows that, the parent has to know the fields of details, which are actually used in child components.

Related

Can a component have its own state?

Whenever I use Frameworks such as React , Vue or anything else. I see that every value (input value, select value ...) is always being emitted through events to parent. I don't really get why this is good. It takes more ressources / time to pass it to the parent component. Why doesn't the component handle its data by itself?? Why should every single value inside a child component get passed to the state of the parent to make the handling of the data ?
For example, I generally see the loading of the APIs in the parent and then the values of the API get passed down through props or Redux/Vuex. Why can't we get the API values inside the child directly when it's being mounted instead of having to pass it down on many levels or through the local store
It's perfectly valid to have a state inside reusable component, especially if it holds users input. You can still provide it with callbacks in props, which respond to onChange, onBlur or other events based on your needs, or you can communicate with your reusable component via refs, which is fairly flexible way to get user input.
The important thing is, that the reusable component should be as much independent on it's environment as possible. If you don't provide it with a state, the component (in case of your Pickers) will be dependent on props passed down from its parent expressing it's current user input on every rerender and on callbacks updating its current input somewhere higher in component hierarchy on every change, which is not what you want in every case, and it already forces you to make relatively large adjustments in you parent component, which misses the point of reusability.

How to access data in child component of query called in parent component in rtk query?

How can I access the data of the query called in the parent component, inside a child component without prop drilling.
For example, if in parent.js I call the getPost query
const {data} = useGetPostQuery();
then how can I access this data further down the hierarchy?
There are two options I see:
The "right" answer way - is to use a selector from API slice to access the cached data which is already in rtk-q's Redux store. Let's say we have posts API slice definition. So we'll be able to access some particular cached post directly with a selector, provided by rtk-q:
.
const selectPost = posts.endpoints.getPost.select(postId);
selectPost - is a memorized selector, gererater by rtk-q for a particular postId. To fetch the data - that you'll need to pass the app state in, and it will return the expected post from the cache, that your parent component fetched before.
Inside the component it can be used with useSelector like:
const { data: post } = useSelector(posts.endpoints.getPost.select(postId));
Some more details here:
https://redux.js.org/tutorials/essentials/part-8-rtk-query-advanced#selecting-users-data
And a pretty straightforward way - is just to use the same query code in your child :).
Even it sounds weird\smelly - there is nothing wrong with it, due to you making your component standalone\decoupled, and it will get this data anyway - even will be used outside the parent component. But if it will be used in a case you described, as a child - it will just fetch data from the cache, with no real API call, so with no harm to performance. And it looks even more clear than selectors.
I prefer exactly this approach, and I feel it keeps the code more consistent - I'm just declaring that "I need that data" in a component, and don't care if it was fetched before or not, keeping all the components decoupled from knowing about any others component, making it cohesive.
It's somehow touched here:
https://redux.js.org/tutorials/essentials/part-8-rtk-query-advanced#cache-data-subscription-lifetimes
Offtopic bonus: In a case when you don't need the post data on a parent's component and want to fetch it only to improve responsiveness - you may use the usePrefetch hook. For example - on the "Edit" button hover, it will make an API call, put the data in the cache, so post will be available for children instantaneous.
More here:
https://redux-toolkit.js.org/rtk-query/usage/prefetching
If you want data to be available to all the rendered components then use Context. But, frequent changes in the context will cause permanence issues as the entire tree will get rerendered if the component is not designed properly.
Another option is to use Redux in the app to manage the states.
https://redux.js.org/introduction/core-concepts
Local storage is another option but will not recommend this.
The easiest way to send data from parent to child through props but if you want to do it without props you should use other stratigies like: localStorage,cockies or redux.

React - How to connect / disconnect children components to data from parent based on click?

Parent component provides data to child component (that displays it) (have multiple children). The child component has 2 buttons connect and disconnect. When connected to should receive and show the data, when disconnected clicked, it should disconnect from data.
How I would do it: child calls method in parent for connect/disconnect,then i would have a flag in parent state and based on that either render child with or without data, then inside child i render props or null if no props provided.
That would mean if I have 10 child components, I would need 10 flags which doesnt seem good, so I 2nd idea:
having the flag insight the child and method for triggering connect/disconnect, render based on if props were provided
My question is, is there a good practice react way of connecting children to parents data flow/stopping data flow without always passing data to children and just rendering/not rendering it based on if child 'wans' to be conencted or not?
Thanks!
Did you try using Redux or Context API?
The two are very similar and it should get what you want.
They basically use 1 store(think of them multiple states are connected to 1 store) and you get data from child components.
After getting your data you can check if user is logged in or not and put your buttons according to this data.
If I was not clear enough feel free to ask any question.
Also official docs for Redux and Context API are:
https://redux.js.org/introduction/getting-started
https://tr.reactjs.org/docs/context.html

state vs props for scenario with separate view and data model

I'm building an application where I would like to provide separate views for same data.
In my current implementation, data is obtained by web service call and persisted in state of App component in App.js. App component hosts (renders) another component called StackEditor, which acts as a view for this.state.components in App component.
UI elements rendered by StackEditor can be moved around, and to synchronize state of App I do it as below:
<StackEditor
components={this.state.components}
onLocationChanged={this.handleLocationChanged} />
In handleLocationChanged I update the state:
handleLocationChanged(e, data) {
this.setState(prevState => {
// event data copied to state here
return {components: prevState.components};
});
}
As state is now updated, this forces StackEditor to be rendered again, as its property components is bound to state as components={this.state.components} (see in the code snippet above).
This all works, but now I started questioning if I'm doing it right.
Q1: Should I be using state instead of props?
It seems that location of component is mutated in principle, although from StackEditor point of view, it is immutable (I can decide that change is invalid and not to update the state in event listener).
Q2: Is it possible to share part of the state between 2 components in React?
If I somehow convert StackEditor from getting components from state instead of props, will I get notification on state changed by child component (StackEditor) in my parent component (App)?
Q3: Also, are props more convenient to use than state in general?
When I created another component following HOC guidelines (https://reactjs.org/docs/higher-order-components.html) I discovered that props are easily forwarded to "wrapped" component, but not state. If I provide a function to call back via property (as I did above), "wrapped" component can easily call it, without even noticing that it's "wrapped". I don't see how I can easily notify "wrapped" component about state change in "wrapper", without writing some extra code.
If you imagine your application to be a tree of components in a well designed app it's usually like this:
the leafs are stateless components . They decide how data is rendered.
the nodes are stateful components. They decide which components and data to render.
Q1: Should I be using state instead of props?
It depends on which category of components you have (node or leaf).
Q2: Is it possible to share part of the state between 2 components in
React?
If you feel that your app has a lot of state that mutates and needs to be used by several components spread over your tree you usually start to introduce an external state management library (e.g. redux). Components can subscribe to your store and become stateless as your store now handles the state.
Q3: Also, are props more convenient to use than state in general?
They solve different problems so you can't really say that. A stateless component is usually easier to understand but has no capabilities to control anything.
Also read Identify where your state should live and When to use redux.
All that is only a rule of thumb. A lot of the time you will have components that have both state and props because they control parts of your app but delegate other parts to their children.
This all works, but now I started questioning if I'm doing it right.
As far as I can see from the code you provided this looks pretty much as it has to.

Fully clone React component, including input values

I'm just wondering if there's anyway of completely cloning an already rendered React component. I've read about cloneElement but when I render that clone I get an error:
Uncaught error: Invariant Violation: Element type is invalid
I'm also wondering if this will clone the element's child elements, and their subsequent input values. For example, if some text is in one of the inputs, and then the component is cloned, will these values be preserved? Or is my only option to store those values before clone? This will make it very tightly coupled I feel.
As a general rule, we should always try to keep our components as stateless as possible, meaning that the data you input in the form should be stored somewhere outside of the component (a Store, maybe? ... I'm thinking about http://alt.js.org/docs/stores/).
With this approach, you'll have your component listening to that store. You can have as many copies of the component as you'd like, but the single source of truth would be your store.
Another scenario, if the same component should show data from different stores, then do not use stores, and use props. Have the parent component be the one listening to its store, and pass the necessary data to the child component (the one that you want to have clones in several places in your app).
I hope that helps,

Resources