
Security News
Risky Biz Podcast: Making Reachability Analysis Work in Real-World Codebases
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
@air/react-memoized-context
Advanced tools
- Use your React Context without the additional re-renders in the consumers - Ability to read values out of context "on-the-fly" - useful in callbacks so you don't have to bind the UI to a context value change just to use the value in a callback - Redux-l
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, have 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 });
};
You can play with the example using codesandbox: https://codesandbox.io/s/boring-morning-j44i9p
FAQs
- Use your React Context without the additional re-renders in the consumers - Ability to read values out of context "on-the-fly" - useful in callbacks so you don't have to bind the UI to a context value change just to use the value in a callback - Redux-l
The npm package @air/react-memoized-context receives a total of 1,024 weekly downloads. As such, @air/react-memoized-context popularity was classified as popular.
We found that @air/react-memoized-context demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 21 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
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.
Security News
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.