Frontend Gobal Stores

Global Stores
MOBX: Influenced by Object-Oriented Programming and Reactive Programming principles
1) Mobx — smart observations based on a mutable state.
2) not so functional and have classes that contains updatable/mutable data.
3) ultra performant, only the components which consumes the properties gets updated.
4) requires a good knowledge of the API: @observable, @action
5) Mobx is really opinionated and you should follow but in terms of best performance, it is the way to go.
6) you don't see as much of when and how your data is being updated, and it may be harder to track through the application.
Concept revolves around mainly four things
1. Application State (Can be of any type like primitive type or referential type)
2. Derivations ( Any values that depends upon the state or are computed through state)
3. Computed are functions that run on state change in shot like derivations
4. Reactions are similar to a computed value, but instead of producing a new value, a reaction produces a side effect for things like printing to the console, making network requests, incrementally updating the react component tree to patch the DOM, etc
5. Actions are all the things that alter the state.
#EXAMPLE
class ObservableTodoStore {
@observable todos = [];
@observable pendingRequests = 0;
constructor() {
mobx.autorun(() => console.log(this.report));
}
@computed get completedTodosCount() {
return this.todos.filter((todo) => todo.completed === true).length;
}
@computed get report() {
if (this.todos.length === 0) return "<none>";
const nextTodo = this.todos.find((todo) => todo.completed === false);
return (
`Next todo: "${nextTodo ? nextTodo.task : "<none>"}". ` +
`Progress: ${this.completedTodosCount}/${this.todos.length}`
);
}
addTodo(task) {
this.todos.push({
task: task,
completed: false,
assignee: null,
});
}
}
const observableTodoStore = new ObservableTodoStore();
// component
@observer
class TodoList extends React.Component {
render() {
const store = this.props.store;
return (
<div>
{" "}
{store.report}{" "}
<ul>
{" "}
{store.todos.map((todo, idx) => (
<TodoView todo={todo} key={idx} />
))}{" "}
</ul>{" "}
{store.pendingRequests > 0 ? <marquee> Loading... </marquee> : null}{" "}
<button onClick={this.onNewTodo}> New Todo </button>{" "}
<small> (double - click a todo to edit) </small> <RenderCounter />
</div>
);
}
onNewTodo = () => {
this.props.store.addTodo(prompt("Enter a new todo:", "coffee plz"));
};
}
@observer
class TodoView extends React.Component {
render() {
const todo = this.props.todo;
return (
<li onDoubleClick={this.onRename}>
<input
type="checkbox"
checked={todo.completed}
onChange={this.onToggleCompleted}
/>{" "}
{todo.task}{" "}
{todo.assignee ? <small> {todo.assignee.name} </small> : null}{" "}
<RenderCounter />
</li>
);
}
onToggleCompleted = () => {
const todo = this.props.todo;
todo.completed = !todo.completed;
};
onRename = () => {
const todo = this.props.todo;
todo.task = prompt("Task name", todo.task) || todo.task;
};
}
ReactDOM.render(
<TodoList store={observableTodoStore} />,
document.getElementById("reactjs-app")
);
RECOILJS: mimics react hooks e.g useState
1) state management library based on decentralized states (miultiple small atoms/states)
2) works well with React hooks and indeed it mimics the useState API with new names: useRecoilState.
3) you can reuse states/atoms with selectors (almost a la Redux).
4) consume and updates on any levels.
5) At the moment, Recoil seems like a good compromise between simplicity and performance. It is a fine addition to React hooks and it is neither the worst nor the best in term of performance.
6) It's experimental.
#EXAMPLE
// state.js
import { atom } from "recoil";
// count here is passed to useRecoilState
const count = atom({
key: "count",
default: 0,
});
import { selector, useRecoilState } from "recoil";
const isEvenCount = selector({
key: "evenCount",
get: ({ get }) => {
const state = get(count);
return state % 2 === 0;
},
});
// Functional Component
export const Counter = () => {
const [countState, setCount] = useRecoilState(count);
// see this 👇
const value = useRecoilValue(isEvenCount);
};
REDUX : influenced by Functional Programming principles:
1) Prevents unnecessary re-renders, as when the state changes, it returns new state which uses shallow copy.
2) Testing will be easy as UI and data management are separated.
3) History of state is maintained which helps in implementing features like undo very easily. aka time travel debugging.
4) It is most commonly used with React.
5) As state is immutable in redux, the reducer updates the state by returning a new state every time which can cause excessive use of memory.
6) Seperate modules/middleware for async work e.g redux-saga, redux-thunk and computed/derived props e.g reselect.
7) It has large boilerplate code and restricted design.
#EXAMPLE
import { combineReducers, createStore } from "redux";
// actions.js
export const activateGeod = (geod) => ({
type: "ACTIVATE_GEOD",
geod,
});
export const closeGeod = () => ({
type: "CLOSE_GEOD",
});
// reducers.js
export const geod = (state = {}, action) => {
switch (action.type) {
case "ACTIVATE_GEOD":
return action.geod;
case "CLOSE_GEOD":
return {};
default:
return state;
}
};
export const reducers = combineReducers({
geod,
});
// store.js
export function configureStore(initialState = {}) {
const store = createStore(reducers, initialState);
return store;
}
export const store = configureStore();
// COMPONENT USAGE
import React from "react";
import { connect } from "react-redux";
import { activateGeod, closeGeod } from "./redux";
// App.js
export class App extends React.Component {
render() {
return (
<div>
<h1>{this.props.geod.title || "Hello World!"}</h1>
{this.props.geod.title ? (
<button onClick={this.props.closeGeod}>Exit Geod</button>
) : (
<button
onClick={() =>
this.props.activateGeod({ title: "I am a geo dude!" })
}
>
Click Me!
</button>
)}
</div>
);
}
}
// AppContainer.js
const mapStateToProps = (state) => ({
geod: state.geod,
});
const mapDispatchToProps = {
activateGeod,
closeGeod,
};
const AppContainer = connect(mapStateToProps, mapDispatchToProps)(App);
export default AppContainer;
unstated-next
1) It's 40x smaller than redux : ~200 bytes min+gz.
2) it's easily integrated with every React library.
3) It's minimal. It's just React and takes 5 minutes to learn.
4) Written in TypeScript and will make it easier for you to type your React code
#EXAMPLE
import { createContainer } from "unstated-next";
function useCounter() {
let [count, setCount] = useState(0);
let decrement = () => setCount(count - 1);
let increment = () => setCount(count + 1);
return { count, decrement, increment };
}
let Counter = createContainer(useCounter);
function CounterDisplay() {
let counter = Counter.useContainer();
return (
<div>
<button onClick={counter.decrement}>-</button>
<p>You clicked {counter.count} times</p>
<button onClick={counter.increment}>+</button>
</div>
);
}
function App() {
return (
<Counter.Provider>
<CounterDisplay />
<CounterDisplay />
</Counter.Provider>
);
}
Apollo client state
1) it uses react variable state which are linked with useQuery, when ever you change local state your quries get refreshed.
2) no derived state or computed state.
3) you define custom query fields and then use "useQuery" hook to read state and custom mutation/actions to mutate state.
4) it's immutatble.
#EXAMPLE
// state.js <----------------------------------------------->
import { ReactiveVar, makeVar } from "@apollo/client";
export type modelState = {
name: string,
id: string,
};
const modelStateInitialValue: modelState = {
name: "",
id: "",
};
export const modelStateVar: ReactiveVar<modelState> =
makeVar < modelState > modelStateInitialValue;
// model state
export const modelFields = {
selectedmodel: {
read() {
return modelStateVar();
},
},
};
//mutations/actions <----------------------------------------------->
import { ReactiveVar } from "@apollo/client";
import { modelStateVar, modelState } from "./state";
// mutation
const selectmodel = (stateVar: ReactiveVar<modelState>) => (
modelState: modelState
) => {
stateVar(modelState);
};
export const modelLocalMutations = {
selectmodel: selectmodel(modelStateVar),
};
// apollo client <----------------------------------------------->
new ApolloClient({
connectToDevTools: true,
link: createUploadLink({ uri: GRAPHQL_ENDPOINT }),
cache: new InMemoryCache({
addTypename: true,
typePolicies: {
Query: {
fields: {
...fields, // <--------------- custom fields
},
},
},
}),
});
// COMPONENT;
// read and write on frontend <----------------------------------------------->
//WRITING
modelLocalMutations.selectModel({
id: "asd7897asdasfbdhwqjbdqj",
name: "TEST",
});
//READING
const GET_MODEL_LOCAL_STATE = gql`
query modelState {
selectedModel @client {
id
name
}
}
`;
const {
data: { selectedModel },
} = useQuery(GET_MODEL_LOCAL_STATE);