3

I am practicing React hooks and encountered an issue with useReducer and dispatch functions. The reducer function that I created is being called twice when I'm clicking on either buttons on the SECOND time onwards. My console log output is printed once when clicking either buttons for the first time, and after that on each button push it's printed twice.

Here are my files:

Utils.js

import React, {createContext, useReducer, useContext} from 'react';

const initialState = {

    text: 0
}

const StoreContext = createContext(initialState);

const reducer = (state, action) => {
    console.log('hello');

    switch(action.type) {
        case 'add-counter': return {
            ...state,
            text: state.text + 1
        }
        default:
            throw new Error();
    }
}

export const StoreProvider = ({children}) => {
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
        <StoreContext.Provider value={{state, dispatch}}>
            {children}
        </StoreContext.Provider>
    )
}

export const useStore = (store) => {
    const {state, dispatch} = useContext(StoreContext);
    return {state, dispatch};
}

UserList.js

import React, {useCallback} from 'react';
import { Row, Col, Button } from 'antd';
import TextDisplayComponent from './TextDisplay';
import {useStore} from '../util';

function ListComponent() {
    const {dispatch} = useStore();
    const increment = useCallback(() => dispatch({type: 'add-counter'}), [dispatch])

    return (
        <Row>
            <Col span={12} style={{textAlign: 'center'}}>
                <Button type="primary" onClick={increment}>Press Me</Button>
            </Col>
            <Col span={12} style={{textAlign: 'center'}}>
                <TextDisplayComponent />
            </Col>
        </Row>
    )
}

export default ListComponent;

TextDisplay.js

import React, {useCallback} from 'react';
import {Button} from 'antd'
import {useStore} from '../util'

function TextDisplayComponent() {
    const {state, dispatch} = useStore();
    const increment = useCallback(() => dispatch({type: 'add-counter'}), [dispatch])

    return (
        <div>
            {state.text}
            <Button onClick={increment}>Add</Button>
        </div>
    )
}

export default TextDisplayComponent

App.js

import React from 'react';
import UserListComponent from './components/UserList';
import 'antd/dist/antd.css';
import {StoreProvider} from './util';

function App() {

  React.createContext()
  return (
    <StoreProvider>
      <div className="App">
        <UserListComponent />
      </div>
    </StoreProvider>
  );
}

export default App;

Can anyone please help? Thanks.

Complete test project can be found at https://github.com/Saro-DA/my-app.git

Saro
  • 810
  • 2
  • 12
  • 22

1 Answers1

3

That's intentional. You're wrapping your app in <React.StrictMode>, which will cause that to happen in development mode.

Please check this:

Another thing that React Strict Mode does is run certain callbacks/methods twice (in DEV mode ONLY). You read that right! The following callbacks/methods will be run twice in Strict Mode (in DEV mode ONLY)

Luís Ramalho
  • 10,018
  • 4
  • 52
  • 67
  • 1
    Thank you for the answer! I tried in production mode and this didn't happen exactly as you mentioned. Thanks again! – Saro Apr 01 '20 at 15:30
  • 1
    Thank you for the info, I had the exact same problem! However, one thing is not clear to me: if you put in a console.log into the reducer, the log is executed just once all the time. How does it work that a code block runs twice but there is just one console log? – Stiegi Oct 01 '21 at 12:21
  • @Stiegi I have the same concerns :/ but I found this link: https://github.com/facebook/react/issues/16295#issuecomment-518318185 seems like its the correct behaviour. – Eduards Mar 02 '22 at 23:20