This is not an issue but rather a question.
I wanted to use React solely for my Global state management and pass the todos through useReducer and useContext and I wonder if this is by any means a right way to go. I was called out by a react coder that this way the components rerender when they aren't supposed to but my element inspection shows only the changed component rerenders. Would please guide me as whether or not I can continue developing this way or have to revert back to Mobx or redux or many other third party state manager libraries.
Yes, you can and it's easier than ever thanks to the new hooks API! For very simple things like for instance, a global theme you can just create a context with React.createContext, and useContext.
For a more robust solution, you can actually implement a Flux architecture with a combination of useContext and useReducer. Here's one I made earlier.
// AcmeContext.js
import React, { useReducer, createContext } from 'react'
const AcmeContext = createContext({})
const actions = {
DO_SOMETHING: 'doSomething'
}
const actionCreators = dispatch => ({
updateComment: comment => {
dispatch({
type: actions.DO_SOMETHING,
payload: comment
})
}
})
// first paramter is your state, second is the action
let reducer = (currentState, { type, payload }) => {
switch (type) {
case actions.DO_SOMETHING:
// important: return a NEW new object for this context, don't change the old currentState
return { ...currentState, hello: payload }
default:
return
}
}
// this component wraps any of the child components that you want to share state with
function AcmeProvider({ children, initialState }) {
const [state, dispatch] = useReducer(reducer, initialState)
const actions = actionCreators(dispatch)
return (
<AcmeContext.Provider value={{ state, actions }}>
{children}
</AcmeContext.Provider>
);
}
export { AcmeContext, AcmeProvider }
Then, you wrap the component you want to provide the context to with the exported provider.
// App.jsx
import { AcmeProvider } from './AcmeContext'
import TestComponent from './TestComponent'
render((
<AcmeProvider initialState={{ hello: 'world' }}>
<TestComponent />
</AcmeProvider>
), document.querySelector('.app'))
Finally, you can call it from the child component.
// TestComponent.jsx
import { AcmeContext } from './AcmeContext'
export default () => {
const { state, actions } = useContext(AcmeContext)
return (
<div>
Hello {state.hello}!
<button onClick={() => actions.updateComment('me')}>Set response on onClick to 'me'</button>
</div>
)
}
This does have a couple of downsides to a full Redux implementation. You don't get the Redux dev tools and you don't get things like redux-thunk which means you'll have to add that logic to the component and get the component to update the context.
Yes you can totally use the default React APIs for full state management on a project. The introduction of hooks makes it easy to manage. useContext has slowly become my favourite hook because it removes the need for consumers and makes the JSX look a bit nicer.
If you are worried about things rerendering too many times, you can still use all of the tricks in the React Toolbox like React.memo.
Related
So, I'm planning on doing a Facebook clone to add to my portfolio, and I want to use, react-Next.js, node.js, express.js, typeORM and postgresSQL ( everything in typescript ), but i have a big issue with global state managment
The Question: So, I was thinking, and I said, ok, I'm going to use redux, i know how to use it and i love it, but, to implement redux in next.js, seems QUITE HARD, so, i was i said, well, what if i don't need to use a global state managment ? what if i just use the SWR hook and revalidate data whenever i create/update data in the fronted ? and that might be ok, but is that a bad idea? shouldn't i do that ?
My Goal : Everything i need to know, is, is it bad if i only use SWR or should in try my best implementing redux i next.js? i have those options, but i just don't know what to do, with create-react-app setting up redux is easy, but in next.js i just don't get it, so, if you can help me with your answer, i would thank you a lot !!
swr+contextApi is used together to replace redux. This is step by step how you can set it up:
create your different hooks. for example
export const createFirstHook =
({ arg1, arg2 }) =>
() => {
// CONDITIONAL USESWR CALL
// isValidation is true whenever you are retrievnig a new data
const { data, isValidating, ...swr } = useSWR(
// write fetching logic
);
return {
...swr,
isValidating,
data,
};
};
in your app, you will have many hooks and combine them like this. Think of this as the main reducer in redux.
import { createFirstHook} from "./useNetwork";
export const setupHooks = (deps) => {
return {
useFirstHook: createFirstHook(deps),
...
};
};
write your context and include hooks in the returned object.
const Web3Context = createContext(null)
const createWeb3State = ({arg1, arg2}) => {
return {
arg1,
arg2,
// Passing hooks here
hooks: setupHooks({erg1,arg2})
}
}
export default function Web3Provider({children}) {
const [web3Api, setWeb3Api] = useState(
// this function will set the hooks
createWeb3State({
arg1: null,
arg2: null,
})
)
// you add more logic
// eventuallay you return this
return (
<Web3Context.Provider value={web3Api}>
{children}
</Web3Context.Provider>
)
}
this is how you reach the provider data in other parts of your app.
import { useContext } from "react";
const context= useContext(Web3Context);
This will make you import import { useContext } from "react" ewerwhere you need the context. Instead, in this provider file, you import the useContext and export this function
export function useWeb3() {
return useContext(Web3Context);
}
in next.js you have to wrap the _app.js with the provider
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Web3Provider>
<Component {...pageProps} />
</Web3Provider>
</>
);
}
Now you can reach your context and hooks anywhere in your app. In this approach each hook function acts like reducers but good thing is you can use those hooks independently somewhere else in your app.
I am trying to find a good, clean, with little boilerplate, way to handle React's global state
The idea here is to have a HOC, taking advantage of React's new Hooks & Context APIs, that returns a Context provider with the value bound to its state. I use rxjs for triggering a state update on store change.
I also export a few more objects from my store (notably : the raw rxjs subject object and a Proxy of the store that always returns the latest value).
This works. When I change something in my global store, I get updates anywhere in the app (be it a React component, or outside React). However, to achieve this, the HOC component re-renders.
Is this a no-op ?
The piece of code / logic I think could be problematic is the HOC component:
const Provider = ({ children }) => {
const [store, setStore] = useState(GlobalStore.value)
useEffect(() => {
GlobalStore.subscribe(setStore)
}, [])
return <Context.Provider value={store}>{children}</Context.Provider>
}
GlobalStore is a rxjs BehaviorSubject. Every time the subject is updated, the state of the Provider component gets updated which triggers a re-render.
Full demo is available there: https://codesandbox.io/s/qzkqrm698q
The real question is: isn't that a poor way of doing global state management ? I feel it might be because I basically re-render everything on state update...
EDIT: I think I have written a more performant version that's not as lightweight (depends on MobX), but I think it generates a lot less overhead (demo at: https://codesandbox.io/s/7oxko37rq) - Now what would be cool would be to have the same end result, but dropping MobX - The question makes no sense anymore
I understand your need to handle a global state. I already found myself in the same situation. We have adopted similar solutions, but in my case, I've decided to completelly drop from ContextAPI.
The ContextAPI really sucks to me. It seems to pretend to be a controller based pattern, but you end up wrapping the code inside an non-sense HOC. Maybe I've missed he point here, but in my opinion the ContextAPI is just a complicated way to offer scoped based data flow.
So, I decided to implement my own global state manager, using React Hooks and RxJS. Mainly because I do not use to work on really huge projects (where Redux would fit perfectly).
My solution is very simple. So lets read some codes because they say more than words:
1. Store
I've created an class only to dar nome aos bois (it's a popular brazilian expression, google it 😊) and to have a easy way to use partial update on BehaviorSubject value:
import { BehaviorSubject } from "rxjs";
export default class Store<T extends Object> extends BehaviorSubject<T> {
update(value: Partial<T>) {
this.next({ ...this.value, ...value });
}
}
2. createSharedStore
An function to instantiate the Store class (yes it is just because I don't like to type new ¯\(ツ)/¯):
import Store from "./store";
export default function <T>(initialValue: T) {
return new Store<T>(initialValue);
}
3. useSharedStore
I created an hook to easily use an local state connected with the Store:
import Store from "./store";
import { useCallback, useEffect, useState } from "react";
import { skip } from "rxjs/operators";
import createSharedStore from "./createSharedStore";
const globalStore = createSharedStore<any>({});
type SetPartialSharedStateAction<S> = (state: S) => S;
type SetSharedStateAction<S> = (
state: S | SetPartialSharedStateAction<S>
) => void;
export default function <T>(
store: Store<T> = globalStore
): [T, SetSharedStateAction<T>] {
const [state, setState] = useState(store.value);
useEffect(() => {
const subscription = store
.pipe(skip(1))
.subscribe((data) => setState(data));
return () => subscription.unsubscribe();
});
const setStateProxy = useCallback(
(state: T | SetPartialSharedStateAction<T>) => {
if (typeof state === "function") {
const partialUpdate: any = state;
store.next(partialUpdate(store.value));
} else {
store.next(state);
}
},
[store]
);
return [state, setStateProxy];
}
4. ExampleStore
Then I export individual stores for each feature that needs shared state:
import { createSharedStore } from "hooks/SharedState";
export default createSharedStore<Models.Example | undefined>(undefined);
5. ExampleComponent
Finally, this is how to use in the component (just like a regular React state):
import React from "react";
import { useSharedState } from "hooks/SharedState";
import ExampleStore from "stores/ExampleStore";
export default function () {
// ...
const [state, setState] = useSharedState(ExampleStore);
// ...
function handleChanges(event) {
setState(event.currentTarget.value);
}
return (
<>
<h1>{state.foo}</h1>
<input onChange={handleChange} />
</>
);
}
GlobalStore subject is redundant. RxJS observables and React context API both implement pub-sub pattern, there are no benefits in using them together this way. If GlobalStore.subscribe is supposed to be used in children to update the state, this will result in unnecessary tight coupling.
Updating glubal state with new object will result in re-rendering the entire component hierarchy. A common way to avoid performance issues in children is to pick necessary state parts and make them pure components to prevent unnecessary updates:
<Context.Consumer>
({ foo: { bar }, setState }) => <PureFoo bar={bar} setState={setState}/>
</Context.Provider>
PureFoo won't be re-rendered on state updates as long as bar and setState are the same.
Let's say we have a context provider set up, along with some initial data property values.
Somewhere along the line, let's say a consumer then modifies those properties.
On page reload, those changes are lost. What is the best way to persist the data so we can retain those data modifications? Any method other than simply local storage?
Yeah, if you want the data to persist across reloads, your options are going to be storing that info server-side (via an api call) or in browser storage (local storage, session storage, cookies). The option you'll want to use depends on what level of persistence you're looking to achieve. Regardless of storage choice, it would likely look something along the lines of
const MyContext = React.createContext(defaultValue);
class Parent extends React.Component {
setValue = (value) => {
this.setState({ value });
}
state = {
setValue: this.setValue,
value: localStorage.getItem("parentValueKey")
}
componentDidUpdate(prevProps, prevState) {
if (this.state.value !== prevState.value) {
// Whatever storage mechanism you end up deciding to use.
localStorage.setItem("parentValueKey", this.state.value)
}
}
render() {
return (
<MyContext.Provider value={this.state}>
{this.props.children}
</MyContext.Provider>
)
}
}
Context doesn't persist in the way you want. Here's a sample of what I've done, using stateless functional with React hooks.
import React, {useState, useEffect} from 'react'
export function sample(){
// useState React hook
const [data, setData] = useState({})
const [moreData, setMoreData] = useState([])
// useState React hook
useEffect(() => {
setData({test: "sample", user: "some person"})
setMoreData(["test", "string"])
}, [])
return data, moreData
}
export const AppContext = React.createContext()
export const AppProvider = props => (
<AppContext.Provider value={{ ...sample() }}>
{props.children}
</AppContext.Provider>
)
Understand from the start that this isa workaround, not a permanent solution. Persisting data is the job of a database, not the client. However, if you need persisted data for development, this is one way. Notice first that I'm using React hooks. This is a fully supported feature as of 16.8. The useEffect() replaces the lifecycle methods found in class declarations like that of TLadd above. He's using componentDidUpdate to persist. The most up-to-date way of doing this is useEffect. When the app is refreshed this method will be called and set some hard-coded data in context.
To use the provider:
import React from 'react'
import Component from './path/to/component'
import { AppProvider } from './path/to/context'
const App = () => {
return (
<AppProvider>
<Component />
</AppProvider>
)
}
When you refresh, data and moreData will still have whatever default values you assign to them.
I am assuming that you are already familiar with setting context and setting up the context provider.
One of the things you can do is to store the value in the browser's Cookie or any storage available to you, and then, in your Context Provider, retrieve the value, if you can find it, you set it, if not, set the default. If the provider file is a class based component, you would like to retrieve this value in the constructor(), otherwise if it is functional, you can use useLayoutEffect() to set it.
The below super simple code is not working as expected.
I am using react and injecting/providing props via redux store. what my understanding is store inject props in the React component.
To make this component work why I need both line1 and line 2 ?
import React from 'react';
import './sytles.css';
import { fetchUsers } from "./actions/userAction"
import { connect } from "react-redux"
#connect((store) => {
return {
signup: store.users,
};
})
class Signup extends React.Component {
handleClick(event) {
this.setState({email: event.target.value}) //line 1
this.props.signup.email = event.target.value; // line 2
}
render() {
return (
<input
type="text"
name="email"
value={this.props.signup.email}
onChange=
{ (event) => this.handleClick(event) }/>
);
}
}
export default Signup;
You can't reassign props -- it's read only even aside from Redux. But to change the Redux store, you dispatch an action. Per the Redux documentation of The Three Principles of Redux:
State is read-only
The only way to change the state is to emit an action, an object describing what happened.
This ensures that neither the views nor the network callbacks will ever write directly to the state. Instead, they express an intent to transform the state. Because all changes are centralized and happen one by one in a strict order, there are no subtle race conditions to watch out for. As actions are just plain objects, they can be logged, serialized, stored, and later replayed for debugging or testing purposes.
You are doing it incorrectly, props must never be mutated directly, also you shouldn't keep a state that is directly derivable from props. Since your data signup is present in store, you need an action creator that updates this value in the store
const handleSignup = (email) => {
return {
type: "SIGNUP_EMAIL",
email
}
}
and dispatch it like
handleClick(event) {
dispatch(handleSignup(event.target.value));
}
The store service from Redux is what ultimately utilized by various components in a React App. The methods (such as dispatch, getState and subscribe) exposed by it are used by all kinds components (like container or presentational).
I think the approach to pass this store service around is an important design decision. I see two approaches and they are:
1) By passing the store as a prop to every component at all nested levels. This is not the recommended one.
2) Use a tool like react-redux, which with the help of context, makes the store (state and dispatch to be exact) available wherever it is needed.
My questions are: Why not simply import the store wherever it is needed. For an SPA-based React App, store will be a singleton. Components nested at any level can simply import the store. Why should we adopt anyone of the above two approaches?
For a component at any nested level: Can we do this
import store from "path/to/store";
let MyComponent = () => {
let state = store.getState();
return (
<div onClick={() => {
store.dispatch({
type: "SOME_EVENT",
payload: store.somedata
});
}}>{state.dataINeedHere}</div>
);
};
export default MyComponent;
instead of
import { connect } from "react-redux";
let MyComponent = ({somedata, onMyAction}) => {
let state = store.getState();
return (
<div onClick={() => {
onMyAction(somedata);
}}>{somedata}</div>
);
};
const mapStateToProps = (state) => {
return {
somedata: state.somedata
}
}
const mapDispatchToProps = (dispatch) => {
return {
onMyAction: (input) => {
dispatch({
type: "SOME_EVENT",
payload: input
});
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
The Redux FAQ covers this question, at http://redux.js.org/docs/FAQ.html#store-setup-multiple-stores.
Summarizing: while you can directly import a store, you're tying your code to that store implementation, which makes it less reusable and harder to test. Ideally, none of your own code actually ever references the store directly. Connected components, middleware, and thunked action creators all receive the relevant dispatch and getState function references by dependency injection, making them reusable and allowing easy mocking of behavior for testing.
Consider what happens when you have a second MyComponent, which manages data from a different part of the store. Your options are to tell each exactly how to access/update its data:
<Container>
<MyComponent path="/a/b/c" />
<MyComponent path="/a/b/d" />
</Container>
or you can give each access to only what it needs:
<Container>
<!-- context /a/b was passed to Container -->
<MyComponent data={c} onUpdate={update(c)} />
<MyComponent data={d} onUpdate={update(d)} />
</Container>
The latter makes the MyComponent much simpler and much more flexible.