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 :

Edit #SO-redux-connect


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 :

Edit #SO-redux-connect2

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 :

Edit #SO-redux-connect3

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 :

Edit #SO-redux-connect4


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.