React + Flux. Optimised proceedure for passing store state to component - javascript

I'm having a little conceptual difficulty with a certain aspect of the React/Flux architecture, I know, crazy, right. It has to do with how a Container should pass the Store to a Component, and how the Component should read from the Store, which as far as I see are interdependent.
As an example - I have a simple chart which updates the x and y range depending on changes to a form.
I have a simple Store, updated from Dispatch events, "XRANGE_CHANGE" and "YRANGE_CHANGE", of an Action.
import Immutable from "immutable";
import { ReduceStore } from "flux/utils";
import Dispatcher from "../Dispatch";
class ChartStore extends ReduceStore {
constructor() {
super(Dispatcher);
}
getInitialState() {
return Immutable.OrderedMap({
xRange: [],
yRange: []
});
}
reduce(state, action) {
switch(action.type) {
case "XRANGE_CHANGE":
return state.set("xRange", action.item);
case "YRANGE_CHANGE":
return state.set("yRange", action.item);
default:
console.error("Action type not found");
return state;
}
}
}
export default new ChartStore();
And a Container, which will pass this Store to the Chart component;
import React from "react";
import { Container } from "flux/utils";
import ChartAction from "./ChartAction";
import ChartStore from "./ChartStore";
import Chart from "./Component";
class ExampleContainer extends React.Component {
static calculateState() {
const chartStore = ChartStore.getState(),
xRange = chartStore.get("xRange"),
yRange = chartStore.get("yRange")
return {
xRange: xRange,
yRange: yRange,
xChange: ChartAction.xChange,
yChange: ChartAction.yChange
};
}
static getStores() {
return [ChartStore];
}
render() {
const state = this.state;
return <Chart
// actions
xChange={state.xChange}
yChange={state.yChange}
// !!!!! here's where my confusion lies !!!!!
//store={state.chartStore}
// ammended
xRange={state.xRange}
yRange={state.yRange}
/>
</div>;
}
}
export default Container.create(FinanceContainer);
The commented exclamation marks above indicate where I lose track of the "accepted" React way of doing things.I'm not quite sure of the best way to pass the Store to the Chart component, which will dictate how I read the Store within the component. I have a few options as far as I see, all work but could be completely wrong.
As above, I pass the entire store to the Component and in the Components' render function read store.get("xRange") or store.get("yRange")
In the Container I define xRange={chartStore.get("xRange")} etc.
In Either the Container or the Component I perform store.toJSON()/toObject() and read directly from the result.
I could be completely way off the mark with any of these scenarios. Or any of these ways could be fine.
Any advice would be appreciated. As I continue on I'd like to know I'm carrying out a sensible procedure. Thanks in advance.

As you've noticed, this isn't something with a definitive answer. But I think a good way of determining the "right" methodology is by looking at libraries that are written "for flux" and how they handle these problems. In particular, I would take a look at Redux (a flux implementation) and Reselect (an extension of Redux that addresses this issue further).
The pattern that these libraries use is essentially that your calculateState method ought to transform the flux state into the relevant state information for that container. It should grab relevant information (e.g. state.get('xRange')) as well as possibly performing tranformations on the data held in state if helpful (e.g. range: {x: state.get('xRange'), y: state.get('yRange')}).
As with most things in the flux pattern, the idea here is to provide a definitive "source of truth". You want every sub-component to interpret the flux state in the same way, and you want to have a single method to modify, should the data need to be computed differently. By doing an ETL of the flux state into the container, you achieve that. Should there be some future change to which piece of the flux state is needed for this section of your app, you would merely need to modify this calculateState method, as opposed to all lower usages of that data.

Related

How to pass the set[State] function to a non-descendent trigger component

Here is the diagram. ChildComponentB has a state - stateX. In ChildComponentA, once the event occurs, it will change the stateX in ChildComponentB.
If the ChildComponentA is the child component of ChildComponentB, then it's easy, just pass the setStateX as a prop to ChildComponentA. But in this case, it's not.
The real scenario is the following. I have a canvas component, there are some static Rectangles already there, once there are mouse move over the line of the Rectangles, I'd like to add the indicator lines to another child component of the canvas component.
Hence, the rectComponent is not the descendent of the distanceIndicatorsComponent. So I can't pass the setLines to RectComponent.
What's your approach to do that?
If I use useContext approach, will it work?
Thank you, #KonradLinkowski to provide your solution. Here is his code. However, useContext is still lifing the state up to ParentComponent.
import React, { useContext, createContext, useState } from "react";
const Context = createContext();
function ChildComponentA(props) {
const { setStateX } = useContext(Context);
return (
<div>
componentA button:{" "}
<button onClick={() => setStateX((i) => i + 1)}>add</button>
</div>
);
}
function ChildComponentB(props) {
const { stateX } = useContext(Context);
return <div> stateX is {stateX} </div>;
}
export default function ParentComponent(props) {
const [stateX, setStateX] = useState(0);
return (
<>
<Context.Provider value={{ stateX, setStateX }}>
<ChildComponentA> </ChildComponentA>
<ChildComponentB> </ChildComponentB>
</Context.Provider>
</>
);
}
Regarding the reusbility of the ComponentB i.e. distanceIndicatorsComponent in this scenario, it includes the JSX and the states plus the interface in which there are logic to change the states. The are all parts which should be reusable in the furture.
From OOP perspective, the lines (state) belongs to DistanceIndicatorsComponent, and the how to change the lines (Add Line in this case) should be also reusable logic which belongs to distanceIndicatorsComponent.
However, from React perspective, to lift the setLines (this is the interface triggered under some event) is not "good enough" from OOP perspective. To lift the state - lines and state management function - setLines up to CanvasComponent is a "not good enough" in terms of the encapsulation. Put a wrap component on top of ComponentB is the same thing, the setLines still can't be passed to FrameComponent unless FrameComponent is a child-component of the wrap component.
It's very common to see there is a very heavy component holding all the state and the events at the top. It makes me feel that's a bad smell of the code. The reusability of the component should be based on a set of components, in this set of components, there is one uncontrolled component at the top, and underneath of this uncontrolled component are controlled components. This set of components is a external reusability unit.
Here, in this diagram, there should be more than one reusable unit rather than one. If lift the state up to CanvasComponent, it makes all the components underneath are un-reusable. In some extents, you still can re-use the JSX of this component, but I'd say, in terms of reusablity, it should invovle as many reusable logic as possible.
I might be wrong, please correct me. And thank you for sharing your valuable comments.
Requirements
First let us sum up the requirements.
Rect Component and Distance Indicators have not much to do with each other. Making them aware of each other or creating a dependency between them would be not desired in a good OOP design.
The interaction between both is very specific. Establishing a mechanism or a data structure just for this special sort of interaction would add an overhead to all components that don't need this sort of interaction.
General Concepts
So you must use a mechanism that is so generic that it does not add any sort of coupling. You need to establish something between these two components, which only these two components know and which for all the rest of your program is nonsense. What mechanisms serve for such a purpose?
Function pointers
Lambda functions
Events
Function pointers and lambda functions are complicated constructs. Not everybody prefers to use them. Now you see why events are so popular. They address a common requirement of connecting two components without revealing any of the details of them to anybody.
I personally recommend you to use lambda functions in this situation. Because this is one strength of JavaScript. Search in google for callback or asynchronous lambda function. This often adds the least overhead to existing code. Because a lambda functions has an important property:
With lambda functions you can do things very locally. Doing things locally is an important design principle. You don't need to define extra methods or functions or classes. You can just create them wherever you are, return them, pass them freely around to where you actually need them and store them there. You can store them even without knowing what is behind them.
I think, this is your answer. The only thing you need is a mechanism to pass lambda functions and to store your lambda functions. But this is on a very generic level and therefore adds no coupling.
With events you are on similar path. The event mechanism is already there. But therefore you already have a good answer.
Example with pure JavaScript
When applying this to JavaScript we can imagine that function pointers could be compared to function expressions in JavaScript. And lambda functions can be compared to arrow functions in JavaScript. (Note: Arrow functions also provide "closures", which is required in this case, see How do JavaScript closures work?).
A simple example illustrates this:
class DistanceIndicator {
constructor(height, width) {
this.height = height;
this.width = width;
}
resize(height){
this.height = height;
}
incorrect_resizer(height){
return this.resize;
}
resizer(){
return (height) => this.resize(height);
}
resizer_with_less_overhead(){
return (height) => this.height = height;
}
}
p = new DistanceIndicator();
p.resize(19);
// If you want to use this, you have to store p. You may see
// this as not so nice, because, you are not interested in what
// actually p is. And you don't want to expose the information
// that it has a method resize. You want to have the freedom
// of changing such details without the need of changing all
// the code where something happens with Rectangles.
console.log(p.height);
resizer = p.incorrect_resizer()
//resizer(18);
// In this example, resizer is a function pointer. It would be
// nice to store it and be able to call it whenever we want to
// inform Rectangle about something interesting. But it does not
// work because the resize method cannot be isolated from the
// class. The "this" is not there.
console.log(p.height);
resizer = p.resizer();
resizer(17);
// That works. Lambda functions do the job. They are able to
// include the "this" object.
console.log(p.height);
resizer = p.resizer_with_less_overhead();
resizer(16);
console.log(p.height);
// As you have now a resizer, you can store it wherever you want.
// You can call it without knowing what is behind it.
The idea in the example is that you can store the resizers wherever you want without knowing what they are. You shouldn't name them resizer, but give them a generic name like size_notification.
Example for React
The React concept for contexts is a typical candidate for data exchange between components. But the principle of React is a pure unidirectional data flow (top-down). This is also true for the context, which means, we cannot use a context for what we want.
React does not provide support for the implementation of the proposed idea. React is only responsible for the pure construction of the HTML page and a comfortable and performant rendering. It is not responsible for the "business" logic of our HTML page. This is done in full JavaScript. That makes sense because you want be able to develop complex web applications. Therefore you need all your favourite programming concepts. A real application does not follow the design principle of React. React is only a presentation layer. Most people like OOP progamming.
So when implementing something with React we must keep in mind that React is just a library for JavaScript. The full power of JavaScript is always available and should be used for our web application.
After realizing this, the problem becomes simple. See this code:
import React from 'react';
let sizeNotificator = (newValue) => {console.log(newValue)};
function Rect(props) {
return <button onClick={() => sizeNotificator("12")}>resize to 12</button>;
}
class DistanceIndicator extends React.Component {
state = {
size: "0",
};
setSize(newValue) {
this.setState({
size : newValue
});
};
componentDidMount(){
sizeNotificator = ((newValue) => {this.setSize(newValue);})
}
render() {
return <p>Current size: { this.state.size}</p>;
}
}
class App extends React.Component {
render() {
return(<div>
<DistanceIndicator/>
<Rect/>
</div>);
}
}
export default App;
With this code the requirement is fulfilled that none of the DistanceIndicator implementation details are revealed to the outside of DistanceIndicator.
Obviously this example code only works if there is not more than one DistanceIndicator. To solve this is a different topic with probably not only one good solution.
If keeping the shared state in the ParentComponent is the problem, you can extract the Context.Provider to a separate component and pass components as it's children, those children can access the context value via useContext hook.
function ParentContextProvider({ children }) {
const [stateX, setStateX] = useState(0);
return (
<Context.Provider value={{ stateX, setStateX }}>
{children}
</Context.Provider>
);
}
export default function ParentComponent(props) {
return (
<ParentContextProvider>
<ChildComponentA />
<ChildComponentB />
</ParentContextProvider>
);
}
Now you can add any new state/setState to the ParentContextProvider and can pass it to it's children
Have you looked at Redux stores? You could have a variable like "showLine" or "originX"/"originY", then have one child dispatch changes, and the other child useSelector for the values?
Do you know if Redux works for your use case?
I prefer to use a simple events pattern for this type of scenario. Eg using a component such as js-event-bus.
CHILD COMPONENT A
props.eventBus.emit('MouseOverRectangle', null, new MyEvent(23));
CHILD COMPONENT B
useEffect(() => {
startup();
return () => cleanup();
}, []);
function startup() {
props.eventBus.on('MouseOverRectangle', handleEvent);
}
function cleanup() {
props.eventBus.detach('MouseOverRectangle', handleEvent);
}
function handleEvent(e: MyEvent) {
// Update state of component B here
}
RESULTS
This tends to result in quite clean encapsulation and also simple code. Eg any React conponent can communicate with any other, without needing to reveal internal details.

Why Is React Context.Provider Necessary (Or useful)?

The reason React has contexts is to allow for multiple sibling components to share a piece of state-data. It is the go-to method for allowing two unrelated components to read/write in shared variables. The reason it is necessary is that React has no way to easily source a data value to multiple screens without actually passing that data between screens. Instead, it allows each screen access to the data when it needs it.
So... The implementation requires that a component be created, called a Context.Provider component, and then you have to wrap the components who need access to the shared data inside the Context.Provider. But why? Why on earth is that a requirement? Contexts are designed sharing data between components who aren't hierarchally related, and were required to put the components within a heirarchy to do so?
It would be 100 times more straight forward and just as effective to simply drop the requirement of using a Context.Provider, simple have the useContext function give access to a set variable by default:
// In ctx.js
import React from 'react';
export default CTX = React.createContext({val: "value"});
// In compA.js
import CTX from './ctx.js';
import {useContext} from 'react';
function A(props) {
var [context, setContext] = useContext(CTX);
console.log(context); //Logs {val: 'value'};
setContext({val: "newValue"});
}
Then later on, assuming component B renders after A:
import CTX from './ctx.js';
import {useContext} from 'react';
function B(props) {
var [context, setContext] = useContext(CTX);
console.log(context); //Logs {val: 'newValue'};
}
The above usage, if it actually worked, solves the task of "sharing data between unrelated components", and is much much simpler than requiring an entire new component be defined in the context file. This solution is better because:
1. No required restructuring of the application. You don't need to wrap components in a provider.
2. Any Components can just ask for any shared state easily, and they can set the shared state easily.
3. Easier to understand with much less code involved (One line of code for import and one line to initiate the context).
4. Doesn't sacrifice anything. This method allows for easy sharing of state between components, which is the entire reason of contexts in the first place.
Am I crazy? Is there a legitamate reason that we'd absolutely need to wrap our components up in a special component to share data?.. Why can't the shared state just exist independently? Its like they chose a bad solution... Why make every developer wrap there components in another component before using shared state, why not just let the developer use the damned shared state when they need to use it instead of jumping through a hoop? Someone please educate me.
Edit: One answer said that with my described method we wouldn't be able to access multiple contexts with a single component. That is false. It is actually easier with my described method:
// In context.js
export const CTX = React.createContext({val: "val"});
export const CTX2 = React.createContext({val2: "val2"});
// In app.js
function App(props) {
const [state, setState] = useContext(CTX);
const [state2, setState2] = userContext(CTX2);
return (<></>);
}
Easy. No need for Context.Provider. This is multiple contexts being used in one component, requiring just two calls to useContext versus wrapping your entire application in two nested contexts, which is what is what you have to do with current Context.Provider method...
Mate, answer is simple. React component only re-renders when it's props or state changes. Without Context.Provider component react will never understand when to re-render child components, thus you will have stale, render-blocked components.
The purpose for having a Context Provider wrap around children is to keep track of state and props, read on how state and props between parents and children affect each other. If there was no way for the Context Provider to keep track of its children, how would the components that use the Context be able to update(Changing parent state affects children, so there may be rerendering).
It's also important to understand React's philosophy and it's focus on components, it is a component-based library after all.
Important thing to remember:
Parent state change will affect children, so if state changes in parent, children components will be reevaluated and depending on how your components, state, and data are optimized (memo, callback, etc.) a rerender may occur, thus updating those children components as well.
Contexts Are Made To Handle All Use Cases
I've since spent more time using Contexts in my applications and have come to realize that Context.Provider is quite useful in a variety of situations. My initial complaint has merit in that often times when using Context we are simply wanting a variant of state that can be shared between components. In this common use case, Context.Provider does indeed requires us to write a bit of unnecessary boilerplate code and requires us to wrap elements in the provider so that they have access to the context.
However any time our shared state becomes a little more complicated having a dedicated Context.Provider component can make our lives a lot easier. Here is a use case to consider
Shared Data From External Sources (Post, Get)
Contexts may allow us to store any code related to the initialization of the shared state within the context itself, resulting in more easily readable and maintainable code. For example, lets say we have some user text posts on our server that are displayed by multiple components within our application, and we would also like for our users to be able to add new posts. All of this can be handled quite neatly within the Context.Provider:
import React, {useContext, useEffect, useState} from 'react';
export const PostsContext = React.createContext([]);
export default PostsContextProvider({children}) {
const [posts, setPosts] = useState([]);
function fetchPosts() {
// Here we will fetch the posts from our API, and then set the state
// stored within the Context.Provider equal to the fetched posts.
fetch('https://www.fakewebsite.com/api/posts/get', {
method: 'GET',
headers: {'Content-Type': 'application/json'}
}).then((response)=>{
// Convert response to json
return response.json();
}).then((json)=>{
// json here is the posts we fetched from the server, so we set the state
// equal to this value. This will update the state within all components
// that are using the context.
setPosts(json.posts);
})
}
useEffect(function(){
// This function will run a single time when the application is started
fetchPosts();
},[])
function addNewPost(post) {
// This is the function that will be used by the components.
// First, we will send the new post to the server so that it can store it.
fetch('https://www.fakewebsite.com/api/posts/post', {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({post: post})
}).then((response)=>{
if(response.ok) {
// The server has updated its database with our new post.
// Now we just need to fetch the posts from the server again to get the updated data.
fetchPosts();
}
})
}
return (
<PostsContext.Provider
value={[posts, addNewPost]}
>
{children}
<PostsContext.Provider />
)
}
Notice that the value prop we are passing does not actually pass the state setter function directly. Instead, we pass the addNewPost function. So, when a component calls useContext(PostsContext) they will get the addNewPost function. This is extremely useful, it will allow our components to easily add a single post to the shared state, while also handling the server update! Very cool. With the solution I originally proposed, this would be impossible, because we would only ever get a simple state setting function from our useContext call.
Now, we must wrap our application in the provider to make it available to all components:
// App.js
import React from 'react';
import PostsContextProvider from './posts_context';
import MyComponent from './my_component';
import MyOtherComponent from './my_other_component';
export default function App() {
return (
<PostsContextProvider>
<MyComponent/>
<MyOtherComponent/>
</PostsContextProvider>
)
}
At this point, MyComponent and MyOtherComponent now have access to the context using the useContext hook. It is now extremely simple for the components to access the posts data and also update it with a new post.
import React, {useContext} from 'react';
import {PostContext} from './posts_context';
export default function MyComponent() {
const [posts, addPost] = useContext(PostsContext); // 'posts' will always be up to date with the latest data thanks to the context.
...
}
import React, {useContext} from 'react';
import {PostContext} from './posts_context';
export default function MyOtherComponent() {
const [posts, addPost] = useContext(PostsContext);
...
function handleAddPost(title, text) {
// Now when this component wants to add a new post,
// we just use the `addPost` function from the context.
addPost({title, text});
}
...
}
The beauty of this is that all the code related to the fetching and posting of data can be neatly contained within the provider, separated from the UI code. Each component has easy access to the posts data, and when either component adds a new post the other component will be updated with the new data.
Final Thoughts
This is just scratching the surface of the usefulness of Context.Provider. It's easy to imagine using a Context.Provider to handle persistent data storage using a method very similar to the above, replacing the fetch calls with function that store/fetch persistent data. Or even, some combination of persistent data and fetched data.
Upon revisiting my original question, it actually made me laugh. I was sort of right, there should perhaps be a way to handle simple shared state between components that does not require wrapping components in a provider and does not require any provider code at all. However, providers are just so dang useful in any kind of state management within an application that it is actually probably a good thing to force people to use them for simple shared state, because then they will have to learn about this wonderful tool.

React Context Folder hiarchy / architecture?

I'm currently looking at implementing Context into one of our apps over Redux, but, I can't seem to find any information on what would be the best structure for large scale apps?
Redux has a defined way to create reducers, actions, etc. With Context, all I've found are the generic "create a provider, put state and methods all on the same file, and then use a consumer".
TL;DR Is there a way to build a hiarchy that is beneficial for long term, and large scale applications with React Context?
Edit: I guess this is incorrect to think of them having a similar structured relationship. Unfortunately, I'm not able to use Redux because of AEM's limitations. Context does work however, so I wanted to hopefully be able to build some structure with that.
First of all, I don't think there is necessarily a right or wrong answer to this question, but I will just give you my two cents.
I am currently refactoring a web application which serves several millions of sessions per month and am testing a redux and context version on internal stage servers.
Important notices:
I am using a mono-store approach
It's not an app which constantly has global store updates
To the folder structure. I like to keep my store in the root of the project. For a react app based on react-create-react-app that would be the /src and it basically consists of the following files:
index.js // everything gets "bundled" here
initialState.js // provides the store with intial state e.g. from server, cache etc.
methods/*.js // contains split methods based on the part of the app that they are used in (if it can be split into separate parts)
Ergo my index.js is as simple as:
import React from 'react';
import storeMethods from './methods';
import initialState from './initialState';
// to start of experimenting with context
// i would keep all read and write key value
// pairs right here and split as the codebase
// grows and you realize you need more space
export const store = {
...initialState,
...storeMethods
}
export const StoreContext = React.createContext(store)
storeMethods is a bundled export from all methods in the methods/ folder. Basically it's just another object of containing keys which values are functions like so:
export const methods = {
showNavBar: function() {
this.setState({ navBarOpen: true })
}
}
initialState is as much as the representation of key value pairs that are required to render the base content of the app and or never change. Basically some global settings. Initialstate coming from the server, is being added to the store in the constructor of my App, right before I bind the lexical scope.
The store get's thrown into the state of the relevant outermost React Component and is used as the app state, where I bind the store's scope to the React Components lexical scope.
Then I have a higher order component withContextConsumer which is used to wrap any React component which needs access to the state. The HOC distributes the subscribed keys down as props to the wrapped component and can be consumed as read only or write.
No matter how you end up using Context, don't forget, that any Consumer will have it's render method automatically called if the Context Store is being updated. To avoid that on a simple oldProps !== newProps level, you can use PureComponents. For more complex diffs you can use the lifecyclemethod shouldComponentUpdate
edit
Basic App Structure
App.js:
import React, { PureComponent } from 'react'
import { StoreContext, store } from './store'
import { bindScopeToFunction } from './helpers'
class App extends PureComponent {
constructor(props) {
super(props)
const { initialState = {} } = props
const boundStore = bindScopeToFunction(store, this)
this.state = {...boundStore, ...initialState}
}
render () {
return(
<StoreContext.Provider value={this.state}>
// in here you render all your app
// routing, childcomponents etc
// in any component where you need access
// to the global store
// wrap it in <StoreContext.Consumer> it has
// the whole store as render prop
</StoreContext.Provider>
)
}
}
Working basic example can be found here https://codesandbox.io/s/pm85w4y6xm

ReactJS - Lifting state up vs keeping a local state

At my company we're migrating the front-end of a web application to ReactJS.
We are working with create-react-app (updated to v16), without Redux.
Now I'm stuck on a page which structure can be simplified by the following image:
The data displayed by the three components (SearchableList, SelectableList and Map) is retrieved with the same backend request in the componentDidMount() method of MainContainer. The result of this request is then stored in the state of MainContainer and has a structure more or less like this:
state.allData = {
left: {
data: [ ... ]
},
right: {
data: [ ... ],
pins: [ ... ]
}
}
LeftContainer receives as prop state.allData.left from MainContainer and passes props.left.data to SearchableList, once again as prop.
RightContainer receives as prop state.allData.right from MainContainer and passes props.right.data to SelectableList and props.right.pins to Map.
SelectableList displays a checkbox to allow actions on its items. Whenever an action occur on an item of SelectableList component it may have side effects on Map pins.
I've decided to store in the state of RightContainer a list that keeps all the ids of items displayed by SelectableList; this list is passed as props to both SelectableList and Map. Then I pass to SelectableList a callback, that whenever a selection is made updates the list of ids inside RightContainer; new props arrive in both SelectableList and Map, and so render() is called in both components.
It works fine and helps to keep everything that may happen to SelectableList and Map inside RightContainer, but I'm asking if this is correct for the lifting-state-up and single-source-of-truth concepts.
As feasible alternative I thought of adding a _selected property to each item in state.right.data in MainContainer and pass the select callback three levels down to SelectableList, handling all the possible actions in MainContainer. But as soon as a selection event occurs this will eventually force the loading of LeftContainer and RightContainer, introducing the need of implementing logics like shouldComponentUpdate() to avoid useless render() especially in LeftContainer.
Which is / could be the best solution to optimise this page from an architectural and performance point of view?
Below you have an extract of my components to help you understand the situation.
MainContainer.js
class MainContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
allData: {}
};
}
componentDidMount() {
fetch( ... )
.then((res) => {
this.setState({
allData: res
});
});
}
render() {
return (
<div className="main-container">
<LeftContainer left={state.allData.left} />
<RightContainer right={state.allData.right} />
</div>
);
}
}
export default MainContainer;
RightContainer.js
class RightContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedItems: [ ... ]
};
}
onDataSelection(e) {
const itemId = e.target.id;
// ... handle itemId and selectedItems ...
}
render() {
return (
<div className="main-container">
<SelectableList
data={props.right.data}
onDataSelection={e => this.onDataSelection(e)}
selectedItems={this.state.selectedItems}
/>
<Map
pins={props.right.pins}
selectedItems={this.state.selectedItems}
/>
</div>
);
}
}
export default RightContainer;
Thanks in advance!
As React docs state
Often, several components need to reflect the same changing data. We
recommend lifting the shared state up to their closest common
ancestor.
There should be a single “source of truth” for any data that changes
in a React application. Usually, the state is first added to the
component that needs it for rendering. Then, if other components also
need it, you can lift it up to their closest common ancestor. Instead
of trying to sync the state between different components, you should
rely on the top-down data flow.
Lifting state involves writing more “boilerplate” code than two-way
binding approaches, but as a benefit, it takes less work to find and
isolate bugs. Since any state “lives” in some component and that
component alone can change it, the surface area for bugs is greatly
reduced. Additionally, you can implement any custom logic to reject or
transform user input.
So essentially you need to lift those state up the tree that are being used up the Siblings component as well. So you first implementation where you store the selectedItems as a state in the RightContainer is completely justified and a good approach, since the parent doesn't need to know about and this data is being shared by the two child components of RightContainer and those two now have a single source of truth.
As per your question:
As feasible alternative I thought of adding a _selected property to
each item in state.right.data in MainContainer and pass the select
callback three levels down to SelectableList, handling all the
possible actions in MainContainer
I wouldn't agree that this is a better approach than the first one, since you MainContainer doesn't need to know the selectedItems or handler any of the updates. MainContainer isn't doing anything about those states and is just passing it down.
Consider to optimise on performance, you yourself talk about implementing a shouldComponentUpdate, but you can avoid that by creating your components by extending React.PureComponent which essentially implements the shouldComponentUpdate with a shallow comparison of state and props.
According to the docs:
If your React component’s render() function renders the same result
given the same props and state, you can use React.PureComponent for a
performance boost in some cases.
However if multiple deeply nested components are making use of the same data, it makes sense to make use of redux and store that data in the redux-state. In this way it is globally accessible to the entire App and can be shared between components that are not directly related.
For example consider the following case
const App = () => {
<Router>
<Route path="/" component={Home}/>
<Route path="/mypage" component={MyComp}/>
</Router>
}
Now here if both Home and MyComp want to access the same data. You could pass the data as props from App by calling them through render prop. However it would easily be done by connecting both of these components to Redux state using a connect function like
const mapStateToProps = (state) => {
return {
data: state.data
}
}
export connect(mapStateToProps)(Home);
and similarly for MyComp. Also its easy to configure actions for updating relevant informations
Also its particularly easy to configure Redux for your application and you would be able to store data related to the same things in the individual reducers. In this way you would be able to modularise your application data as well
My honest advice on this. From experience is:
Redux is simple. It's easy to understand and scale BUT you should use Redux for some specific use cases.
Since Redux encapsulates your App you can think of storing stuff like:
current app locale
current authenticated user
current token from somewhere
Stuff that you would need on a global scale. react-redux even allows for a #connect decorator on components. So like:
#connect(state => ({
locale: state.locale,
currentUser: state.currentUser
}))
class App extends React.Component
Those are all passed down as props and connect can be used anywhere on the App. Although I recommend just passing down the global props with the spread operator
<Navbar {...this.props} />
All other components (or "pages") inside your app can do their own encapsulated state. For example the Users page can do it's own thing.
class Users extends React.Component {
constructor(props) {
super(props);
this.state = {
loadingUsers: false,
users: [],
};
}
......
You would access locale and currentUser through props because they were passed down from the Container components.
This approach I've done it multiple times and it works.
But, since you wanted to really consolidate the knowledge of React first, before doing Redux you can just store your state on the top-level component and pass it down to the children.
Downsides:
You're gonna have to keep passing them down into inner level components
To update state from the inner level components you're gonna have to pass the function that updates the state.
These downsides are a little boring and cumbersome to manage. That's why Redux was built.
Hope I helped. good luck
By using Redux you can avoid such callbacks and maintain the whole state in one single store - so make your parent component connected component - and make left and right components dumb ones - and just pass in the props you get from parent to child - and you don't have to worry about callbacks in this case.

At which point in the lifecycle should I modify my props?

I have a react component that receives some props. These props need to be mapped to a different structure in order to be usable (for a select, which expects a different structure than our store). I'm wondering at which point in the lifecycle I should do this.
I've read the documentation: https://facebook.github.io/react/docs/component-specs.html, and it recommends to keep the render function pure:
The render() function should be pure, meaning that it does not modify
component state, it returns the same result each time it's invoked,
and it does not read from or write to the DOM or otherwise interact
with the browser
Now I'm assuming that it's still ok to map the props, as that's not state and I'm not changing them:
import React from 'react';
export default class IndicatorSelect extends React.Component {
constructor(props) {
super(props);
}
render() {
const options = this.props.store.items.map(item => ({ value: item.id, label: item.name }));
return (
<div>Display the data in some way here: {options.value} {options.label}</div>
);
}
}
ThisComponent.propTypes = {
store: React.PropTypes.object.isRequired,
};
Is this the recommended way, or should the parent component be responsible for formatting the props passed to this component? Or I should I do this in a lifecycle method, or a completely different manner?
Yes, #selvagsz comment is right - normalising props inside render is not that bad unless you do it heavily. If it's "too much", I would create a container component that normalises the props and passes them to a presentational one that only takes care of rendering.
It is not easy to answer the question, but a rule of thumb I follow is:
A presentational component should receive its props in the most convenient / normalised way possible so that it only takes care of rendering.
However, for normalisation as in your example it doesn't make sense to create additional container component - the overhead is too big at this point. When you start feeling the pain of converting props, create a container component and let it convert them for you or do it in the parent if it's already a container.

Categories

Resources