Re-render the component when the store state is changed
Here I've created 4 demos, each demo is an extended version of the previous one :
1) Connect the sore and update component via mapStateToProps
2) By Using the useSelector
const boolVal = useSelector(state => state.hasFruit ? state.hasFruit.value : false );
3) Paasing the dynamic name to useSelector
const booleanVal = useSelector(state => booleanSelector(state, "hasFruit"));
4) Created a custom hook, so that you can get the updated value bu just passing the name
const booleanVal = useGetValueFromStore("hasFruit");
The problem is re-rendering of the Heading component is not happening
Reason :
Yes because it's not connected to the store, how does it know that there are some changes going on the store
, you need to call connect
to make a connection with the store and be up to date with changes.
Here is the updated code of the blocks.js
:
// WithHeading Block
const WithHeading = props => {
useEffect(() => {
boolean("hasFruit", true); // <--- Setting initial value
text("fruitName", "Apple"); // <--- Setting initial value
}, []); // <----- get called only on mount
return <Heading hasFruit={props.boolVal} fruitName={props.textVal} />;
};
// to get updated state values inside the component as props
const mapStateToProps = state => {
return {
boolVal: state.hasFruit ? state.hasFruit.value : false,
textVal: state.fruitName ? state.fruitName.value : ""
};
};
// to make connection with store
export default connect(mapStateToProps)(WithHeading);
1) WORKING DEMO :
Another approach is you can use useSelector
:
// WithHeading Block
const WithHeading = props => {
// console.log(props);
const boolVal = useSelector(state =>
state.hasFruit ? state.hasFruit.value : false
);
const textVal = useSelector(state =>
state.fruitName ? state.fruitName.value : ""
);
useEffect(() => {
boolean("hasFruit", true);
text("fruitName", "Apple");
}, []);
return <Heading hasFruit={boolVal} fruitName={textVal} />;
};
export default WithHeading;
2) WORKING DEMO :
You can also put the selector in separate file,so that you can use it whenever you want
const WithHeading = props => {
// you can pass the input names here, and get value of it
const booleanVal = useSelector(state => booleanSelector(state, "hasFruit"));
const textVal = useSelector(state => textValSelector(state, "fruitName"));
useEffect(() => {
boolean("hasFruit", true);
text("fruitName", "Apple");
}, []);
return <Heading hasFruit={booleanVal} fruitName={textVal} />;
};
3) WORKING DEMO :
Custom Hook with use of useSelector
:
// a function that will return updated value of given name
const useGetValueFromStore = name => {
const value = useSelector(state => (state[name] ? state[name].value : ""));
return value;
};
// WithHeading Block
const WithHeading = props => {
//------- all you need is just to pass the name --------
const booleanVal = useGetValueFromStore("hasFruit");
const textVal = useGetValueFromStore("fruitName");
useEffect(() => {
boolean("hasFruit", true);
text("fruitName", "Apple");
}, []);
return <Heading hasFruit={booleanVal} fruitName={textVal} />;
};
export default WithHeading;
4) WORKING DEMO :
There are several ways to handle state in React, and many of those choices are based on complexity and requirements. As mentioned in the comments, Redux is a powerful option. Mobx is a remarkable piece of technology, to name two.
React itself does have capacity to disseminate and respond to these changes without external libs. You might consider using the Context API -
./src/contexts/Store
import React, {
useContext,
useState,
useMemo,
createContext,
useEffect,
} from 'react';
const StoreContext = createContext(null);
const StoreProvider = (props) => {
const [state, setLocalState] = useState({});
function set(objToMerge) {
setLocalState({ ...state, ...objToMerge });
}
function get(k) {
return state[k];
}
function getAll(){
return state;
}
const api = useMemo(() => {get, set, getAll}, []);
return <StoreContext.Provider value={api} {...props}></StoreContext.Provider>;
};
function useStoreContext(): StoreProviderApi {
const api = useContext(StoreContext);
if (api === null) {
throw new Error(
'Component must be wrapped in Provider in order to access API',
);
}
return api;
}
export { StoreProvider, useStoreContext };
to use, you do need a Parent level component -
import {StoreProvider} from './contexts/Store';
...
<StoreProvider>
<PropEditor/>
<WithHeading/>
</StoreProvider>
...
Then, within the component itself, you can access the latest state -
import {useStoreContext} from './contexts/Store';
export const Heading = (props) => {
const store = useStoreContext();
const { hasFruit, fruitName } = store.getAll();
return <h1>Fruit name will show { hasFruit ? fruitName : 'Oh no!'}</h1>
};
This has the benefit of not needing to pass tons of props around, and it will auto-render on change.
The downside, however, is that it will re-render on change. That is, there are no mechanisms for selectively re-rendering only the components with changed props. Many projects have multiple contexts to alleviate this.
If your store props need to be used throughout the app, then Redux (with the toolkit) is a good option, because it is a store outside of React, and it handles broadcasting only the prop changes to the subscribing components for those props, rather than re-rendering all of the subscribers (which is what the Context API does).
At that point, it becomes a question of architecture and what is needed for your application requirements.