5

I have a custom hook to fetch data on form submit

export const getIssues = ({ user, repo }) => {
  const [issues, setIssues] = useState([]);

  const handleInputChange = (e) => {
    e.preventDefault();
    axios.get(`https://api.github.com/repos/${user}/${repo}/issues`)
      .then((response) => {
        setIssues(response.data);
      })
      .catch((err) => console.log(err));
  };

  return {
    issues,
    onSubmit: handleInputChange,
  };
};

In my component I call it like this

const response = getIssues({ user: user.value, repo: repo.value })
  return (
    <form className={css['search-form']} {...response}>...</form>
)

The problem is that I want to get my issues value from the hook in another component. For that I wanted to use Context. But I have no idea how to do it.

I could call this function and pass it to Provider, but I can't call it without arguments. So I kind of stuck.

All the help will be much appreciated.

Person
  • 2,190
  • 7
  • 30
  • 58
  • Since `getIssues` is a hook you should call it `useIssues` a linter should have complained about this. The `e.preventDefault` does not belong in the issues hook, since you provide values `user.value` and `repo.value` I assume your form sets these in some way so just calling `useIssues` should be enough. – HMR Sep 01 '19 at 16:46

2 Answers2

2

You are right by saying you need React.Context to handle this situation.

  1. You need to wrap your components into this context.
import React from "react";

const IssuesStateContext = React.createContext();
const IssuesDispatchContext = React.createContext();

function issuesReducer(state, action) {
  switch (action.type) {
    case "setIssues": {
      return [...action.payload];
    }

    default: {
      throw new Error(`Unhandled action type: ${action.type}`);
    }
  }
}

function IssuesProvider({ children }) {
  const [state, dispatch] = React.useReducer(issuesReducer, []);
  return (
    <IssuesStateContext.Provider value={state}>
      <IssuesDispatchContext.Provider value={dispatch}>
        {children}
      </IssuesDispatchContext.Provider>
    </IssuesStateContext.Provider>
  );
}

function useIssuesState() {
  const context = React.useContext(IssuesStateContext);
  if (context === undefined) {
    throw new Error("useIssuesState must be used within a IssuesProvider");
  }
  return context;
}
function useIssuesDispatch() {
  const context = React.useContext(IssuesDispatchContext);
  if (context === undefined) {
    throw new Error("useIssuesDispatch must be used within a IssuesProvider");
  }
  return context;
}

export { IssuesProvider, useIssuesState, useIssuesDispatch };

By using this separation in context you will be able to set issues coming from github in one component and render them in a completely different one.

Example:

App.js

ReactDOM.render(
  <IssuesProvider>
    <Component1 />
    <Component2 />
  </IssuesProvider>
)

Component 1

import React from 'react'
import { useIssuesDispatch } from './issues-context'

function Component1() {
  const dispatch = useIssuesDispatch()
  // fetch issues
  // .then dispatch({ type: 'setIssues', payload: response })

  // render
}

Component 2

import React from 'react'
import { useIssuesState } from './issues-context'

function Component2() {
  const issues = useIssuesState()

  // if issues.length > 0 ? render : null
}
Alejandro Garcia Anglada
  • 2,373
  • 1
  • 25
  • 41
0

You can write a Issues context provider that will provide {issues,useIssues} where issues are the issues and useIssues is a function that takes {user,repo}.

export const Issues = React.createContext();
export default ({ children }) => {
  const [issues, setIssues] = useState([]);
  const useIssues = ({ user, repo }) => {
    useEffect(() => {
      axios
        .get(
          `https://api.github.com/repos/${user}/${repo}/issues`
        )
        .then(response => {
          setIssues(response.data);
        })
        .catch(err => console.log(err));
    }, [user, repo]);
    return issues;
  };
  return (
    <Issues.Provider value={{ issues, useIssues }}>
      {children}
    </Issues.Provider>
  );
};

The component that has all the components that need issues can import this issues provider:

import IssuesProvider from './IssuesProvider';
export default () => (
  <IssuesProvider>
    <ComponentThatNeedsIssues />
    <ComponentThatSetsAndGetsIssues />
  </IssuesProvider>
);

For a component that needs to set issues you can get useIssues from context:

const { useIssues } = useContext(Issues);
const issues = useIssues({user,repo});

For a component that only needs issues:

const { issues } = useContext(Issues);

To see it all work together there is a codepen here

HMR
  • 37,593
  • 24
  • 91
  • 160
  • If I put useIssues function inside my components where I want to fetch issues it gets called every time I input something in the field and not on submit – Person Sep 08 '19 at 12:39
  • If I try to call it ```onSubmit``` I'm getting an error ```Invalid hook call. Hooks can only be called inside of the body of a function component``` – Person Sep 08 '19 at 12:50