If you are referring to component state, then hooks will not help you share it between components. Component state is local to the component. If your state lives in context, then useContext hook would be helpful.
Fundamentally, I think you misunderstood the line "sharing stateful logic between components". Stateful logic is different from state. Stateful logic is stuff that you do that modifies state. For e.g., a component subscribing to a store in componentDidMount() and unsubscribing in componentWillUnmount(). This subscribing/unsubscribing behavior can be implemented in a hook and components which need this behavior can just use the hook.
If you want to share state between components, there are various ways to do so, each with its own merits:
1. Lift State Up
Lift state up to a common ancestor component of the two components.
function Ancestor() {
const [count, setCount] = useState(999);
return <>
<DescendantA count={count} onCountChange={setCount} />
<DescendantB count={count} onCountChange={setCount} />
</>;
}
This state sharing approach is not fundamentally different from the traditional way of using state, hooks just give us a different way to declare component state.
2. Context
If the descendants are too deep down in the component hierarchy and you don't want to pass the state down too many layers, you could use the Context API.
There's a useContext hook which you can leverage on within the child components.
3. External State Management Solution
State management libraries like Redux or Mobx or Zustand. Your state will then live in a store outside of React and components can connect/subscribe to the store to receive updates.
Answer from Yangshun Tay on Stack OverflowIf you are referring to component state, then hooks will not help you share it between components. Component state is local to the component. If your state lives in context, then useContext hook would be helpful.
Fundamentally, I think you misunderstood the line "sharing stateful logic between components". Stateful logic is different from state. Stateful logic is stuff that you do that modifies state. For e.g., a component subscribing to a store in componentDidMount() and unsubscribing in componentWillUnmount(). This subscribing/unsubscribing behavior can be implemented in a hook and components which need this behavior can just use the hook.
If you want to share state between components, there are various ways to do so, each with its own merits:
1. Lift State Up
Lift state up to a common ancestor component of the two components.
function Ancestor() {
const [count, setCount] = useState(999);
return <>
<DescendantA count={count} onCountChange={setCount} />
<DescendantB count={count} onCountChange={setCount} />
</>;
}
This state sharing approach is not fundamentally different from the traditional way of using state, hooks just give us a different way to declare component state.
2. Context
If the descendants are too deep down in the component hierarchy and you don't want to pass the state down too many layers, you could use the Context API.
There's a useContext hook which you can leverage on within the child components.
3. External State Management Solution
State management libraries like Redux or Mobx or Zustand. Your state will then live in a store outside of React and components can connect/subscribe to the store to receive updates.
It is possible without any external state management library. Just use a simple observable implementation:
function makeObservable(target) {
let listeners = []; // initial listeners can be passed an an argument aswell
let value = target;
function get() {
return value;
}
function set(newValue) {
if (value === newValue) return;
value = newValue;
listeners.forEach((l) => l(value));
}
function subscribe(listenerFunc) {
listeners.push(listenerFunc);
return () => unsubscribe(listenerFunc); // will be used inside React.useEffect
}
function unsubscribe(listenerFunc) {
listeners = listeners.filter((l) => l !== listenerFunc);
}
return {
get,
set,
subscribe,
};
}
And then create a store and hook it to react by using subscribe in useEffect:
const userStore = makeObservable({ name: "user", count: 0 });
const useUser = () => {
const [user, setUser] = React.useState(userStore.get());
React.useEffect(() => {
return userStore.subscribe(setUser);
}, []);
const actions = React.useMemo(() => {
return {
setName: (name) => userStore.set({ ...user, name }),
incrementCount: () => userStore.set({ ...user, count: user.count + 1 }),
decrementCount: () => userStore.set({ ...user, count: user.count - 1 }),
}
}, [user])
return {
state: user,
actions
}
}
And that should work. No need for React.Context or lifting state up
Sharing data between React components - javascript
How to share state between hooks?
How can I pass data between sibling components in React?
how to you share state between sibling components?
Videos
There are multiple ways to share data between components. You can use one of the following options:
If the component to which you want to pass the data is a child of
SearchFormcomponent, then you can pass it as aprop.If you are managing state via redux, you can connect components to the redux store and get the required data from the redux store in your components.
You can also use React's
Context APIto share common data between components that may or may not have a parent-child relationship.If the component that needs the data from
SearchFormcomponent, is a parent component ofSearchFormcomponent, you can pass a callback function to theSearchFormcomponent as apropand when data is available inSearchFormcomponent, call the callback function, received as aprop, and pass the data as an argument to that callback function.
Ciao, when I want to share data between components I use React-Redux. Lets make an example: Suppose that you want to share data received by server (nameservers). At first install react-redux:
npm install react-redux
npm install redux
npm install @reduxjs/toolkit
Now we have to create the reducer and the action:
Lets say you have your component in a folder called "/components/MyComponent". Create a file called MyReducer.js.
/components/MyComponent/MyReducer.js
import { createReducer } from '@reduxjs/toolkit';
const initialState = {
nameservers: undefined,
};
const LoginReducer = createReducer(initialState, {
["SET_NAMESERVERS"]: (state, action) => {
state.nameservers= action.payload.nameservers;
},
})
export default MyReducer;
Now, on the same folder, createa file called MyAction.js
/components/MyComponent/MyAction.js
export const setNameServers = data => ({
type: "SET_NAMESERVERS",
payload: { nameservers: data }
});
Then create the store:
On your project root create a folder callled redux. Inside this create a folder called store. Then on this folder create a file called index.js.
redux/store/index.js
import { createStore, combineReducers } from "redux";
import MyReducer from '../../components/MyComponent/MyReducer';
const reducers = combineReducers({
MyReducer,
});
const store = createStore(reducers);
export default store;
Now on index.js file on root folder lets pass the store already created:
index.js
...
import { Provider } from 'react-redux';
import store from "./redux/store";
ReactDOM.render((
<Provider store={store}>
<App />
</Provider>
), document.getElementById('root') || document.createElement('div'));
We have almost done. On your component (MyComponent) you retrieve data from server. Once you have data, lets dispatch data to share into the store:
/components/MyComponent/MyComponent.js
...
import { useDispatch } from 'react-redux';
import { setNameServers } from './MyComponentAction';
const MyComponent: React.FC = () => {
const [nameservers, setNameservers] = useState([]);
const dispatch = useDispatch();
....
const handleSubmit = (event: any) => {
...
fetch(`https://dns.google.com/resolve?name=${domain}&type=NS`)
.then(results => results.json())
.then(data => {
setLoading(false);
if (data && data.Answer) {
data.Answer.sort((a: any, b: any) => a.data.localeCompare(b.data));
setNameservers(data.Answer);
dispatch(setNameServers(data.Answer)); // here the magic
}
});
};
};
Done! now you have nameservers on your react redux store and you can easly get it from another component like this:
OtherComponent.js
import { useSelector } from 'react-redux';
const OtherComponent: React.FC = () => {
const nameservers = useSelector(state => state.MyReducer.nameservers);
};
And if you log nameservers somewhere in OtherComponent you will see data retrieved in MyComponent. Awesome!
I'm creating an Authentication hook and I want developers to be able to use this hook in multiple components, but with a shared state. Is this terrible? If not, how would I share this state between all the hook's instances?