Use cache to catch data without page refresh
Tags: react, graphql
Published: 2020-09-13 06:43:49

# [demo](https://react-todo-orcin.vercel.app/) 

# problem aim to solve
In a React todo-list development page, data can't update synchronously with CRUD in the database. Users need to refresh the browser to get the latest information, which made a stuck browser experience. Here gonna use cache, which is a temporary data storage location, and todo-list create/delete to make the problem solve example. 

# prerequisite 
* GraphQL client 
  1. [urql](https://formidable.com/open-source/urql/): a GraphQL client 
  2. @urql/exchange-graphcache: for cache setting
  3. [graphql-tag](https://www.npmjs.com/package/graphql-tag): a utility for parsing GraphQL queries
  4. React / react-bootstrap (optional)

* [GraphQL server](https://dev.to/pcreem/build-a-graphql-todolist-server-on-prisma-postgresql-2j63)

# conceptual code
### a. set component, todo create/delete GraphQL queries 
`code .\src\components\todo.js`
```
import React from 'react'
import gql from 'graphql-tag';
import { useQuery, useMutation } from 'urql';
import { InputGroup, FormControl, Button, Col, Row, Alert, Spinner } from 'react-bootstrap'; //optional

export const FEED_QUERY = gql` //show latest info
  query{
    info{
        id
        title
      }
  }
`;

const UPSERT_POST = gql` //exexute todo create 
  mutation upsertPost( $postId:ID!, $title: String!) {
    upsertPost( postId: $postId, title: $title) {
      id
      title
    }
  }
`

const DELETE_POST = gql`
  mutation deletePost($postId:ID!) {
    deletePost(postId: $postId) {
      id
    }
  }
`

const PostForm = (props) => {
  //Upsert Section
  let postId = 0
  const [title, setTitle] = React.useState('')

  const [upsertState, executeUpsert] = useMutation(UPSERT_POST)
  const upsert = React.useCallback(() => {
    if (title.length !== 0 && title.length <= 30) { executeUpsert({ postId, title }); }
  }, [executeUpsert, postId, title])

  return (
    <Col sm={8}>
      <InputGroup className="mb-3">
        <FormControl
          placeholder='Add Todo...'
          aria-label="Recipient's username"
          aria-describedby="basic-addon2"
          maxLength="30"
          value={title}
          onChange={e => setTitle(e.target.value)}
        />
        <InputGroup.Append>
          <Button
            variant="outline-secondary"
            disabled={upsertState.fetching}
            type="submit"
            onClick={() => { upsert(); setTitle('') }}
          >Add</Button>
        </InputGroup.Append>{'  '}
      </InputGroup>
    </Col>
  )
}

function Post({ post }) {
  //Delete Section
  const postId = post.id
  const [deleteState, executeDelete] = useMutation(DELETE_POST)
  const del = React.useCallback(() => {
    executeDelete({ postId })
  }, [executeDelete, postId])

  return (
    <Col >
      <Alert variant="light" disabled={deleteState.fetching} onClose={() => { del(); }} dismissible>
        <p>{post.title}</p>
      </Alert>
    </Col>
  )
}



const PostList = () => {
  const [result] = useQuery({ query: FEED_QUERY })
  const { data, fetching, error } = result

  if (fetching) return <><Spinner animation="border" role="status"><span className="sr-only">Loading...</span></Spinner></>

  if (error) return <div>Error</div>

  const postsToRender = data.info
  return (
    <>
      {postsToRender.map(post => <Post key={post.id} post={post} />)}
    </>
  );
};

const Todo = (props) => {
  return (
    <>
      <br></br>
      <Row className="justify-content-md-center">
        <PostForm />
      </Row>
      <hr></hr>
      <Row className="justify-content-md-center">
        <PostList />
      </Row>
    </>
  )

}

export default Todo
```

### b. cache setting
`code .\src\index.js`
```
//...React setting
import { Provider, Client, dedupExchange, fetchExchange } from 'urql'
import { cacheExchange } from '@urql/exchange-graphcache'
import { FEED_QUERY } from './components/todo.js'

const cache = cacheExchange({
  updates: {
    Mutation: { 
      upsertPost: (result, args, cache, info) => {  //execute create todo 
        cache.updateQuery({ query: FEED_QUERY }, data => { //update the latest information in cache 
          if (data !== null) {
            data.info.unshift(result.upsertPost);
            return data;
          } else {
            return null
          }
        });
      },
      deletePost: (result, args, cache, info) => { // execute delete todo
        cache.invalidate({ __typename: 'Post', id:  result.deletePost.id }); //delete todo in cache
      },
    },
  },
});

const client = new Client({
  url: '/',
  fetchOptions: () => {
    //...your fetch setting
  },
  exchanges: [dedupExchange, cache, fetchExchange],
}) 

ReactDOM.render(
  <Provider value={client}>
    <App />
  </Provider>,
  document.getElementById('root')
)

serviceWorker.unregister();

``` 

# note
The todo-list will get user-related data in the [demo](https://react-todo-orcin.vercel.app/), code is not shown here. You could write a todo-only code to experience the cache effect.