Awesome
react-providers
React-providers is a library which helps you to work and manage React.Context components. The library offers you:
- simple syntax with HOC components like react-redux.connect;
- the single location to place your context models;
- to implement your own business logic in contexts using familiar React components and react-lifecycle methods;
- an easy way to include another context when it became required;
- to work with dependencies between your contexts. So you should just describe dependencies and react-providers will build a correct hierarchy for you. You can see comments example to get more information about managing dependencies;
- working both in server and client
- this library depends only on
hoist-non-react-statics
, so you wouldn't get a lot of npm modules after installing.
Contents
Demo
Here is several examples: https://xnimorz.github.io/react-providers/
Install
yarn add react-providers
or
npm install --save react-providers
Usage step by step
This library can work in two mods:
- Use only HOC for Context
- Create the store with lots of context components
Let's see how to use library in both ways
Using only a HOC for context
- Implement your own React.Component using React.Context or just using React.Context:
export const TodosContext = React.createContext(['Do some work', 'Close task in jira']);
- Connect to this context using HOC
use
:
import { TodosContext } from './path/to/TodosContext';
function MyComponent({ todos }) {
console.log(todos); // ['Do some work', 'Close task in jira']
}
export default use({ todos: TodosContext })(MyComponent);
Working with the store and connect to it
Step 1: Create your context React component:
Implement your own React.Component using React.Context:
https://github.com/xnimorz/react-providers/blob/master/examples/src/models/todoMVC/todos.js — in this file we use React lifecycle methods to store updates in localStorage.
import React from 'react';
const TodosContext = React.createContext();
class TodosContextProvider extends React.Component {
state = {
list: lsData || [],
};
// We can use React lifecycle methods to organise our work!
componentDidUpdate() {}
// You can get access to the methods from context consumer
// This methods like reducers in Redux changes the state of the application but using React api
add = (todo) => {
this.setState((state) => ({
list: [
...state.list,
{ id: 1 + Math.max(0, ...state.list.map((todo) => todo.id)), text: todo, completed: false },
],
},
}));
};
render() {
// describe data that will be Context.Provider value (we can use here memoization, or move it to state to avoid unnecessary rerenders)
const value = {
// our data
data: this.state,
// our methods
add: this.add,
};
// Provide value to consumer
return <TodosContext.Provider value={value}>{this.props.children}</TodosContext.Provider>;
}
}
// Each module should provide 2 components — Consumer and Provider
export default {
Consumer: TodosContext.Consumer,
Provider: TodosContextProvider,
};
Step 2: Add the context to react-providers.AppProvider:
Then, you can add your providers to the AppProvider:
import React from 'react';
import reactDOM from 'react-dom';
import { AppProvider } from 'react-providers';
import todos from './path/to/todos';
reactDOM.render(
<AppProvider contexts={{ todos }}>
<Example>
YourApplicationIsHere
</Example>
<AppProvider/>
);
Step 3: Getting access to data from the context
We can use use
HOC to get access to data that we store with AppProvider
component:
import React from 'react';
import { use } from 'react-providers';
import todos from './path/to/todos';
class Example extends React.Component {
componentDidMount() {
// We'll get access to methods
this.props.todos.add('New todo');
}
render() {
// And data
console.log(this.props.todos.data.list); // first time we'll get [], and second time [{id: 1, text: 'New todo', completed: false}]
return 'SomeData';
}
}
export default use({ todos })(Example);
See more examples here: https://xnimorz.github.io/react-providers/
Step 4 (optional): create context which depends on another context
AppProvider
automatically builds correct Context tree that resolves dependencies between contexts. To get access from one context to another you can use use
HOC:
import React from 'react';
import { AppProvider, use } from 'react-providers';
const A = React.createContext({});
class AProvider extends React.Component<{}, { data: string }> {
state: Readonly<{ data: string }> = {
data: 'A Context',
};
render() {
return <A.Provider value={this.state}>{this.props.children}</A.Provider>;
}
}
const aContext: IContext = {
Consumer: A.Consumer,
Provider: AProvider,
};
const B = React.createContext({});
class BProvider extends React.Component<{}, { data: string }> {
state: Readonly<{ data: string }> = {
data: 'B Context',
};
render() {
console.log(this.props.a.data); // 'A Context'
return <B.Provider value={this.state}>{this.props.children}</B.Provider>;
}
}
const bContext: IContext = {
Consumer: B.Consumer,
// Describe BContext depends on AContext
Provider: use('a')(BProvider),
};
ReactDOM.render(<AppProvider contexts={{ a: aContext, b: bContext }} />);
Developer experience
Why I should use react-providers instead of plain react.Context?
- The centralised place to store and manage our logic
You can store your context in one place where you paste AppProvider component (as a rule, it's better to put it at the beginning of React tree).
You won't spend the time to manage dependencies between components. it's enough to declare dependency using use
HOC in Providers (like in examples above). You have only one limitation — cyclic dependencies aren't allowed.
- Reduce code wrapping
As a rule, each model, which presented by React.Context component, should have only a single responsibility (SOLID principles). In our React components, we often want to get data from several Consumers. In such cases our code would be:
function YourComponent() {
return (
<UsersConsumer>
{(users) =>
<TopicsConsumer>
{(topics) =>
<CommentsConsumer>{(comments) => /*Your data*/ }</CommentsConsumer>
}
</TopicsConsumer>
}
</UsersConsumer>
);
}
export default YourComponent;
Let's compare this example to the same example with react-providers lib:
function YourComponent() {
const { users, topics, comments } = this.props;
return (/*Your data*/);
}
// 1) connection using Object:
export default use({
users: UserContext,
topics: TopicsContext,
comments: CommentsContext
})(YourComponent)));
// 2) or using array:
export default use('users', 'topics', 'comments')(YourComponent)));
We encapsulated all consumers' logic to our HOC component use
. In component, we paid attention to How our component should work and what data it should return.
About react-providers pros
React-providers works with React.Context and offers you an easy way to combine and to store Contexts in one place. You work with familiar Components state. It hasn't any additional boilerplate code.
React-providers manage your dependencies between contexts, so you don't mind about the right order of contexts in React components tree.
HOC use
allows you to describe dependencies using objects, arrays or string.
About redux
Redux (https://github.com/reduxjs/redux) is a great library that offers you time traveling and to store all application data in one place. It uses actions to describe what happens in application and reducers to implement the data logic of your application. To implement async action creators we can use our own middleware or existed middleware like redux-saga or redux-thunk. Redux offers us a great and pretty clear API to build our applications. But we'll have lots of boilerplate code to describe all actions, reducers etc. If you create not a too big application you probably don't need redux.
How to work without redux and any other library. Just using react
On the other hand, you probably don't need redux. Our React components can work without redux. And they do work.
We can implement a method addTodo
to the React component called Todos
, which adds a new todo to the list that stored in the component state. Then we can transfer this addTodo
method as a prop to other components. By this way, components can call addTodo
.
In described case addTodo
calls this.setState
method to set a new state to our Todos
React component. addTodo
implements the reducer pattern in our application. So addTodo
is pretty similar to reducers in Redux. Moreover, react.lifecycle methods offer you to implement middlewares logic in react. You can add you own logic to componentDidMount, DidUpdate etc. methods. Consequently, we can work using only React API and React boilerplates to implement both view and business logic, because React already has methods that you need to implement your business logic.
Let's have a look at the way how we can work without redux:
- We can implement our business or async logic locally using React components. To transfer data from one component to another we can use props.
- When or if this logic becomes necessary in several places in our application we can move it to React Context.
- When one context depends on another context we can decompose them and place them in the correct order in React components tree.
OR
When we realize the logic becomes necessary in several places in our app we can move it to React Context and put it to react-providers.AppProvider component. And now we won't mind about where we should add Consumer for context. We just use use
HOC where we need like react-redux.connect.
Dependencies resolver
This library resolves automatically dependencies between Providers. It means you can create two contexts AContext and BContext. BContext can depend on AContext, and the application will work correctly.
The main limitation is you should create cyclic dependencies. e.g. BContext can depend on AContext, but AContext can't depend on BContext simultaneously.
License
MIT License