2

I am trying to make my code clean and readable. So I decided to create a custom hook to store my useStates there. Then I created a new file to store my event listener. Now, I have three files: Page.js, useStates.js, and Listeners.js.

The problem is that I cannot use my states in event listeners.


I tried to store my states in global scope variables in useStates.js and pass them with getters. But it didn't work because updating the state didn't change the page(But it did rerender).


useStates.js:

import react, { useState } from 'react';

export default () => {
  const [myState, setMyState] = useState(false);
  return { myState, setMyState };
}

Page.js:

import react from 'react';
import useStates from './useStates';
import { someActionListener } from './listeners';

export default () => {
  const states = useStates();

  return <SomeComponent
            somProp={states.myState}
            onSomeAction={ someActionListener } />
}

Listeners.js:

export const someActionListener = (e) => {
  // This should be done
  states.setMyState(!states.myState);
} 
4
  • Please share a minimal reproducible example of the code you're using. Commented Jan 10, 2022 at 8:35
  • Agreed... we can't help debug code we can't see. Please also try to describe in better detail what specifically isn't working. Are there any errors, what debugging steps have you taken, are there steps to reproduce any issues, etc... Commented Jan 10, 2022 at 8:35
  • you can use state only in react components or hooks.. not standard JS. As your Listeners.js isnt a react component and not a hook you cant use state in there Commented Jan 10, 2022 at 8:46
  • onSomeAction={e => someActionListener(e, states)} Commented Jan 10, 2022 at 8:47

1 Answer 1

2

You could pass the states into the listener:

export const someActionListener = (e, states) => {
  // This should be done
  states.setMyState(!states.myState);
}

...

<SomeComponent
  somProp={states.myState}
  onSomeAction={e => someActionListener(e, states)}
/>

Or curry the states value instead, leading to a more clean UI, it saves the anonymous function callback:

export const someActionListener = states =>  e => {
  // This should be done
  states.setMyState(!states.myState);
}

...

<SomeComponent
  somProp={states.myState}
  onSomeAction={someActionListener(states)}
/>

I suggest tweaking the action listener to take a callback instead. Event listeners typically do this anyway. A curried function is an easy way to close over a callback and return the event listener callback. This decouples the state and state updating from the event listener logic, allowing the React component using both to maintain control of the state and how it's updated.

export const someActionListener = (callback) => (e) => {
  // This should be done
  callback();
}

Pass in the state updater function as a callback.

export default () => {
  const states = useStates();

  return (
    <SomeComponent
      somProp={states.myState}
      onSomeAction={someActionListener(
        () => states.setMyState(!states.myState))
      )}
    />
  );
}

Or using a functional state update since you are toggling a state value. This is to avoid stale state enclosures in callbacks.

<SomeComponent
  somProp={states.myState}
  onSomeAction={someActionListener(
    () => states.setMyState(states => ({
       ...states
       myState: !states.myState
    })
  }
/>
Sign up to request clarification or add additional context in comments.

1 Comment

@NavidNaseri Both solutions are effectively forwarding something. It may be more straightforward, but may not be as clean. If my answer looks more verbose it's because it's trying to help you avoid some easy traps. Cheers and good luck though!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.