I'm trying to make a context in react that will hold some values as defined by an interface (Client in the example below). My problem is that it requires me to make that field nullable in the state interface (ClientState), meaning I would have to check for null values everywhere I consume the context. I want to avoid that.
Example code:
interface Client {
value1: string,
value2: number
}
interface ClientState {
client?: Client
}
const initialState: ClientState = {
client: undefined
}
const ClientContext = React.createContext<ClientState>(initialState);
export const useClient = (): ClientState => useContext(ClientContext);
export const EmployeeContextProvider = ({ children }: PropsWithChildren<{}>) => {
const [state, setState] = useState({});
// abstracted, not relevant to this problem
const loadFiles = () => {
setState(
{
value1: "test",
value2: 1
}
)
}
useEffect(() => loadFiles(), []);
return (
<ClientContext.Provider value={state}>
{children}
</ClientContext.Provider>
)
}
So far I've tried and deemed unsatisfactory:
Giving the client field in initialState a dummy object. The problem with this is that the real version of this has a large number of these fields, meaning lots of dummy code.
Adding a check to useClient() for members of the Client interface, same problem as 1.
Also of semi-relevance is that I don't need to modify this context beyond initialization and it's perfectly fine for it to be read-only.
client needs to be optional because its value is set from state which is initialised to an empty object. The type of state is inferred as ClientState.
useState<ClientState>({}) would require all properties of ClientState to be optional.
You could force TypeScript to accept an empty (dummy) object as if it complied with ClientState using useState({} as ClientState) but that means your Provider really will be providing an unusable client until setState has been invoked with a real one.
But that seems to be the problem you would prefer, over checking for null/undefined each time you wish to make use of the client...
TypeScript is perhaps saving you from yourself here. If your client really can be undefined then you should check it every time you use it!
I think this does what you want.
Your ClientState type seemed to serve only to allow the client to be undefined, which you said you did not want, so I have assumed you would rather not have this. Also, it was conflicting with your setState call where you are setting a Client rather than a ClientState.
This allows for a null Client in state, but NOT in the Context. A guard makes sure the Context and its children are not rendered until the client is set.
import React, { PropsWithChildren, useContext, useEffect, useState } from "react";
interface Client {
value1: string,
value2: number
}
// Note: ClientContext is initialised with an unusable Client object
const ClientContext = React.createContext<Client>({} as Client);
export const useClient = (): Client => useContext(ClientContext);
export const EmployeeContextProvider = ({ children }: PropsWithChildren<{}>) => {
// We allow state to be set to null
const [state, setState] = useState<Client | null>(null);
// abstracted, not relevant to this problem
const loadFiles = () => {
setState(
{
value1: "test",
value2: 1
}
)
}
useEffect(() => loadFiles(), []);
// Guard against null so that state can be provided as a Client
return (
state != null ?
<ClientContext.Provider value={state} >
{children}
</ClientContext.Provider>
: null
)
}
Related
I am implementing a hook "useUserPosts", which is supposed to be used in several routes of my application.
As I already have a context "PostsContext" which re-renders my Cards when data changes (totalLikes, totalComments, descriptions, ...), I have decided to avoid creating another one called "UserPostsContext" which purpose is to return the user posts array.
I know, why not to use the PostsContext instead?...
The answer is that, in PostsContext, to avoid performance issues, I am storing a map (key, value), in order to get/update the posts dynamic data in O(1), something which is only useful in my components (so, it is used to synchronize Cards basically)
Is it possible/a common practice in React to create hooks that handles global states without using the Context API or Redux?
I mean, something like
// Global State Hook
const useUserPosts = (() => {
const [posts, setPosts] = useState({});
return ((userId) => [posts[id] ?? [], setPosts]);
})();
// Using the Global State Hook
function useFetchUserPosts(userId) {
const [posts, setPosts] = useUserPosts(userId);
const [loading, setLoading] = useState(!posts.length);
const [error, setError] = useState(undefined);
const cursor = useRef(new Date());
const hasMoreToLoad = useRef(false);
const isFirstFetch = useRef(true);
const getUserPosts = async () => {
// ...
}
return { posts, loading, error, getUserPosts };
}
Note: my purpose with this is to:
1. Reproduce some kind of cache
2. Synchronize the fetched data of each stack screen that is mounted in order to reduce backend costs
3. Synchronize user posts deletions
Even if I think creating a new global state is the best solution, if you really want to avoid it, you could create your own as follow :
export class AppState {
private static _instance: AppState;
public state = new BehaviorSubject<AppStateType>({});
/**
* Set app state without erasing the previous values (values not available in the newState param)
* */
public setAppState = (newState: AppStateType) => {
this.state.next({ ...this.state, ...newState });
};
private constructor() {}
public static getInstance(): AppState {
if (!AppState._instance) {
AppState._instance = new AppState();
}
return AppState._instance;
}
}
With this kind of type :
export type AppStateType = {
username?: string;
isThingOk?: boolean;
arrayOfThing?: Array<MyType>;
...
}
And use it this way :
const appState = AppState.getInstance();
...
...
appState.setAppState({ isThingOk: data });
...
...
appState.state.subscribe((state: AppStateType) => {// do your thing here});
Not sure this is the best way to create a state of your own but it works pretty well. Feel free to adapt it to your needs.
I can recommand you to use some light state management library as zustand: https://github.com/pmndrs/zustand.
You can with this library avoid re-render and specify to re-render just want the data you want change or change in certain way with some function to compare old and new value.
I want to access a local array file, filter it, and store the filtred array as a state, finally pass the state to child component.
// array file
const originalData =
[
{ "ComName":"A", "SDGs":"1", "No":1 },
{ "ComName":"B", "SDGs":"2", "No":2 },
...
]
I use getComany() and filterCompany() to get the filtred data, and store it in filtredCompany as a state. But it turns out TypeError: Cannot use 'in' operator to search for 'length' in null.
// Services.js
export function getCompany() {
const companyList = originalData;
return companyList;
}
export function filterCompany(comType) {
// filter detail
let companyList = getCompany();
let filtredMatchCompany = getCompany().filter(type => type.SDGs === comType && type.No == comType);
let matchCom = _.map(filtredMatchCompany, 'ComName');
let filtredCompany = companyList.filter((type)=> matchCom.includes(type.ComName))
// Filter.js
export default function Filter() {
const [filtredCompany,setFiltredCompany] = useState(null);
useEffect(() => {
setFiltredCompany(getCompany());
}, []);
function handleD3(e) {
let typeCompany = e.target.value;
typeCompany !== "all"
? setFiltredCompany(filterCompany(typeCompany))
: setFiltredCompany(getCompany());
}
const outputFilter = filtredCompany;
return (
<>
{buttons &&
buttons.map((type, series) => (
<>
<button key={series} value={type.no} onClick={handleD3}>
{type.name}
</button>
</>
))}
<>
<ObjectD3 outputFilter = {filtredCompany}/>
</>
</>
}
The error might come from initial state null. I try to fix that, and change the initial state to
const [filtredCompany,setFiltredCompany] = useState([]) . TypeError doesn't occur, but setState, which is setFiltredCompany , doesn't work anymore.
const [filtredCompany,setFiltredCompany] = useState(null);
console.log(filtredCompany)
// *click button*
// the filtred data I want
const [filtredCompany,setFiltredCompany] = useState([]);
console.log(filtredCompany)
// *click button*
// []
Does anyone know how to handle this situation or better idea to pass data? Thank you so much in advanced!
Source code here
Let go through a part of your code here.
First of all, for a React Component, the lifecycle methods are executed in the following order: constructor -> render -> componentDidMount.
Within the Filter component, you are setting initial state like this:
useEffect(() => {
setFiltredCompany(getCompany());
}, []);
Now, one thing to remember is all the setState() functions and the useEffect hook, are asynchronous, that is, they complete their execution at some time in the future. So, when React renders your app, by the time ObjectD3 component is rendered, the useEffect hook has not executed, so
the ObjectD3 receives null as a prop and the statement in ObjectD3
this.dataset = this.props.outputFilter;
assigns null to the dataset, thereby giving you the error.
A better way to do it, is to implement another lifecycle method in ObjectD3, named componentDidUpdate, where you can compare the changes in props, since the update and take necessary actions.
Check the updated version of code here.
I'm using Typescript and React in this project. The TS type of the application state is a simple object with data, but the object I want pass to the Context.Provider value prop is a different shape; the state is stuffed into it's own prop, and a bunch of function handlers to update state are included.
Typescript is mad because I initialize a new Context with a values-only state, but the value I provide to the Contest.Provider includes functions and stashes the state behind it's own property.
Typescript Types:
// Application State
// Data ONLY, no handlers. This can be serialized and saved between sessions if needed
export interface IAppContext {
user: IUserData;
sidemenu: {
visible: boolean;
}
}
// Context Provider Value
// State (from above) is in it's own property; also included are handlers for updating state
export interface IContextValue {
globalState: IAppContext;
updateUserData: (value: IUserData) => void;
toggleSideMenu: () => void;
}
React Global Context Component
// default state values
export const defaultAppContext {
user: {
email: '',
id: ''
},
sidemenuIsVisible: false
}
// create context
export const AppContext = createContext(defaultAppContext);
// use in component
export const GlobalContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(appReducer, defaultAppContext);
const contextValue: IContextValue = {
globalState: state,
updateUserData: (payload) => dispatch({ type: ACTIONS.UPDATE_USER, payload }),
toggleSideMenu: () => dispatch({type: ACTIONS.TOGGLE_SIDEMENU,})
};
return <AppContext.Provider value={contextValue}>{children}</AppContext.Provider>;
};
Errors
Error: Type 'IContextValue' is missing the following properties from type 'IAppState': sidemenuIsVisible, user
Error: The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps<IAppState>'
The TS error is when I pass contextValue into the Provider value prop. When the context was initialized, it had a type of IAppContext, but the value I want to provide to any context consumers should include state AND handlers, so the value should be of type IIContextValue.
How can I initialize React Context with IAppContext consisting of only data, but then pass a value of type IContextValue to any consumers of the context?
CodeSandbox Demo
Review the files in /src/context
Possible workaround - I do this to work around the issue, but I don't it's the best solution.
export const AppContext = createContext((defaultAppContext as unknown) as IContextValue);
I'm finding these two pieces of the React Hooks docs a little confusing. Which one is the best practice for updating a state object using the state hook?
Imagine a want to make the following state update:
INITIAL_STATE = {
propA: true,
propB: true
}
stateAfter = {
propA: true,
propB: false // Changing this property
}
OPTION 1
From the Using the React Hook article, we get that this is possible:
const [count, setCount] = useState(0);
setCount(count + 1);
So I could do:
const [myState, setMyState] = useState(INITIAL_STATE);
And then:
setMyState({
...myState,
propB: false
});
OPTION 2
And from the Hooks Reference we get that:
Unlike the setState method found in class components, useState does
not automatically merge update objects. You can replicate this
behavior by combining the function updater form with object spread
syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
As far as I know, both works. So, what is the difference? Which one is the best practice? Should I use pass the function (OPTION 2) to access the previous state, or should I simply access the current state with spread syntax (OPTION 1)?
Both options are valid, but just as with setState in a class component you need to be careful when updating state derived from something that already is in state.
If you e.g. update a count twice in a row, it will not work as expected if you don't use the function version of updating the state.
const { useState } = React;
function App() {
const [count, setCount] = useState(0);
function brokenIncrement() {
setCount(count + 1);
setCount(count + 1);
}
function increment() {
setCount(count => count + 1);
setCount(count => count + 1);
}
return (
<div>
<div>{count}</div>
<button onClick={brokenIncrement}>Broken increment</button>
<button onClick={increment}>Increment</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
If anyone is searching for useState() hooks update for object
Through Input
const [state, setState] = useState({ fName: "", lName: "" });
const handleChange = e => {
const { name, value } = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};
<input
value={state.fName}
type="text"
onChange={handleChange}
name="fName"
/>
<input
value={state.lName}
type="text"
onChange={handleChange}
name="lName"
/>
Through onSubmit or button click
setState(prevState => ({
...prevState,
fName: 'your updated value here'
}));
The best practice is to use separate calls:
const [a, setA] = useState(true);
const [b, setB] = useState(true);
Option 1 might lead to more bugs because such code often end up inside a closure which has an outdated value of myState.
Option 2 should be used when the new state is based on the old one:
setCount(count => count + 1);
For complex state structure consider using useReducer
For complex structures that share some shape and logic you can create a custom hook:
function useField(defaultValue) {
const [value, setValue] = useState(defaultValue);
const [dirty, setDirty] = useState(false);
const [touched, setTouched] = useState(false);
function handleChange(e) {
setValue(e.target.value);
setTouched(true);
}
return {
value, setValue,
dirty, setDirty,
touched, setTouched,
handleChange
}
}
function MyComponent() {
const username = useField('some username');
const email = useField('some#mail.com');
return <input name="username" value={username.value} onChange={username.handleChange}/>;
}
Which one is the best practice for updating a state object using the state hook?
They are both valid as other answers have pointed out.
what is the difference?
It seems like the confusion is due to "Unlike the setState method found in class components, useState does not automatically merge update objects", especially the "merge" part.
Let's compare this.setState & useState
class SetStateApp extends React.Component {
state = {
propA: true,
propB: true
};
toggle = e => {
const { name } = e.target;
this.setState(
prevState => ({
[name]: !prevState[name]
}),
() => console.log(`this.state`, this.state)
);
};
...
}
function HooksApp() {
const INITIAL_STATE = { propA: true, propB: true };
const [myState, setMyState] = React.useState(INITIAL_STATE);
const { propA, propB } = myState;
function toggle(e) {
const { name } = e.target;
setMyState({ [name]: !myState[name] });
}
...
}
Both of them toggles propA/B in toggle handler.
And they both update just one prop passed as e.target.name.
Check out the difference it makes when you update just one property in setMyState.
Following demo shows that clicking on propA throws an error(which occurs setMyState only),
You can following along
Warning: A component is changing a controlled input of type checkbox to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
It's because when you click on propA checkbox, propB value is dropped and only propA value is toggled thus making propB's checked value as undefined making the checkbox uncontrolled.
And the this.setState updates only one property at a time but it merges other property thus the checkboxes stay controlled.
I dug thru the source code and the behavior is due to useState calling useReducer
Internally, useState calls useReducer, which returns whatever state a reducer returns.
https://github.com/facebook/react/blob/2b93d686e3/packages/react-reconciler/src/ReactFiberHooks.js#L1230
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
...
try {
return updateState(initialState);
} finally {
...
}
},
where updateState is the internal implementation for useReducer.
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
If you are familiar with Redux, you normally return a new object by spreading over previous state as you did in option 1.
setMyState({
...myState,
propB: false
});
So if you set just one property, other properties are not merged.
One or more options regarding state type can be suitable depending on your usecase
Generally you could follow the following rules to decide the sort of state that you want
First: Are the individual states related
If the individual state that you have in your application are related to one other then you can choose to group them together in an object. Else its better to keep them separate and use multiple useState so that when dealing with specific handlers you are only updating the relavant state property and are not concerned about the others
For instance, user properties such as name, email are related and you can group them together Whereas for maintaining multiple counters you can make use of multiple useState hooks
Second: Is the logic to update state complex and depends on the handler or user interaction
In the above case its better to make use of useReducer for state definition. Such kind of scenario is very common when you are trying to create for example and todo app where you want to update, create and delete elements on different interactions
Should I use pass the function (OPTION 2) to access the previous
state, or should I simply access the current state with spread syntax
(OPTION 1)?
state updates using hooks are also batched and hence whenever you want to update state based on previous one its better to use the callback pattern.
The callback pattern to update state also comes in handy when the setter doesn't receive updated value from enclosed closure due to it being defined only once. An example of such as case if the useEffect being called only on initial render when adds a listener that updates state on an event.
Both are perfectly fine for that use case. The functional argument that you pass to setState is only really useful when you want to conditionally set the state by diffing the previous state (I mean you can just do it with logic surrounding the call to setState but I think it looks cleaner in the function) or if you set state in a closure that doesn't have immediate access to the freshest version of previous state.
An example being something like an event listener that is only bound once (for whatever reason) on mount to the window. E.g.
useEffect(function() {
window.addEventListener("click", handleClick)
}, [])
function handleClick() {
setState(prevState => ({...prevState, new: true }))
}
If handleClick was only setting the state using option 1, it would look like setState({...prevState, new: true }). However, this would likely introduce a bug because prevState would only capture the state on initial render and not from any updates. The function argument passed to setState would always have access to the most recent iteration of your state.
Both options are valid but they do make a difference.
Use Option 1 (setCount(count + 1)) if
Property doesn't matter visually when it updates browser
Sacrifice refresh rate for performance
Updating input state based on event (ie event.target.value); if you use Option 2, it will set event to null due to performance reasons unless you have event.persist() - Refer to event pooling.
Use Option 2 (setCount(c => c + 1)) if
Property does matter when it updates on the browser
Sacrifice performance for better refresh rate
I noticed this issue when some Alerts with autoclose feature that should close sequentially closed in batches.
Note: I don't have stats proving the difference in performance but its based on a React conference on React 16 performance optimizations.
I find it very convenient to use useReducer hook for managing complex state, instead of useState. You initialize state and updating function like this:
const initialState = { name: "Bob", occupation: "builder" };
const [state, updateState] = useReducer(
(state, updates) => {...state, ...updates},
initialState
);
And then you're able to update your state by only passing partial updates:
updateState({ occupation: "postman" })
The solution I am going to propose is much simpler and easier to not mess up than the ones above, and has the same usage as the useState API.
Use the npm package use-merge-state (here). Add it to your dependencies, then, use it like:
const useMergeState = require("use-merge-state") // Import
const [state, setState] = useMergeState(initial_state, {merge: true}) // Declare
setState(new_state) // Just like you set a new state with 'useState'
Hope this helps everyone. :)
On the React 16 Context doc page, they have examples that look similar to this one:
const defaultValue = 'light'
const SomeContext = React.createContext(defaultValue)
const startingValue = 'light'
const App = () => (
<SomeContext.Provider theme={startingValue}>
Content
</SomeContext.Provider>
)
It seems that the defaultValue is useless because if you instead set the startingValue to anything else or don't set it (which is undefined), it overrides it. That's fine, it should do that.
But then what's the point of the defaultValue?
If I want to have a static context that doesn't change, it would be nice to be able to do something like below, and just have the Provider been passed through the defaultValue
const App = () => (
<SomeContext.Provider>
Content
</SomeContext.Provider>
)
When there's no Provider, the defaultValue argument is used for the function createContext. This is helpful for testing components in isolation without wrapping them, or testing it with different values from the Provider.
Code sample:
import { createContext, useContext } from "react";
const Context = createContext( "Default Value" );
function Child() {
const context = useContext(Context);
return <h2>Child1: {context}</h2>;
}
function Child2() {
const context = useContext(Context);
return <h2>Child2: {context}</h2>;
}
function App() {
return (
<>
<Context.Provider value={ "Initial Value" }>
<Child /> {/* Child inside Provider will get "Initial Value" */}
</Context.Provider>
<Child2 /> {/* Child outside Provider will get "Default Value" */}
</>
);
}
Codesandbox Demo
Just sharing my typical setup when using TypeScript, to complete answer from #tiomno above, because I think many googlers that ends up here are actually looking for this:
interface GridItemContextType {
/** Unique id of the item */
i: string;
}
const GridItemContext = React.createContext<GridItemContextType | undefined>(
undefined
);
export const useGridItemContext = () => {
const gridItemContext = useContext(GridItemContext);
if (!gridItemContext)
throw new Error(
'No GridItemContext.Provider found when calling useGridItemContext.'
);
return gridItemContext;
};
The hook provides a safer typing in this scenario. The undefined defaultValue protects you from forgetting to setup the provider.
My two cents:
After reading this instructive article by Kent C. Dodds as usual :), I learnt that the defaultValue is useful when you destructure the value returned by useContext:
Define the context in one corner of the codebase without defaultValue:
const CountStateContext = React.createContext() // <-- define the context in one corner of the codebase without defaultValue
and use it like so in a component:
const { count } = React.useContext(CountStateContext)
JS will obviously say TypeError: Cannot read property 'count' of undefined
But you can simply not do that and avoid the defaultValue altogether.
About tests, my teacher Kent has a good point when he says:
The React docs suggest that providing a default value "can be helpful
in testing components in isolation without wrapping them." While it's
true that it allows you to do this, I disagree that it's better than
wrapping your components with the necessary context. Remember that
every time you do something in your test that you don't do in your
application, you reduce the amount of confidence that test can give
you.
Extra for TypeScript; if you don't want to use a defaultValue, it's easy to please the lint by doing the following:
const MyFancyContext = React.createContext<MyFancyType | undefined>(undefined)
You only need to be sure to add the extra validations later on to be sure you have covered the cases when MyFancyContext === undefined
MyFancyContext ?? 'default'
MyFancyContext?.notThatFancyProperty
etc
You can set the default values using useReducer hook, then the 2nd argument will be the default value:
import React, { createContext, useReducer } from "react";
import { yourReducer } from "./yourReducer";
export const WidgetContext = createContext();
const ContextProvider = (props) => {
const { children , defaultValues } = props;
const [state, dispatch] = useReducer(yourReducer, defaultValues);
return (
<WidgetContext.Provider value={{ state, dispatch }}>
{children}
</WidgetContext.Provider>
);
};
export default ContextProvider;
// implementation
<ContextProvider
defaultValues={{
disabled: false,
icon: undefined,
text: "Hello",
badge: "100k",
styletype: "primary",
dir: "ltr",
}}
>
</ContextProvider>