helion3
helion3

Reputation: 37381

Sharing global/singleton data in react app

I'm rewriting a small app to try and better understand React. I'm trying to determine the "correct"/most efficient method of sharing "singleton" data - for example, a user who's been properly authenticated upon login.

Right now the parent "application" component has a user property in its state, which I pass to child components as a prop:

<Toolbar user={this.state.user} />
<RouteHandler user={this.state.user}/>

(I'm using react-router). This works, and in read-only cases like this, isn't terrible. However, my actual login form component (which is a route, and would be inside RouteHandler), needs some way to "set" the new user data, so I also need to pass in some callback:

<RouteHandler onAuthenticated={this.setUser} user={this.state.user}/>

Not a big problem, except for the fact that now this method is available to every "route" handled by RouteHandler.

I've been reading up and it seems like the only alternative is an EventEmitter or Dispatch-style system.

Is there a better way I'm missing? Is an event emitter/dispatcher system worth using when there's really only one or two uses in an app this small?

Upvotes: 23

Views: 34534

Answers (3)

Manoj Kurien
Manoj Kurien

Reputation: 159

React Context provides a way to pass data through the component tree without having to pass props down manually at every level. With context, every component nested under a Provider has access to the data, but you need to explicitly read the value.

I recommend using React Hooks with useContext. One way to do this would be to set the value of the context to be an object with setter and getter functions.

import React, { useState, useContext } from "react"

export const UserContext = React.createContext({}); //Initialise

//Wrapper with getter and setter 
const App = () => {
    const [user, setUser] = useState();
    const value = {user, setUser}
    return (
        <div>
            <UserContext.Provider value={value}>
                <RouteHandler/>
                <AnotherComponent/>
            </UserContext>
            <ComponentWithoutAccessToUserContext/>
        </div>
    )
}

const RouteHandler = (props)=> {
   const { user, setUser } = useContext(UserContext)
   // This component now has access to read 'user' and modify it with 'setUser'


}

const AnotherComponent = () => {
   return (<div>Component which could be get access to the UserContext</div>)
}

Upvotes: 15

cmdv
cmdv

Reputation: 1753

One way is to subscribe to an Observable emitted from your data model.

Router.run(routes, Handler =>
    Model.subject.subscribe(appState =>
        React.render(
            <Handler {...appState}/>,
            document.getElementById('app')
        )
    )
);

...appState being the data coming from observable (in this case model), making these your props so you can then feed them to the app like below

<RouteHandler {...this.props} />

and any child component can pick them up with this.props

the answer is more complex that this but if you look at RxJS+React you will get a full working examples of simple data flows

Upvotes: 1

Bogdan Savluk
Bogdan Savluk

Reputation: 6312

For singleton - you can just create separate module for user service and import it into module where you define components that need it it.

Other quite similar, but more powerful option, is to use DI container - define your react components as a services in DI container, with dependencies to other services like one for user data. This would be more suitable for universal(isomorphic) app - because, you will be able to easily replace dependencies with specific implementations, or for case when you need to create separate instances for separate scopes(like for user sessions server-side).

Also if using this approach, I would recommend to separate pure react components from logic - you can create separate pure component that receives all data, and callbacks as a props, and than create HoC component in DI container that will wrap it and will pass needed data and callbacks.

If you need DI container - there is a plenty of them, but I will recommend to look at angular 2 di container, or if you would like something simpler - below I referenced my project, it has very simple but yet powerful DI inspired by angular 2 DI(it is easy to pull from that project - just one file + test)).


About notifying components about changes, and organising async logic - you still will need something like EventEmitter to notify components about changes, and you will need to write life cycle callbacks for components to subscribe/unsubscribe from updates… You can do this by hand or creating mixin or HoC to shorten that.

But from my perspective, there is better approach - try reactive programming, and RxJS in particular. It plays very well with react.

If you are interested about options connecting Rx with React - take a look at gist https://gist.github.com/zxbodya/20c63681d45a049df3fc, also it can be helpful about implementing HoC component with subscription to EventEmitter mentioned above.

I have a project that is intended for creating isomorphic(rendered server side, and than same html reused client side) widgets with react. It has DI container to pass dependencies, and it uses RxJS to manage async logic:

https://github.com/zxbodya/reactive-widgets

Upvotes: 0

Related Questions