Security News
Node.js EOL Versions CVE Dubbed the "Worst CVE of the Year" by Security Experts
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
@air/react-memoized-context
Advanced tools
React Memoized Context React Context with Redux-like performance and patterns without installing Redux.
React Context with Redux-like performance and patterns without installing Redux.
Here at Air, we needed a way to store multiple instances of complex global state (what React Context API does) but with the performance of Redux. react-memoized-context
solves this problem.
A React Context provider renders all consumers every time it's value
changes - even if the component isn't using a property on the value
(if it's an object). This can cause lots of performance issues and the community is trying to solve it. We've looked at these other solutions but they're either not ready, had too many bugs or lacked features (like reading values on the fly) so we decided to roll our own.
Redux is great as a global store when multiple components want to read and write to a single centralized value. But when you want to have multiple global values with the same structure, Redux isn't as flexible because you need to duplicate your reducers, actions, and selectors. That's where React Context is nice because you can just wrap around another Provider.
npm install --save @air/react-memoized-context
yarn add @air/react-memoized-context
Create types for your context:
export interface User {
id: string;
name: string;
score: number;
}
export interface UsersTeamContextValue {
users: User[];
}
export interface UsersTeamContextActions {
addUser: (user: User) => void;
assignScore: (userId: User['id'], score: number) => void;
}
MemoizedContextType
:
export interface UsersTeamContextType extends MemoizedContextType<UsersTeamContextValue>, UsersTeamContextActionsType {}
export const defaultUsersTeamContextValue: UsersTeamContextType = {
...defaultMemoizedContextValue,
getValue: () => ({
users: [],
}),
addUser: () => {},
assignScore: () => {},
};
export interface AddUserAction extends MemoizedContextAction {
type: 'addUser';
data?: { user: User };
}
export interface AssignScoreAction extends MemoizedContextAction {
type: 'assignScore';
data?: { userId: User['id']; score: number };
}
export type UserTeamContextActions = AddUserAction | AssignScoreAction;
Create your context:
const UsersTeamContext = createContext<UsersTeamContextType>(defaultUsersTeamContextValue);
const useUsersTeamContext = () => useContext(UsersTeamContext);
Create your dispatch method. It should work as redux dispatch - takes an action, modifies state value and returns a new state:
export const usersTeamContextDispatch = (state: UsersTeamContextValue, action: UserTeamContextActions) => {
switch (action.type) {
case 'assignScore':
return {
...state,
users: state.users.map((user) => {
if (user.id === action.data?.userId) {
return {
...user,
score: action.data?.score ?? 0,
};
}
return user;
}),
};
case 'addUser':
return {
...state,
users: action.data ? [...state.users, action.data.user] : state.users,
};
}
};
Create your provider:
export const UsersTeamProvider = ({ children }: PropsWithChildren<{}>) => {
const { contextValue } = useMemoizedContextProvider<UsersTeamContextValue>(
// provide default value for your context
{
users: [],
},
usersTeamContextDispatch,
);
// create methods you want to expose to clients
const addUser = useCallback((user: User) => contextValue.dispatch({ type: 'addUser', data: { user } }), [contextValue]);
const assignScore = useCallback(
(userId: User['id'], score: number) => contextValue.dispatch({ type: 'assignScore', data: { userId, score } }),
[contextValue],
);
// memoize your final value that will be available for clients
// just return what's in contextValue and add your methods
const value = useMemo<UsersTeamContextType>(
() => ({
...contextValue,
addUser,
assignScore,
}),
[addUser, assignScore, contextValue],
);
return <UsersTeamContext.Provider value={value}>{children}</UsersTeamContext.Provider>;
};
To retrieve data from context, you need selectors:
export const usersTeamUsersSelector = (state: UsersTeamContextValue) => state.users;
usage in component:
const context = useUsersTeamContext();
// pass context to useMemoizedContextSelector
const users = useMemoizedContextSelector(context, usersTeamUsersSelector);
to simplify it, you can create a helper:
export function useUsersTeamContextSelector<T>(selector: (st: UsersTeamContextValue) => T) {
const context = useUsersTeamContext();
return useMemoizedContextSelector(context, selector);
}
then, to retrieve users
from context you can do:
const users = useUsersTeamContextSelector(usersTeamUsersSelector);
Start using your context!
Wrap your components with your Provider
component, as you do with React Context:
<UsersTeamProvider>
<UsersTeam name="Team 1" />
</UsersTeamProvider>
To modify context value, use any of your actions:
import { useUsersTeamContextSelector } from "./usersTeamContext";
const { addUser } = useUsersTeamContext()
const onClick = () => {
addUser({ name: 'John' })
}
You can read context values on the fly if you need. For example, we will create a user with users.length
as id. We can use usersTeamUsersSelector
, but the component would be rerendered every time when any user changes. We don't want that - we need just users
length. We could create a selector that gets users length, but again - everytime we add a user, the component will rerender. For us, it's enough to know users length by the time we create a user:
// get whole context value - it will not cause any rerender!
const contextValue = useUsersTeamContext();
const addNewUser = () => {
// read users array when we need it
const users = contextValue.getValue().users;
// call addUser action to add a new user
contextValue.addUser({ id: users.length + 1, name: userName, score: 0 });
};
FAQs
React Memoized Context React Context with Redux-like performance and patterns without installing Redux.
The npm package @air/react-memoized-context receives a total of 351 weekly downloads. As such, @air/react-memoized-context popularity was classified as not popular.
We found that @air/react-memoized-context demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 19 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
Security News
cURL and Go security teams are publicly rejecting CVSS as flawed for assessing vulnerabilities and are calling for more accurate, context-aware approaches.
Security News
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.