Global state management and Next.js - reactjs

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.

Related

Is it okay to change the name of the custom React Hook in 3rd party library?

There is a rule that react hooks should not be used inside conditional statements. However, I found that if I rename the function when importing it, this rule doesn't apply. Using zustand, a third-party library, I avoid this rule as follows, but I haven't found any errors yet. When I create a store function in zustand it seems to be implicitly named useStore, but is it ok to rename it to something like this to avoid the rules of react hooks? Here is some code examples.
// zustandModule.js
import create from 'zustand'
const useStore = create(set => ({
bears: 0,
increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
}));
// App.jsx
import { useStore } from '../zustandModule';
export default function App({ someBoolean }) {
const count = someBoolean ? useStore((state) => (state.bears)) : 1;
// ^
// Error: React Hook "useStore" is called conditionally. React Hooks must be called in the exact same order in every component render.
return <div>count: {count}</div>
}
No error
// App.jsx
import { useStore as getStore } from '../zustandModule';
export default function App({ someBoolean }) {
const count = someBoolean ? getStore((state) => (state.bears)) : 1;
return <div>count: {count}</div>
}
FWIW, I have a project where I am using Zustand, and I have three stores to store three different types of data. (Note:) I typically end up calling them like the following in my components, for example:
const themeClasses = themeStore((state) => state.themeClasses);
This doesn't raise any errors, and I think it's probably fine.
I am not saying this is a best practice, though. In fact there's a good chance its awful practice, but this is the first time I've been working on a project of this complexity in React and I didn't think I could wrap my head around Redux in time to stay on budget. However, I do keep to the rules of hooks: I only call them inside components, I don't call them conditionally, and I call them from the top. You definitely should not be using a hook rename to get around rules, you'll just end up with code that produces unexpected effects that will take longer to debug.

React - Hooks + Context - Is this a good way to do global state management?

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.

Fully react state management

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.

React Context API - persist data on page refresh

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.

Context API consume from anywhere

I am trying to implement a shared state into my application using the React context api.
I am creating an errorContext state at the root of my tree. The error context looks like so:
// ErrorContext.js
import React from 'react';
const ErrorContext = React.createContext({
isError: false,
setError: (error) => {}
});
export default ErrorContext;
Desired Result
I would like to update (consume) this context from anywhere in the app (specifically from within a promise)
Ideally the consume step should be extracted into a exported helper function
Example Usage of helper function
http.get('/blah')
.catch((error) => {
HelperLibrary.setError(true);
})
Following the react context docs:
I can create a provider like so :
class ProviderClass {
state = {
isError: false,
setError: (error) => {
this.state.isError = error;
}
}
render() {
return (
<ErrorContext.Provider value={this.state}>
{this.props.children}
</ErrorContext.Provider>
)
}
}
Then I can consume this provider by using the Consumer wrapper from inside a render call:
<ErrorContext.Consumer>
{(context) => {
context.setError(true);
}}
</ErrorContext.Consumer>
The Problem with this approach
This approach would require every developer on my team to write lots of boilerplate code every-time they wish to handle a web service error.
e.g. They would have to place ErrorContext.Consumer inside the components render() method and render it conditionally depending on the web service response.
What I have tried
Using ReactDOM.render from within a helper function.
const setError = (error) =>{
ReactDOM.render(
<ErrorContext.Consumer>
// boilerplate that i mentioned above
</ErrorContext.Consumer>,
document.getElementById('contextNodeInDOM')
) }
export default setError;
Why doesn't this work?
For some reason ReactDOM.render() always places this code outside the React component tree.
<App>
...
<ProviderClass>
...
<div id="contextNodeInDOM'></div> <-- even though my node is here
...
</ProviderClass>
</App>
<ErrorContext.Consumer></ErrorContext.Consumer> <-- ReactDOM.render puts the content here
Therefore there is no context parent found for the consumer, so it defaults to the default context (which has no state)
From the docs
If there is no Provider for this context above, the value argument
will be equal to the defaultValue that was passed to createContext().
If anyone can assist me on my next step, I am coming from Angular so apologies if my terminology is incorrect or if I am doing something extremely stupid.
You can export a HOC to wrap the error component before export, eliminating the boilerplate and ensuring that the context is provided only where needed, and without messing with the DOM:
// error_context.js(x)
export const withErrorContext = (Component) => {
return (props) => (
<ErrorContext.Consumer>
{context => <Component {...props} errorContext={context} />}
</ErrorContext.Consumer>
)
};
// some_component.js(x)
const SomeComponent = ({ errorContext, ...props }) => {
http.get('/blah')
.catch((error) => {
errorContext.setError(true);
})
return(
<div></div>
)
};
export default withErrorContext(SomeComponent);
Now that React 16.8 has landed you can also do this more cleanly with hooks:
const SomeComponent = props => {
const { setError } = useContext(ErrorContext)
http.get("/blah").catch(() => setError(true))
return <div />
}
Following the react context docs:
I can create a provider like so :
class ProviderClass {
state = {
isError: false,
setError: (error) => {
this.state.isError = error;
}
}
I don't think so - there should be setState used. There is a general rule in react "don't mutate state - use setState()" - abusing causes large part of react issues.
I have a feeling you don't understand context role/usage. This is more like a shortcut to global store eliminating the need of explicitly passing down props to childs through deep components structure (sometimes more than 10 levels).
App > CtxProvider > Router > Other > .. > CtxConsumer > ComponentConsumingCtxStorePropsNMethods
Accessing rendered DOM nodes with id is used in some special cases, generally should be avoided because following renders will destroy any changes made externally.
Use portals if you need to render sth somewhere outside of main react app html node.

Resources