I'll eventually add something here.
diegohaz / constate Goto Github PK
View Code? Open in Web Editor NEWReact Context + State
Home Page: https://codesandbox.io/s/github/diegohaz/constate/tree/master/examples/counter
License: MIT License
React Context + State
Home Page: https://codesandbox.io/s/github/diegohaz/constate/tree/master/examples/counter
License: MIT License
I'm having to use lifecycles often. I think it might be a good idea to implement something like https://github.com/reactions/component.
const increment = amount => state => ({ count: state.count + amount });
const CounterContainer = props => (
<Container
initialState={{ count: 0 }}
onMount={({ setState }) => setState(increment(10))} // componentDidMount
onUpdate={({ setState }) => setState(increment(10))} // setState call
onUnmount={({ setState }) => setState(increment(10))} // componentWillUnmount
actions={{ increment }}
{...props}
/>
);
Currently the actions, effects, selectors and lifecycle methods like onMount
, onUpdate
and onUnmount
only have access to state
and setState
.
Making bound versions of their siblings available within these functions would enable more use-cases and promote code re-use.
The use-case below shows several interesting characteristics:
onMount
handler// one of the actions
const updateRequestStatus = (status: 'loading' | 'loaded' | 'failed') => (state: State) => ({
...state,
requestHistory: [
...state.requestHistory,
{
status,
time: Date.now(),
},
],
});
// one of the selectors
const isLoading = () => ({ requestHistory }: State) => (
requestHistory.length > 0
? requestHistory[requestHistory.length - 1].status === 'loading'
: false
)
// one of the effects
const loadFromServer = (id: string) => ({ actions, selectors, state }) => {
if (selectors.isLoading()) {
return
}
actions.updateRequestStatus('loading');
fetch(`${state.basepath}/my-rest-route/${id}`)
.then(result => actions.updateRequestStatus('loaded'));
}
// the onMount handler
const onMount: OnMount<State> = ({ effects, state }) => {
effects.loadFromServer(state.someId);
}
While the first aspect can be achieved in a different way (encapsulate in external update functions), triggering effects on mount currently requires many contortions to achieve.
Would you be interested in adding these features or would you accept PRs to that extent?
const TodosContainer = props => (
<Container initialState={{ todos: [] }} />
);
const VisibilityContainer = props => (
<Container initialState={{ visibleItems: [] }} />
);
<Container compose={[TodosContainer, VisibilityContainer]}>
{({ todos, visibleItems }) => ...}
</Container>
I'm not able to use the demo, please help.
https://codesandbox.io/s/github/diegohaz/constate/tree/master/examples/counter?module=/App.js
I'm seeing an error:
TypeError
(0 , _react.useState) is not a function
As the title said, can i use this over redux?
Can constate used for big apps?
What limitation constate bring over popular state management like redux?
import { mount } from "constate";
import CounterContainer from "./CounterContainer";
const counter = mount(CounterContainer);
expect(counter.count).toBe(0);
counter.increment(10);
expect(counter.count).toBe(10);
const App = () => (
<Provider onMount={} onUpdate={} onUnmount={}>
...
</Provider>
);
Thanks so much for constate
- wonderful, indeed!
I'm using it in a rather large project and finding that it is tedious to wrap every test with a Provider
. React's use of a default context value in createContext
helps with this normally because then useContext
will get the default, even if no provider is in the tree. Alas, you can't do that with constate
because there's no way use the hook outside of a component.
I'd love to get your thoughts on this... What I'm thinking of is something like this (admittedly big) api change:
// OLD
const counter = useContext(CounterContainer.Context);
// NEW
const counter = CounterContainer.use();
The use()
function does the useContext
subscription, but also knows how to call the
underlying hook instead if the context is empty. This makes writing tests really easy - just like React's createContext
behavior when no provider is present.
There are lots of (mostly unfortunate) issues with this proposal, tho:
Since we use undefined
in the context to signify the no-provider case, container hooks can never return undefined
(seems pretty minor?). Easy to guard for that case.
Since use()
has to call the hook when no provider is given, container hooks cannot have
props anymore (seems pretty major).
It is a bit nasty to conditionally call the hook (use
will call it if no provider is present, otherwise it won't). But this might not be a problem ITRW because you're either running real code (with a provider) or you're running tests (without a provider) and the render tree should be consistent across renders in each of those cases. React will complain if you get this wrong (like conditionally adding/removing providers, which seems super unlikely).
So it ends up like something like this:
function createContainer<V>(
useValue: () => V,
createMemoInputs?: (value: V) => any[]
) {
const Context = createContext<V | undefined>(undefined);
const Provider = (props: { children?: React.ReactNode }) => {
const value = useValue();
if (value === undefined) {
throw new Error('Container hooks must return a value');
}
// createMemoInputs won't change between renders
const memoizedValue = createMemoInputs
? React.useMemo(() => value, createMemoInputs(value))
: value;
return (
<Context.Provider value={memoizedValue}>
{props.children}
</Context.Provider>
);
};
const use = () => {
let value = useContext(Context);
if (value === undefined) {
warnNoProvider();
value = useValue();
}
return value;
};
return {
use,
Provider
};
}
<Container
initialState={{ foo: "foo", bar: "bar" }}
subscription={{ foo: true }}
/>
It will be rendered only if foo
has changed. Inspired by https://github.com/final-form/react-final-form#subscription-fieldsubscription
One of our project's GatsbyJS server side rendering seems to have some issues with finding the shape of state during build.
Example Snippet
export const SharedContainer: ComposableContainer<State, Actions, {}, Effects> = props => {
return <Container initialState={initialState} effects={effects} actions={actions} context="myContextString" {...props} />;
};
Gatsby Build error
error Building static HTML for pages failed
See our docs page on debugging HTML builds for help https://goo.gl/yL9lND
302 | mountContainer = _ref.mountContainer;
303 | return createElement(InnerContainer, _extends({}, _this4.props, {
> 304 | state: state[context],
| ^
305 | setContextState: setContextState,
306 | mountContainer: mountContainer
307 | }));
WebpackError: TypeError: Cannot read property 'myContextString' of undefined
The link in the error describes common pitfalls of SSR in gatsby.
e.g. window
object is missing, either mockup window or wrap the code in a conditional so that it only runs if the object is truthy.
Running the app in dev mode does not have this issue. (Not SSR)
I'm trying to figure out how to get around this. I'd like to not have to mock out the state of the app and just use context as is, but i'm not sure if that is possible at this point.
The following code will just update the component MyView's state. I want to update app's state, but how? If I put an onMount on the app container (context="app") it will never be run since it's not the first being called as by design (and in your documentation).
const onMount = async ({ state, setState }) => {
try {
doSomething()
} catch (error) {
setState({ showSnackbar: true, snackbarMessage: error.msg })
}
}
const ConnectedMyView = props => (
<Container context="myview" onMount={({ state, setState }) => onMount({ state, setState })}>
{() => (
<Container context="app">
{({ foo }) => (
<MyView foo={foo} {...props}/>
)}
</Container>
)}
</Container>
)
React hooks were announced this week and will be available in React 16.7. I'm opening this issue to talk about an addition (or maybe a replacement) on this library's API.
First, it's important to clarify the purpose of this library. A big part of the state of our applications should be local, and some should be shared, contextual or global (whatever people call it). But we don't always know whether a state should be local or shared when first writing it.
Having to refactor local state into Context or Redux later could be cumbersome. Many choose to write global state (using Redux, MobX, React Context etc.) upfront just because some day they may need it.
Constate makes it easier to write local state and make it global later.
And that's why it's called Constate. Context + State.
Provider
Provides state to its children;
import { Provider } from "constate";
function App() {
return (
<Provider devtools>
<Counter />
</Provider>
);
}
useContextState
Same as React's useState
, but accepting a context key argument so it accesses the shared state on Provider
.
import { useContextState } from "constate";
function Counter() {
const [count, setCount] = useContextState("counter1", 0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
useContextReducer
will be available as well.
useContextEffect
Effect that runs on Provider
so it will run only once per context key, not per consumer.
import { useContextState, useContextEffect } from "constate";
function Counter() {
const [count, setCount] = useContextState("counter1", 0);
useContextEffect("counter1", () => {
document.title = count;
}, [count]);
return <div>{count}</div>;
}
Other methods such as useContextMutationEffect
and useContextLayoutEffect
will be available as well.
Context
The Context
object will be exposed so users will be able to useContext
directly to access the whole shared state tree:
import { useContext } from "react";
import { Context } from "constate";
function Counter() {
const [state, setState] = useContext(Context);
const incrementCounter1 = () => setState({
...state,
counter1: state.counter1 + 1
});
return <button onClick={incrementCounter1}>{state.counter1}</button>;
}
createContext
Alternatively, a new context object can be created:
// context.js
import { createContext } from "constate";
const {
Context,
Provider,
useContextState,
useContextEffect
} = createContext({ counter1: 0 });
export {
Context,
Provider,
useContextState,
useContextEffect
};
import { useContextState } from "./context";
function Counter() {
const [count, setCount] = useContextState("counter1");
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
useState
/ useEffect
Given the following example using normal React useState
:
import { useState } from "react";
const CounterButton = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return <button onClick={increment}>{count}</button>;
};
Say we want to share that count state between multiple components. My idea is to export an alternative useState
method so people can pass an additional context
argument to access a shared state.
import React from "react";
// import `useState` from constate instead of react
import { Provider, useState } from "constate";
const CounterButton = () => {
// the only difference here is the additional argument
const [count, setCount] = useState(0, "counter1");
const increment = () => setCount(count + 1);
return <button onClick={increment}>{count}</button>;
};
// Another component that accesses the same shared state
const CounterText = () => {
const [count] = useState(0, "counter1");
return <p>You clicked {count} times</p>;
};
const App = () => (
<Provider devtools>
<CounterText />
<CounterButton />
</Provider>
);
Also, it could be abstracted so as to provide something similar to what we have today:
// useCounter.js
import { useState } from "constate";
function useCounter({ initialState, context } = {}) {
const [state, setState] = useState({ count: 0, ...initialState }, context);
const increment = () => setState(prevState => ({
...prevState,
count: prevState.count + 1
});
return { ...state, increment };
}
export default useCounter;
// Counter.js
import React from "react";
import useCounter from "./useCounter";
const Counter = () => {
const { count, increment } = useCounter({ context: "counter1" });
return (
<div>
<p>You clicked {count} times</p>
<button onClick={increment}>Click me</button>
</div>
);
}
Currently, we have some lifecycle props such as onMount
, onUpdate
and onUnmount
. They serve not only as a nicer way to use lifecycles within functional components, but they also guarantee that they will trigger per context
(if the prop is passed), and not per component.
Now we can use lifecycles within functions with useEffect
, so Constate doesn't have to intrude on it anymore. But the second problem still persists.
As an example, consider a shared state that would fetch some data from the server in componentDidMount. If the state is shared, this should be triggered only once (when the first component gets mounted, for example), and not once per component that accesses that state.
I'm still not sure how to make it work. Maybe we would need to export an alternative useEffect
to do the job.
useConstate
/useConstateEffect
Instead of useState
and useEffect
, we can just export useConstate
and useConstateEffect
:
import { useConstate, useConstateEffect } from "constate";
const Counter = () => {
const context = "counter1";
const [count, setCount] = useConstate({ initialState: 0, context });
useConstateEffect({
create: () => {
document.title = count;
},
inputs: [count],
context
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={increment}>Click me</button>
</div>
);
};
That's because the signatures can't be the same. When using context
, most of the time we won't set initialState
. Having it as the first argument would require us to write useState(undefined, "context")
all the time, which can be cumbersome.
An object parameter solves this problem. The keys follow the parameter names of the original methods on React codebase.
useConstate
import { useConstate } from "constate";
const Counter = () => {
const { useState, useEffect } = useConstate("counter1");
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
useEffect(() => {
document.title = count;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={increment}>Click me</button>
</div>
);
};
- import { useState, useEffect } from "react";
+ import { useConstate } from "constate";
const Counter = () => {
+ const { useState, useEffect } = useConstate("counter1");
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
useEffect(() => {
document.title = count;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={increment}>Click me</button>
</div>
);
};
useContextState
/useContextEffect
import { useContextState, useContextEffect } from "constate";
const Counter = () => {
const [count, setCount] = useContextState("counter1", 0);
const increment = () => setCount(count + 1);
useContextEffect(
"counter1",
() => {
document.title = count;
},
[count]
);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={increment}>Click me</button>
</div>
);
};
(I was writing some abstractions with the same idea and came across your project, thanks for sharing)
Why do we need selectors? Couldn't we just use a normal function since any state property is available to the render function? <button onClick={() => increment(1)}>{count} {getParity(count)}</button>
Currently if you create a hook with this library and debug it with the React Devtools, the Provider is just called Provider
and the Context is just called Context
(The screenshot is from the CodeSandbox example). This can be confusing if you have many context hooks.
I propose an option, which allows the user to define a name. This name could be used to prefix the Provider and Context. Example:
const useCounterContext = createUseContext(useCounter, { name: 'counter' });
In this case the name of the Provider could be CounterProvider
and of the Context CounterContext
(by using displayName in the library).
If you are interested, I could create a PR for this.
Do you plan creating a HOC like connect
for like dispatching actions on componentDidMount
and having state available on those steps
In my component I need to read global state (context="app") but I also need to update local state, what is the best practice for combining that using constate? Below is an example that isn't working but just to get you some kind of idea what I've tried. Is nesting containers needed here?
(I got another related question about onUnmount also but I think it's better to add that later to not make this more complex than necessary.)
updateFoobar is triggered in MyView and should update local state.
const initialState = {
foobar: false,
}
const effects = {
...
}
const effectsLocal = {
updateFoobar: value => ({ setState }) => {
setState({foobar: value})
},
}
const MyView = props => (
<Container
initialState={initialState}
effects={effectsLocal}>
{(foobar, updateFoobar) => (
<Container context="app" effects={effects}>
{({ globalVar, setState }) => {
return (
<MyView
{...props}
globalVar={globalVar}
updateFoobar={(value) => updateFoobar(value)}
/>
)
}}
</Container>
)}
</Container>
)
So I have
import createContainer from "constate";
export const Form = createContainer(useFormState, value => [value.values]);
function formReducer(state, action) {
switch (action.type) {
case 'add-text':
let newState = {
...state,
report_fields: [...state.report_fields, {value:null, type:'text'}]
}
return newState;
default:
throw new Error();
}
}
function useFormState({ initialValues = {} } = {}) {
const [state, dispatch] = useReducer(formReducer, initialValues);
return { state, dispatch };
}
<Form.Provider initialValues={{report_fields: props.report_fields, report: props.report, team: props.team}}>
<Wizard/>
</Form.Provider>
The Wizard does this
function Wizard() {
const { step, next, previous } = useContext(Step.Context);
const steps = [StepOneQuestions, StepTwoMembers];
return React.createElement(steps[step]);
}
And in StepTwo
export default function StepOneQuestions (props) {
const {state, dispatch} = useContext(Form.Context);
return ( .... )
}
That StepOneQuestions is not re-rendering when dispatching an action such as add-text
Since effects
and lifecycles have no way to access the updated state in setState
callbacks (using this.state
), we should provide it.
const increment = () => ({ setState }) => {
setState(state => ({ count: state.count + 1 }), newState => ...)
}
Another option would be deprecate state
prop in favor of a new getState
.
Just wonder if this is typable. Project is 👍
Hello there, I think we still need a helper HOC for applying the container provider named withProvider/withContainer/withContainerProvider
or whatever. Otherwise, we always have to modify some component's parent to add such codes <Container.Provider>....</Container.Provider>
and when we decide to remove a context
we have to modify its parent again and again.
I have a request that adding the HOC helper:
import React, { useState, useContext, useEffect } from "react";
import createContaine, { withProvider }r from "constate";
import {userCounter, MainCounter} from '../containers/counter'
import { Button, Count } from '../components'
// we wish use context inside the DynamicImportedRoutePage(current componet),
// not only its descendants
function DynamicImportedRoutePage() {
// we may use count context here
const { increment } = useContext(MainCounter.Context);
useEffect(
() => {
increment()
},
[]
)
return (
<>
<Count />
<Button />
</>
);
}
// Use the HOC
export default withProvider(MainCounter)(DynamicImportedRoutePage)
When we decide to refactor the DynamicImportedRoutePage
, just remove/change the HOC and update the function body
Note: this issue refers to v1 alpha
I saw that you wraps useContextState
inside a useContextReducer
, but this has a problem. `React.useState and React.useReducer handle lazy initialization differently.
React.useState
accept initialState
as a function to be computed once, but React.useReducer
doesn't. It uses a initialAction argument instead for perform lazy initialization.
Because of this, it's currently not working, since initialState argument is passed to React.useReducer that does not handle initialState as function.
useContextState('context-state', () => someExpensiveComputation(props))
(following up on your tweet)
to simplify things further, I'd say that the Container
terminology is unnecessary and the API would be nicer if it aligned with React's default context/hook API
so have createContext
(rather than createContainer
) return { Provider, Consumer}
, and pass it to the hook as useContext(MyContext)
in other words, constate is just a tiny wrapper around context to faciliate usage n improve performance without a custom API (at least this I'm thinking of doing in my app, using your code as an example, so thanks!)
I'm trying to utilize Constate to manage a Gatsby application.
I have my gastby-ssr.js
and gatsby-browser.js
files set up properly with a provider:
gatsby-ssr:
import React from "react";
import { Provider } from "constate";
import { renderToString } from "react-dom/server";
export const replaceRenderer = ({ bodyComponent, replaceBodyHTMLString }) => {
const app = () => <Provider devtools>{bodyComponent}</Provider>;
replaceBodyHTMLString(renderToString(<app />));
};
gastby-browser:
import React from 'react';
import { Provider } from 'constate';
export const ConnectedRouterWrapper = ({ children }) => {
<Provider devtools={true}>
{children}
</Provider>
};
This is the container that I'm trying to use:
import * as React from 'react';
import { Container } from 'constate';
//declare and export the initial state for Constate to start with
export const initialState = {
count: 0
};
//actions, or syncronous functions that will update the shared state
export const actions = {
increment: amount => state => ({ count: (parseInt(state.count) + 1) })
};
//effects, or asyncronous functions that will update the shared state
export const effects = {
// increment: amount => state => ({ count: state.count + amount })
};
//export the container itself
const MainContainer = (props) => {
return <Container initialState={initialState} effects={effects} actions={actions} context="main" {...props} />;
};
export default MainContainer;
and here is how I'm rendering the "MainContainer" component:
{/* Component 1 */}
<MainContainer context="main">
{({ count, increment }) => (
<button onClick={increment}>{count}</button>
)}
</MainContainer>
{/* Component 2, which isn't updating */}
<MainContainer context="main">
{({ count, increment }) => (
<button onClick={increment}>{count}</button>
)}
</MainContainer>
When I click on "Component 1" it calls the increment function properly, but "Component 2" isn't re-rendered with the updated state like "Component 1" is
Was going to go through some local dev cycles with npm link to further troubleshoot #38 and ran into some issues building constate locally.
Env
OSX 10.13.6
node v8.11.2
npm v6.4.0
Steps to reproduce
run npm i
run npm run build
Error Output
> [email protected] prebuild /directory/constate
> npm run clean
> [email protected] clean /directory/constate
> rimraf dist
> [email protected] build /directory/constate
> tsc --emitDeclarationOnly && rollup -c
src/index.ts → dist/constate.cjs.js, dist/constate.es.js...
(node:8176) Warning: N-API is an experimental feature and could change at any time.
[!] Error: Unexpected token
src/types.ts (3:7)
1: import * as React from "react";
2:
3: export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
^
4:
5: export type Dictionary<T> = { [key: string]: T };
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] build: `tsc --emitDeclarationOnly && rollup -c`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /directory/.npm/_logs/2018-08-28T19_41_13_671Z-debug.log
Starting to wonder if it's just me 😅
Since that's an experimental feature, it should be removed from the main package and imported like import mount from "constate/mount"
Hello.
I read in the documentation that
Constate exports a single method called createContainer. It receives two arguments: useValue and createInputs (optional). And returns { Context, Provider }.
So, without too much insight into hooks
, I assumed that the Provider
returned was interchangeable with Reacts Context.Provider
. Therefor i tried using it as a static Class.contextType. -This does not work, as the Provider
returned is just a proxy
that warns "[constate] Missing Provider".
Reading the closed RFC: Hooks issue, I am wondering if what I am trying to achieve (working with a huge legacy code base), is doable with Constate?
I am sorry for the vague question, I am racing through my options due to timelimits, trying to gather as much intel as possible.
In current version (1.0.0) type declarations are published under dist/ts/src
and when importing for example by the default VS Code action, the result is:
Besides unfancy look, it leads to error during compilation
Module not found: Error: Can't resolve 'constate/dist/ts/src' in 'C:\source\project\src'
...
Solution:
import { useState, useMemo } from "react";
import createUseContext from "constate";
function useCounter() {
const [count, setCount] = React.useState(0);
const increment = () => setCount(count + 1);
return { count, increment };
}
// const useCounterContext = createUseContext(useCounter, value => [value.count]);
const useCounterContext = createUseContext(() => {
const value = useCounter();
return useMemo(() => value, [value.count]);
});
in other words, for a consumer to select which inputs from state it'd like to trigger rendering for
Hi,
as I can see in:
https://github.com/diegohaz/constate/blob/master/src/Container.tsx#L152
there is no cache for selectors and every time is rendered it maps actions
, effect
, selectors
. I think that large amount of actions in bigger projects and mapping them inside each <Container>
render cycle will dramatically decrease overall app performance.
So question is:
For now - project is unusable for project that are larger than Todo app
.
const onUpdate = ({ prevState, state, setState, type }) => {
// type will be action name, effect name or lifecycle prop name
}
constate/src/createContext.tsx
Line 72 in fc304ce
Do not render nor call onUpdate
on setState({ interval })
.
const initialState = { count: 0 };
const onMount = ({ setState }) => {
const fn = () => setState(state => ({ count: state.count + 1 }));
const interval = setInterval(fn, 1000);
setState({ interval });
};
const onUnmount = ({ state }) => {
clearInterval(state.interval);
};
const shouldUpdate = ({ state, nextState }) => state.interval === nextState.interval;
const Counter = () => (
<Container
initialState={initialState}
onMount={onMount}
onUnmount={onUnmount}
shouldUpdate={shouldUpdate}
>
{({ count }) => <button>{count}</button>}
</Container>
);
Could you elaborate and add an example in the documentation where we can prevent the components from rendering if they are only calling out the functions.
Going with the count
and Increment
examples.
It is ok to re-render a component which is using count and displaying it if count changes.
But should we really be re-rendering the component which is only calling the increment
function. The component calling the function does not care what the new state is, all it is interested is in calling the function.
More examples on these lines would be great, since I could not find more documentation besides the example on the git page.
Great library and idea. This is exactly what I was looking for, ability to create hooks and then share state globally using context !!! I started doing the work and mid way found your library, so much appreciate it.
const CounterContainer = props => (
<Container
initialState={{ count: 0 }}
actions={{ increment: () => state => ({ count: state.count + 1 }) }}
{...props}
/>
);
const withContainer = (Container, containerProps) => WrappedComponent => props => (
<Container {...containerProps}>
{state => <WrappedComponent {...props} {...state} />}
</Container>
);
const Counter = ({ count, increment }) => (
<button onClick={increment}>{count}</button>
);
export default withContainer(CounterContainer)(Counter);
Component which are using the context provider will recalled on every state change. I’m aware that only DOM elements that really changed will get re-rendered but that is still a problematic issue because many components often have functions in them and they will be re-executed.
Did you tackle this issue?
I'm thinking on a way to handle side effects and/or async actions on constate. This is how I'm currently planning to use it:
const effects = {
createPost: payload => async ({ state, setState, props }) => {
try {
const data = await api.post('/posts', payload)
setState({ data })
} catch (error) {
setState({ error })
}
}
}
Possible names:
Since the last version, when a Container has context
, we're ignoring initialState
after it's been set. We should warn when someone tries to set further initialState
.
i see that like any usage of contextAPI, each child that is under the provider is getting rendered.
i found that if i wrap the functions that are under that provider with React.memo it will prevent unneeded renders
was wondering what you think about this, did constate handle this issue? i could not find in the docs.
This is the code change:
const Button = React.memo(() => {
// 3️⃣ Use container context instead of custom hook
// const { increment } = useCounter();
const { increment } = useContext(CounterContainer.Context);
return <button onClick={increment}>+</button>;
})
const Count() = React.memo(() => {
// 4️⃣ Use container context in other components
// const { count } = useCounter();
const { count } = useContext(CounterContainer.Context);
return <span>{count}</span>;
}
Also, props
should behave consistently between Container
and ContextContainer
const tick = ({ setState, props }) => {
if (props.foo === "bar") {
setState({ foo: "bar" });
}
};
How can you test a component that uses a Constate Container
?
I've found some issues testing child-as-function with Enzyme. Do you have any strategy for it?
What do you think of this API? It's a very simple change to the library, but personally I find it more elegant.
import React, { useState, useContext } from "react";
import createContainer from "constate";
// 1️⃣ Create a custom hook as usual
const useCounter = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(prevCount => prevCount + 1);
return { count, increment };
}
// 2️⃣ When you need to share your state, simply wrap your hook
// with the createContainer higher-order hook, like so:
const useCounter = createContainer(() => {
// ... same logic here
});
function Button() {
// 3️⃣ Use the same hook like you were before, with the same api. No change.
const { increment } = useCounter()
return <button onClick={increment}>+</button>;
}
function Count() {
// 4️⃣ But now you can use it in other components as well.
const { count } = useCounter()
return <span>{count}</span>;
}
function App() {
// 5️⃣ The caveat, you wrap your components with the Provider that is
// attached to the hook
return (
<useCounter.Provider>
<Count />
<Button />
</useCounter.Provider>
);
}
The change in the code is roughly this (the last 3 lines specifically):
export function createContainer(useValue, createMemoInputs) {
const Context = React.createContext()
const Provider = props => {
const value = useValue(props)
const { children } = props
const memoizedValue = createMemoInputs ? React.useMemo(() => value, createMemoInputs(value)) : value
return <Context.Provider value={memoizedValue}>{children}</Context.Provider>
}
const hook = () => useContext(Context)
hook.Provider = Provider
return hook
}
Is it an anti-pattern to cache the setContextState
function for subsequent use by a non-React-Context component?
For example, this approach appears to work in practice:
// Non-context module
export const onMount = ({ setContextState }) => {
cachedSetContextState = setContextState;
};
Which can be registered with a <Provider>
:
<Provider onMount={onMount} />
So that later on in the app the cached context state can be accessed to update the context state by a component that isn't otherwise bound into the React context:
// In the same non-context module as above
// Has access to the cached `cachedSetContextState` function
cachedSetContextState('context', { foo: 'bar' });
Is this a bad idea?
What I think I trying to achieve is something like channels in redux-saga
, i.e. to be able to mutate the state from a non-GUI source.
after installing 'eslint-plugin-react-hooks', a warning is reported saying:
'React.useMemo' is called conditionally
in this line,
const memoizedValue = createMemoInputs
? React.useMemo(() => value, createMemoInputs(value))
: value;
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.