In a website, I have multiple react rendering elements. I want to pass the data between this 2 individual elements. What are the possible options for passing the data between 2 elements
ReactDOM.render(<Header/>, document.getElementById('header'));
ReactDOM.render(<SideBar/>, document.getElementById('sidebar'));
I want to have a single data storage between these elements. Like I get data in one component and I want that data to be accessible in all the elements (in all ReactDOM). So what can be the possible options for this?
Edit: Due to requirement I can't merge them in to same root component. Only some portion of page is in react other is still in the HTML/Jquery. So I render them individually in the required div.
Does redux store work between different ReactDOMs?
In a website, I have multiple react rendering elements. I want to pass the data between this 2 individual elements. What are the possible options for passing the data between 2 elements
ReactDOM.render(<Header/>, document.getElementById('header'));
ReactDOM.render(<SideBar/>, document.getElementById('sidebar'));
I want to have a single data storage between these elements. Like I get data in one component and I want that data to be accessible in all the elements (in all ReactDOM). So what can be the possible options for this?
Edit: Due to requirement I can't merge them in to same root component. Only some portion of page is in react other is still in the HTML/Jquery. So I render them individually in the required div.
Does redux store work between different ReactDOMs?
Share Improve this question edited Apr 25, 2018 at 16:31 hardiksa asked Apr 25, 2018 at 13:48 hardiksahardiksa 1,4971 gold badge15 silver badges19 bronze badges 1- check this too: javascriptstuff.com/component-communication – Giorgi Moniava Commented Apr 25, 2018 at 13:52
5 Answers
Reset to default 15Use Portals from the react-dom
library:
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
const HeaderPortal = ReactDOM.createPortal(<Header />, document.getElementById('header'))
const SideBarPortal = ReactDOM.createPortal(<SideBar />, document.getElementById('sidebar'))
const store = createStore(/* ... */)
ReactDOM.render(
<YourDataStoreProvider store={store}>
<HeaderPortal />
<SideBarPortal />
</YourDataStoreProvider>,
document.getElementById('root') // assuming you have an empty "dummy" node with that id
);
Just render you app into a container anywhere in your DOM or create a new "dummy" node. In my example I assume there is an empty node with the id root
. Then render your other components into a portal.
Does redux store works between different ReactDOMs?
Your app will work as if it was rendered entirely into the same container. If you use Portals your components will be in the same component tree having the same context while being rendered somewhere else.
Should I use Portals?
Using Portals usually is intended to be used for components that visually need to "break out" of its container like modals or dialogs. But you can also use them to create widget-like components that can be rendered anywhere.
Creating a generic Portal component
You can also create a generic <Portal>
component that creates a portal given a container id
:
import {createPortal} from 'react-dom';
const Portal = ({children, container}) => createPortal(
children,
document.getElementById(container),
);
export default Portal;
And use it this way:
ReactDOM.render(
<YourDataStoreProvider store={store}>
<Portal container="header">
<Header />
</Portal>
<Portal container="sidebar">
<SideBar />
</Portal>
</YourDataStoreProvider>,
document.getElementById('root')
);
EDIT:
You need one node in the DOM where you can render your app. This can be a new DOM element that you create or it can be one of the containers you already have. Given you use the <Portal>
component from above it could also look like this:
ReactDOM.render(
<YourDataStoreProvider store={store}>
<Header /> // this will be visible in your header container
<Portal container="sidebar">
<SideBar /> // this will be rendered to the sidebar container
</Portal>
</YourDataStoreProvider>,
document.getElementById('header')
);
This will render your app in the header
container. But only your <Header>
component will actually have a DOM representation in that container. The sidebar will be rendered in the sidebar
container by the portal. But still they will share the same react component tree and have the same store provider.
You can create a component container and have both as a child and render them separate. Exchange the data through props and the mother container.
I provide an example:
https://codepen.io/hkares/pen/rvLPzQ
HTML
//html
<header id="header"></header>
<main>
<nav id="nav"></nav>
<section>I am section</section>
</main>
<footer>This is a footer</footer>
<div id="useless"></div>
Index.js
const HEADER_NODE = document.querySelector("#header");
const NAVBAR_NODE = document.querySelector("#nav");
const Header = ({ name }) => ReactDOM.createPortal(<h1>{name}</h1>, HEADER_NODE);
const NavBar = (props) => ReactDOM.createPortal(
<article>
<label>
Set header name: <input {...props} />
</label>
</article>
, NAVBAR_NODE);
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "this is a header"
};
}
onChange = ({ target }) => this.setState({ value: target.value });
render() {
const { value } = this.state;
return (
<React.Fragment>
<Header name={value} />
<NavBar value={value} onChange={this.onChange} />
</React.Fragment>
);
}
}
ReactDOM.render(
<Container />,
document.querySelector("#useless")
);
container component
class App extends React.Component{
render(){
var data=[];
return(<span>
<Header data={data}/>
<SideBar data={data}/>
</span>);
}
}
Depends on the relationship between the two components.
Scenario I:If they are just siblings, then you can wrap them in your container component or just a simple , and passing the props to the child, like
<div>
<HeaderPortal data={yourSharedData}/>
<SideBarPortal data={yourSharedData}/>
</div>
or
<YourContainer>
<HeaderPortal />
<SideBarPortal/>
</YourContainer>
Then in YourContainer
, you can define your shared data, and add them to each of your child like
render(){
<div>
{children.map(child=> React.cloneElement(child, {
data: yourSharedData
})}
</div>
}
Scenario II If these two components are far from each other
Like the above, you wanna share data between 2 and 12, then you should use redux
storage to share the data, which is a central places to manage your component state, and share any part it to the required component via mapStateToProps
, you can find more details here.
Build a context system:
- a context is an object that stores data.
- a module can access and update data in context.
- a module can be notified when data in context is updated.
- depends on your need, a module can either freely add data to context, or restrict to only update existing data.
It's not really a react specific problem.
An example of context system look like this:
content.js
import uniqueId from 'lodash/uniqueId';
let context = {
foo: '',
bar: ''
};
let listeners = {
// key: [{ id, callback }]
};
let getAppContext = function(key) {
return context[key];
};
let updateAppContext = function(key, value) {
if (!context.hasOwnProperty(key)) {
throw new Error(
`Failed to update appContenxt: "${key}" does not exist.`
);
}
let oldValue = context[key];
context[key] = value;
let callbacks = listeners[key];
if (callbacks !== undefined && callbacks.length !== 0) {
callbacks.forEach(function(item) {
item.callback(value, oldValue);
});
}
};
let onAppContextUpdate = function(key, callback) {
if (!context.hasOwnProperty(key)) {
throw new Error(
`Failed to add listener on appContenxt update: "${key}" does not exist.`
);
}
if (listeners[key] === undefined) {
listeners[key] = [];
}
let isListening = true;
let id = uniqueId();
listeners[key].push({ id, callback });
return function removeListener() {
if (isListening) {
listeners[key] = listeners[key].filter(i => i.id !== id);
isListening = false;
}
};
};
export {
getAppContext,
updateAppContext,
onAppContextUpdate,
};
Usage
import { getAppContext, updateAppContext, onAppContextUpdate } from 'content.js';
// read foo
let foo = getAppContext('foo');
// update foo
updateAppContext('foo', 123);
// get notified when foo is updated
let removeListener = onAppContextUpdate('foo', function(newVal, oldVal) {
console.log(newVal, oldVal);
});
// unlisten
removeListener();