1
votes

Comment créer un hook personnalisé avec useState qui peut mettre à jour plusieurs éléments si l'état change?

Prenons l'exemple suivant:

const useCounter = () => {
  const [count, setCount] = useState(0);
  return [ count, setCount ];
};
const Shower = () => {
  const [ value ] = useCounter();

  console.log(value); //stays 0

  return value;
}

const Setter = () => {
  const [ value, setValue ] = useCounter();

  console.log(value); //updates on click

  return <button onClick={() => setValue(value+1)}>
    Add
  </button>
}

const App = () => {
  return (
    <div className="App">
      <Setter />
      <Shower />
    </div>
  );
}

Qu'est-ce que je fais de mal? Je m'attendrais à ce qu'il utilise le même état, peu importe où et combien de fois il est utilisé, et si cet état est mis à jour, il devrait mettre à jour chaque composant qui l'utilise, je pense.

Des suggestions?


1 commentaires

il semble que vous souhaitiez utiliser l'API contextuelle ici.


3 Réponses :


1
votes

useState renvoie une paire de valeur et setter . Un élément de données et un moyen de le modifier, mais chaque fois que vous instanciez un nouveau composant, un nouvel instace de cette paire sera également créé. Les hooks sont un excellent moyen de partager la logique statetul entre les composants, pas l ' state lui-même. Shower get est appelé et une instance de useCounter est créée. Setter est appelé et une nouvelle instance est créée. La structure est la même, l ' état ne l'est pas.

Pour partager l ' état entre les composants, utilisez props , redux ou Context API


0 commentaires

1
votes

C'est ce que l'API de contexte de réaction essaie de résoudre.

const Shower = () => {
  const { count } = useCounter();    
  return count;
}

const Setter = () => {
  const { count, setCount } = useCounter();    
  return <button onClick={() => setCount(count+1)}>
    Add
  </button>
}

const App = () => {
  return (
    <CounterProvider>
      <div className="App">
        <Setter />
        <Shower />
      </div>
    </CounterProvider>
  );
}

useCounter vous fournira désormais le même count et setCount dans chaque composant que vous appelez.

Pour l'utiliser:

const CounterContext = React.createContext({
  count: 0,
  setCount: () => null
})

const CounterProvider = ({ children }) => {
  const [count, setCount] = useState(0);

  return (
    <CounterContext.Provider value={{
      count, setCount
    }}>
      {children}
    </CounterContext.Provider>
  )
}

const useCounter = () => {
  return React.useContext(CounterContext)
};


1 commentaires

Donc, fondamentalement, c'est comme appeler useState dans App, et passer "state" et "setState" comme accessoires aux enfants, n'est-ce pas?



0
votes

Lorsque je partage des choses entre des composants fonctionnels, j'aime utiliser le modèle ci-dessous, c'est la version réutilisable redux-ish de la réponse de Federkun ci-dessus:

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>
// this component should be an ancestor of component sharing state
// note that it works no matter how deep your subcomponents are in the render tree
class SharedStateContextProvider extends React.Component {
    /* static propTypes = {
        sharedContext: PropTypes.object,
        reducer: PropTypes.func,
        initialState: PropTypes.object,
        children: PropTypes.oneOfType([
            PropTypes.node,
            PropTypes.arrayOf(PropTypes.node),
        ]),
    } */

    constructor(props) {
        super(props);
        this.state = {
            contextValue: { state: props.initialState, dispatch: this.handleDispatch },
        };
    }

    handleDispatch = (action) => {
        const { reducer } = this.props;
        const { contextValue: { state: sharedState } } = this.state;
        const newState = reducer(sharedState, action);
        if (newState !== sharedState) {
            this.setState(
              () => ({
                contextValue: { state: newState, dispatch: this.handleDispatch }
              })
            );
        }
    }

    render() {
        const { sharedContext: Context, children } = this.props;
        const { contextValue } = this.state;
        return (
            <Context.Provider value={contextValue}>
                {children}
            </Context.Provider>
        );
    }
}

// the actual shared context
const CounterContext = React.createContext();

// add as much logic as you want here to change the state
// as you would do with redux
function counterReducer(state, action) {
  switch(action.type) {
    case 'setValue':
      return {
        ...state,
        value: action.data
      };
    default:
      return state;
  }
}

// counterContext is a prop so the dependency in injected
const Shower = ({ counterContext }) => {
  // well known redux-ish interface
  const { state, dispatch } = React.useContext(counterContext);

  console.log(state.value);

  return state.value;
}

// counterContext is a prop so the dependency in injected
const Setter = ({ counterContext }) => {
  // well known redux-ish interface
  const { state, dispatch } = React.useContext(counterContext);

  console.log(state.value); //updates on click

  return <button onClick={() => dispatch({ type: 'setValue', data: state.value+1 })}>
    Add
  </button>
}

// the actual shared state
const initialCounterState = { value: 0 };

const App = () => {
  return (
    <div className="App">
      <SharedStateContextProvider
          sharedContext={CounterContext}
          reducer={counterReducer}
          initialState={initialCounterState}
      >
        <Setter counterContext={CounterContext} />
        <Shower counterContext={CounterContext} />
      </SharedStateContextProvider>
    </div>
  );
}

const root = document.getElementById('root');

ReactDOM.render(<App />, root);


0 commentaires