sharing data between disconnected components - reactjs

I'm not creating a react app from scratch, but adding interactive components to an existing webpage. I'm mounting two components, disconnected to each other like this:
ReactDOM.render(<Component1 />, document.getElementById('comp1-root'));
ReactDOM.render(<Component2 />, document.getElementById('comp2-root'));
They lie far from each other on the page.
How do I have them to communicate their states with each other?
Thank you.

React Portals is what I was looking for.
As per my question, I wanted to have same context for all the components mounted at different locations (dom nodes).
Portals solved exactly this issue. Now, I can have one context component housing all the components that exist on that page. Like this:
const dashboardContextDom = document.getElementById('dashboard-root');
const comp1DOM = document.getElementById('comp1-root');
const comp2DOM = document.getElementById('comp2-root');
const Dashboard = () => {
return (
<>
{ReactDOM.createPortal(<Component1 />, comp1DOM)}
{ReactDOM.createPortal(<Component2 />, comp2DOM)}
</>
);
}
if(dashboardContextDom) {
ReactDOM.render(<Dashboard />, dashboardContextDom);
}
With these components housed in one context allows to easily pass state from one component to another via prop drilling and lifting state up.

You have two options with React:
Move Component 1 and Component 2 and their state into a parent component, and pass the parent's update state function to them:
ComponentWrapper {
...
const updateState = () => {
//update state
}
return (
<Component 1 updateParentState={updateState}/>
<Component 2 updateParentState={updateState}/>
)
...
}
You cannot update the state of a component that is not a parent or a child without using an external state management solution, such as Redux or the new useContext api/hook. These libraries involve moving all individual component's states to a larger centralized global state, which can be updated and accessed by all components.

Related

Creating React components without rendering them

I'm trying to create some components that need to communicate their loading states to the parent component.
Here's an example:
const Parent = () => {
// Loading
const [hasFirstFinishedLoading, setHasFirstFinishedLoading] = React.useState(false);
const [hasSecondFinishedLoading, setHasSecondFinishedLoading] = React.useState(false);
const children = (
<>
<First
onFinishedLoading={() => setHasFirstFinishedLoading(true)}
/>
<Second
onFinishedLoading={() => setHasSecondFinishedLoading(true)}
/>
</>
);
if (hasFirstFinishedLoading && hasSecondFinishedLoading) {
return <>{children}</>
}
return <LoadingComponent />
}
Essentially, the idea is that I render a Loading component while the children components are loading (i.e making some network requests). However, this doesn't work because the children component are not mounted, and so they don't start their loading process.
Is there a way to mount the children components so they can load?
Some approaches I've already considered:
Create a top level context that handles loading (i.e network requests) and let that control rendering. I can just render the children components once the context has finished its network requests.
Render the children components, but set display:none.
Are there any other approaches?
Your approaches sound reasonable. As soon as React 18 is released, however, Suspense will be officially available. It is built for exactly this use case, but currently only experimental support is available. Check out the react docs for more info.

How to pass data value from one Child Component to another Child Component in React?

I have a Weather project where I have two components. One for day/night timings: SunriseSunset and another for daily forecast: DailyForecast. I need to pass value of time obtained from SunriseSunset to DailyForecast.
Here are the two components for reference:
SunriseSunset.js (First File) --> Function Component
const SunriseSunset = (props) => {
const time2 = moment.tz(props.timezone).format('HH:mm')
// I want to pass the time2 value in DailyForecast.js file
return (
<React.Fragment>
</React.Fragment>
)
}
export default SunriseSunset
DailyForecast.js (Second File) --> Class Component
export class DailyForecast extends Component {
return (
<div>
</div>
)
}
}
export default DailyForecast
You should lift the shared state/data up to common ancestor. Here is a working
CodeSandbox example.
const Parent = () => {
const timezone = "Asia/Calcutta";
const time2 = moment.tz(timezone).format("HH:mm");
return (
<>
<SunriseSunset time={time2} />
<br />
<DailyForecast time={time2} />
</>
);
}
Here is the official documentation: Lifting State Up
Also, if you don't want to drill down props on multiple levels then you should consider React context API or something like Redux for managing application state. Redux is a robust state container but it might be overkill for your use case.
The first question you need to ask yourself is do these two child components have the same parent?
you need to pass values from SunriseSunset to the same parent of DailyForecast and then when the parent gets the value it needs to pass the value down to DailyForecast;(you can also use context api to do it in a simpler way)
You can use third-party state management library like redux to construct a isolated state.
And then you need to make SunriseSunset and DailyForecast have access to this isolated state. after that these child components are able to share states;(there are so many state management libraries like Mobx, or state-machine)
There are also other options if you do not want any of the above solutions.
Just use local storage or url to share your states.
For instance, in SunriseSunset you can save this state in localstorage or url and in DailyForecast you can read this state from the current url or localstorage. But this option is not elegant.

How do I pass things between components on the same level?

I've got a React app of the form:
<App>
<ParentComponent>
<FormComponent>
<AnotherComponent>
</ParentComponent>
</App>
I want to be able to update some state values of <FormComponent> by clicking on elements in <AnotherComponent>.
I don't want to place them inside each other, but keep them side-by-side. I don't want to lift up <FormComponent> state as it's nicely encapsulated.
What's the best way to achieve this? Can I do it with just react or do I need RxJS/something else?
The data in React Flows Down.
If you don't want to lift the state up, what's left is Context API or any state management library like Redux and MobX (both implement Context API with different strategy).
But still, the state is "above" FormComponent (you still lifting state up).
const Context = React.createContext();
const ParentComponent = () => {
const contextState = useState(DEFAULT_STATE);
return (
<Context.Provider value={contextState}>
<FormComponent />
<AnotherComponent />
</Context.Provider>
);
};
const FormComponent = () => {
const [, setState] = useContext(Context);
// use setState
};
const AnotherComponent = () => {
const [state] = useContext(Context);
// valid state updated from FormComponent
};
As far as I can tell the "right thing" to do in these instances is move the state up one level, into your Parent component.
If you have a look at the Intro to React:
To collect data from multiple children, or to have two child components communicate with each other, you need to declare the shared state in their parent component instead.
"Lifting state up" is a common thing in React applications and doesn't require introducing a state management solution like Redux or RxJS.
Apart from the ways mentioned above you can pass down a function as prop from the Parent component to AnotherComponent. And when clicking any element in Another component, pass the intended value in that function, which will in turn be transferred to ParentComponent. And you can then pass the value as props to the FormComponent from the ParentComponent.
You can check this example for better understanding https://react-m9skpu.stackblitz.io

How to deal with the props and tightly coupled components in React?

I have a couple components which are tightly coupled to each other. The highest component receives prop called options. The prop options is passed down through next components and so on.
Which is the best way to emit changes from nested components to each others? I wouldn't rather use redux in this case.
This example will work for React16.3 and above.
Click here to check working example.
a) Get Data from parent component to nested chid conponent using context api of react
1. Grand Parent Component
Context lets us pass a value deep into the component tree without
explicitly threading it through every component. Create a context for
the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
const theme = "dark";
return (
<ThemeContext.Provider value={theme}>
<Toolbar />
</ThemeContext.Provider>
);
}
}
2. Parent Component
A component in the middle doesn't have to pass the theme down
explicitly anymore.
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
3. Child Component
function ThemedButton(props) {
// Use a Consumer to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
return (
<ThemeContext.Consumer>
{theme => <div>{theme}</div>}
</ThemeContext.Consumer>
);
}
Replace theme to options in your case.
For more detail take the reference of react doc. Click here
b) Store data from parent component to store and get it in nested child component using redux
Here you are getting data from state and passing options data to
your component
const mapStateToProps = (state) => ({
options: state.options,
});
Here you are connecting your component from state
export default connect(
mapStateToProps,
null,
)(ChildComponent);

Limit Redux to update only components affected by the change

trying to understand React-Redux, i find it unusual that all my components get new props when ever any slice of the state get changed. so is this by design or i'm doing something wrong ?
example App
class App extends React.Component {
render(){return (
<div>
<Navbar data={this.props.navbar} />
<Content data={this.props.content} />
</div>);
}
}
select (state) => ({ navbar:state.navbar, content:state.content});
export default connect(select)(App);
Components
export const NavbarForm = props => {
console.log('RENDERING with props--->',props);
return (<h1>NAV {props.data.val}</h1>);
};
export const ContentForm = props => {
console.log('RENDERING CONTENT with props--->',props);
return (<h1>CONTENT {props.data.val}</h1>);
};
////////INDEX.js//////
const placeholderReducer = (state={val:0},action)=>{
//will update val to current time if action start with test/;
if(action.type.indexOf('TEST/') === 0)return {val:Date.now();}
return state;
}
export const rootReducer = combineReducers({
navbar:placeholderReducer,
content: (state,action)=>(state || {}), //**this will never do a thing.. so content should never updates right !!**
});
const store = createStore(rootReducer, {}, applyMiddleware(thunk));
render( <Provider store={store}> <App /></Provider>, document.getElementById('app')
);
setInterval(()=>{ store.dispatch(()=>{type:'TEST/BOOM'}) },3000);
okay in this app, what i expect is that Navbar component will get updated every 3000ms while content component will never updates because its reducer will always return the same state.
yet i find it really strange that both components does reRender every time an action is fired.
is this by design ? should i worry about performance if my app has 100+ component ?
This is entirely by design. React assumes that your entire app will be re-rendered from the top down by default, or at least a given subtree will be re-rendered if a certain component does a setState or something similar.
Because you only have the very top component in your app connected, everything from there on down is React's standard behavior. A parent component re-renders, causing all of its children to re-render, causing all of their children to re-render, and so on down.
The core approach to improving UI performance in React is to use the shouldComponentUpdate lifecycle method to check incoming props and return false if the component does not need to re-render. This will cause React to skip re-rendering that component and all of its descendants. Comparisons in shouldComponentUpdate are generally done using shallow reference equality, which is where the "same object references means don't update" thing becomes useful.
When using Redux and connect, you will almost always find yourself using connect on many different components in your UI. This provides a number of benefits. Components can individually extract the pieces of the store state that they need, rather than having to hand them all down from the root component. In addition, connect implements a default shouldComponentUpdate for you, and does a similar check on the values you return from your mapStateToProps function. So, in a sense, using connect on multiple components tends to give you a "free win" in regards to performance.
Further reading on the topic:
Redux FAQ: Connecting multiple components
React/Redux Links: Performance articles
Yes this is by design. Action is dispatched. Reducers run. Store subscribers get notified "the store has changed". Connected components are store subscribers.
Typically you just don't worry about it until you can actually measure a performance problem that you can attribute to this - don't prematurely optimize.
If you find out that it is a problem, then you can do one of the following:
Add a shouldComponentUpdate method to your components so they can see that the props they received aren't different and do not need to render (there are lots of Pure Render mixins & high order components available to make this easy)
Instead of connecting the top-level app, connect the Navbar and Content components directly. The App will never rerender, but the children will if the store changes. And react-redux automatically uses shouldComponentUpdate to only re-render the connected components that actually have new props.

Resources