0

Overview

When WrappedApp initially loads, a query is sent to the GraphQL API to fetch the data (Tags).

After this has completed a user can add an item to the ShortList in WrappedApp by clicking on a Tag in the TagsList (see screenshot below).

Question

How can I get this click of a Tag in the TagsList (i.e. security was clicked in the example below) to trigger the second GraphQL query named GQLSIMILARTAGS and render the result above the ShortList components data or at least console.log the result of the GQLSIMILARTAGS query? GQLSIMILARTAGS accepts a variable which would be the Tag that was clicked and added to ShortList.

shortlist

What have I tried?

I tried modifying the addFavourite function in WrappedApp to call GQLFunc with the new GQLSIMILARTAGS query with useQuery(GQLSIMILARTAGS) but this may not be the best approach.

This is the Apollo GraphQL query code.

graphclient.js




const client = new ApolloClient({
  uri: "https://xxxx.herokuapp.com/v1/graphql",
});

const GQLTAGS = gql`
  {
    tag(
      order_by: { tag_related_counts_aggregate: { count: desc } }
      where: { label: { _nin: ["None", "null"] } }
    ) {
      id
      label
      tag_related_counts_aggregate {
        aggregate {
          count
        }
      }
    }
  }
`;

        const GQLSIMILARTAGS = gql`
  query {
    tag(
      where: { tag_related_counts: { search_label: { _eq: "security" } } }
      distinct_on: id
    ) {
      label
      tag_related_counts {
        count
        other_label
        search_label
      }
    }
  }
`;

function GQLFunc(props) {
  const { loading, error, data } = useQuery(GQLTAGS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;

  let CallingApp = props.callingApp;
  if (data) return <CallingApp data={data.tag} />;
}

export { client, GQLTAGS, GQLFunc };

This is the main WrappedApp.js App (edit: Updated to function component as per @xadm feedback).

       function WrappedApp(props) {
  const [filterText, setfilterText] = useState("");
  const [favourites, setFavourites] = useState([]);

  // update filterText in state when user types
  const filterUpdate = (value) => {
    setfilterText(value);
  };

  // add clicked name ID to the favourites array
  const addFavourite = (id) => {
    const newSet = favourites.concat([id]);
    setFavourites(newSet);
  };

  // remove ID from the favourites array
  const deleteFavourite = (id) => {
    //console.log(id);
    const newList = [...favourites.slice(0, id), ...favourites.slice(id + 1)];
    setFavourites(newList);
  };

  const hasSearch = filterText.length > 0;
  return (
    <div>
      {GQLSimilarFunc()}
      <header>
        <Greeting />
        <Search filterVal={filterText} filterUpdate={filterUpdate} />
      </header>
      <main>
        <ShortList
          data={props.data}
          favourites={favourites}
          simfunc={GQLSimilarFunc}
        />

        <TagsList
          data={props.data}
          filter={filterText}
          favourites={favourites}
          addFavourite={addFavourite}
        />
        {/* 
            Show only if user has typed in search.
            To reset the input field, we pass an 
            empty value to the filterUpdate method
          */}
        {hasSearch && (
          <button onClick={() => filterUpdate("")}> Clear Search</button>
        )}
      </main>
    </div>
  );
}

export default WrappedApp;

Other code used in WrappedApp

import React, { Component, useState } from "react";
import { GQLSimilarFunc } from "./graphclient";

/* ############################ */
/* ##### Single tag ##### */
/* ############################ */

const Tag = ({ id, info, handleFavourite }) => (
  <li className={info.count} onClick={() => handleFavourite(id)}>
    {info.label} ({info.tag_related_counts_aggregate.aggregate.count})
  </li>
);


/* ##################### */
/* ##### Shortlist ##### */
/* ##################### */

const ShortList = ({ favourites, data, simfunc }) => {
  const hasFavourites = favourites.length > 0;
  const favList = favourites.map((fav, i) => {
    console.log(data.find((tag) => tag.id === fav).label);
    return (
      <Tag
        id={i}
        key={i}
        info={data.find((tag) => tag.id === fav)}
        //handleFavourite={(id) => deleteFavourite(id)}
        handleFavourite={() => simfunc()}
        /*handleFavourite={GQLSimilarFunc(
          data.find((tag) => tag.id === fav).label
        )}*/
      />
    );
  });
  //console.log(data.find((tag) => tag.id === 4436));

  return (
    <div className="favourites">
      <h4>
        {hasFavourites
          ? "Shortlist. Click to remove.."
          : "Click on a tag to shortlist it.."}
      </h4>
      <ul>{favList}</ul>
      {hasFavourites && <hr />}
    </div>
  );
};

/* ########################### */
/* ##### Tag list ##### */
/* ########################### */

const TagsList = ({ data, filter, favourites, addFavourite }) => {
  const input = filter;

  // Gather list of tags
  const tags = data
    // filtering out the tags that...
    .filter((tag, i) => {
      return (
        // ...are already favourited
        favourites.indexOf(tag.id) === -1 &&
        // ...are not matching the current search value
        !tag.label.indexOf(input)
      );
    })
    // ...output a <Name /> component for each name
    .map((tag, i) => {
      //console.log(tag.label);
      // only display tags that match current input string
      return (
        <Tag
          id={tag.id}
          key={i}
          info={tag}
          handleFavourite={(id) => addFavourite(id)}
        />
      );
    });

  /* ##### the component's output ##### */
  return <ul>{tags}</ul>;
};

/* ###################### */
/* ##### Search bar ##### */
/* ###################### */

// need a component class here
// since we are using `refs`
class Search extends Component {
  render() {
    const { filterVal, filterUpdate } = this.props;
    return (
      <form>
        <input
          type="text"
          ref="filterInput"
          placeholder="Type to filter.."
          // binding the input value to state
          value={filterVal}
          onChange={() => {
            filterUpdate(this.refs.filterInput.value);
          }}
        />
      </form>
    );
  }
}

and this is my index.js

import React from "react";
import ReactDOM from "react-dom";
import * as serviceWorker from "./serviceWorker";
import { ApolloProvider } from "@apollo/react-hooks";
import { client, GQLTags, GQLFunc } from "./graphclient";
import WrappedApp from "./WrappedApp";

/* ############################ */
/* ##### Single tag ##### */
/* ############################ */

ReactDOM.render(
  <ApolloProvider client={client}>
    <GQLFunc callingApp={WrappedApp} />
  </ApolloProvider>,
  document.getElementById("root")
);
nipy
  • 5,138
  • 5
  • 31
  • 72
  • if queries are parent-child related then use parent-child components - just inject another component level between GQLFunc and CallingApp. – xadm Jun 08 '20 at 18:11

1 Answers1

1

It can be as simple as

function GQLFunc(props) {
  const { loading, error, data } = useQuery(GQLTAGS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;

  //let CallingApp = props.callingApp;
  //if (data) return <CallingApp data={data.tag} />;
  if (data) return <GQLChild dataTag={data.tag} callingApp={props.callingApp} />;
}

function GQLChild(props) {
  const { loading, error, data } = useQuery(GQLSIMILARTAGS, {
    variables: {
      someSimilarRequiredVariableFromTagQueryResult: props.dataTag.something
    }
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;

  let CallingApp = props.callingApp;
  // pass data separately or combine into one `data` prop
  if (data) return <CallingApp dataSimilar={data} dataTag={props.dataTag} />;
}
xadm
  • 8,219
  • 3
  • 14
  • 25
  • Wrapping some ready app with a new data source is OK. Building next complexity 'above' App and searching for workarounds to pass next data/handlers from/to queries is **definitely not OK**. It is possible (context or apollo local state) BUT IT IS NOT THE RIGHT WAY. Rebuild your app using queries and mutations in the right places (inside app components). – xadm Jun 08 '20 at 22:16
  • Convert `` into functional component, use `useQuery` inside, pass `filterText` as query variable .... but (to avoid other problems/complexity) you should pass one/main variable - `where` complex condition object constructed using `filterText` value) .... `useMutation` (for add favourite mutation) can be placed in `` but also in `` (no need to pass `addFavourite` handler) ... it all depends - where data fetched/rendered/updated – xadm Jun 08 '20 at 23:02
  • Even if it is a POC then you shouldn't go further in this direction, it will overcomplicate simple things. “Fail Fast, Succeed Faster” – xadm Jun 09 '20 at 22:04
  • I have converted `WrappedApp` to a function component and updated the code in the question. Can you please show me what to do next? – nipy Jun 10 '20 at 09:00
  • 1
    assuming starting situation, where wrapper `` reads tags and renders `` .... you have tags as `props.data` .... next ... `useQuery(GQLSIMILARTAGS)` (hardcoded now) in `` ... it will fetch some `data` with similar items ... you can pass into some child component. – xadm Jun 10 '20 at 11:34
  • Hi @xadm. I have updated the code in the question. `GQLSIMILARTAGS` is hard coded without variables and I am trying to trigger `GQLSimilarFunc ` in `ShortList ` to send second query for similar tags. If I can get this working I would add variables later. Is it possible look at the updated code and tell me how to correct it as I think syntax when calling `ShortList` is incorrect. – nipy Jun 10 '20 at 22:52
  • what is it? function/component/handler? why do you need it? just use `useQuery` where you need – xadm Jun 10 '20 at 23:09
  • sorry I don't understand how I would use `useQuery`. I modified the existing `deleteFavourite` in `ShortList` as I thought I could use it to click on shortlist entry and send for related tags. Could you please show me how `useQuery` would work? – nipy Jun 10 '20 at 23:16
  • just like I wrote ... don't go further before this ... standard useQuery usage – xadm Jun 10 '20 at 23:20
  • `const { loading, error, data } = useQuery(GQLSIMILARTAGS);` in `` .. pass `data` to any child you want/need – xadm Jun 10 '20 at 23:28