0
votes

I am using React Apollo to work with a GaphQL backend. The function I am creating allows users to edit the admin permissions of another user.

I have two components. One component contains the React Apollo logic, including the query and the mutation components. When this component is rendered a prop that is passed down from a match parameter in the router specifies what admin to get the permissions of which is used in the GraphQL query. When the data required for the admin permissions component is retrieved it is passed as a prop to the child component which sets the initial state based on the prop. The child component contains no React Apollo logic and simply contains checkboxes and other inputs that update the state of the component as they are changed. When clicking the save changes button the parent's mutation function is called passing the newly updated permissions to insert variables into the mutation query.

The issue that I am facing is that sometimes the state does not change depending on the props. For example, admin 1 is accessed, the router then updates the URL when a button is clicked to access admin 2. It works up to this point. The router then updates the URL again when a button is clicked to access admin 1, admin 2 is still shown in the child component, despite the props being updated with the new values. Note the constructor of the child component is not called again.

Any suggestions on how I can ensure that when the data produced by the GraphQL query displays the correct child componenet instance? Furthermore, any suggestions on how the structure of this componenet can be improved.

render(){
if(this.props.steamID ===  null) return null;

const QUERY_VARIABLES = {
  serverID: this.props.serverID,
  selectedAdminSteamID: this.props.steamID,
  currentAdminSteamID: Auth.claim.steamID
};

return (
  <Query
    query={QUERY}
    variables={QUERY_VARIABLES}
  >
    {({ loading, error, data }) => {
      if (loading) return ( ... );
      if (error) return ( ... );
      if(!data.server.selectedAdmin) return ( ... );

      return (
        <Mutation
          mutation={MUTATION}
          update={(cache, { data: { updateAdminPermission } }) => {
            const data = cache.readQuery({ query: QUERY, variables: QUERY_VARIABLES });
            data.server.selectedAdmin = updateAdminPermission;
            cache.writeQuery({ query: QUERY, variables: QUERY_VARIABLES, data: data, });
          }}
        >

          {(updateAdminPermission, { loading, error }) => (
            <>
              <AdminPermissions
                serverID={this.props.serverID}
                steamID={this.props.steamID}

                selectedAdmin={data.server.selectedAdmin}
                currentAdmin={data.server.currentAdmin}

                updatePermissionFunction={(variables) => {
                  updateAdminPermission({ variables })
                }}
                updatePermissionLoading={loading}
              />
              <GraphQLErrorModal error={error} />
            </>
          )}

        </Mutation>
      );
    }}
  </Query>
);
}

class AdminPermissions extends React.Component{
    constructor(props){
      super();

      this.state = props.selectedAdmin;

      this.guid = React.createRef();
      this.updatePermission = this.updatePermission.bind(this);
      this.saveChanges = this.saveChanges.bind(this);
    }

    updatePermission(changedPermission, value){
      if(changedPermission === 'manageAssignPermissions' && value > 0){
        for(let permission of panelPermissions.concat(gamePermissions)){
          if(permission.permission === 'manageAssignPermissions') continue;
          this.setState({ [permission.permission]: 2 });
        }
      }
      this.setState({ [changedPermission]: value });
    }

    saveChanges(){
      this.props.updatePermissionFunction({
        serverID: this.props.serverID,
        steamID: this.props.steamID,
        guid: this.guid.current.value,
        ...this.state
      });
    }

    render(){
        // renders pairs of checkboxes with checked value based on state and on change event that calls update permissions method passing the name of the associated permission and a value that is calculated based on which boxes in the pair are ticked.
    }
 }

Query (Update)

  query AdminPermission($serverID: Int!, $selectedAdminSteamID: String!, $currentAdminSteamID: String!) {
    server(id: $serverID) {
      id

      selectedAdmin: adminPermission(steamID: $selectedAdminSteamID) {    
        _id

        admin {
          _id

          steamID
          displayName
          avatar
        }

        player {
          _id
          guid
        }

        manageAssignPermissions
        viewAdminPermissions
        ...
      }
      currentAdmin: adminPermission(steamID: $currentAdminSteamID) {
        _id

        admin {
          _id
          steamID
        }

        manageAssignPermissions
        viewAdminPermissions
        ...
      }
    }
  }
1
What's the actual query you're sending?Daniel Rearden
The query's content isn't really relevant, but it basically fetches a set of properties that are either 0, 1 or 2 and some additional properties about the admin, like their display name, avatar, etc.Thomas Smyth
Try to add fetchPolicy='cache-and-network' to your <Query>Fraction
@ThomasSmyth The query's content can be relevant if it's causing your query not to being cached correctly. Most of the time, these sort of issues are due to Apollo's cache normalization.Daniel Rearden
@DanielRearden I do apologise, I do not realise this problem was as a result of the caching.Thomas Smyth

1 Answers

1
votes

You must set the Query's fetchPolicy to cache-and-network, by default it's cache-first:

<Query
    query={QUERY}
    variables={QUERY_VARIABLES}
    fetchPolicy='cache-and-network'
  >

If you read the documentation:

  • cache-first: This is the default value where we always try reading data from your cache first. If all the data needed to fulfill your query is in the cache then that data will be returned. Apollo will only fetch from the network if a cached result is not available. This fetch policy aims to minimize the number of network requests sent when rendering your component.

  • cache-and-network: This fetch policy will have Apollo first trying to read data from your cache. If all the data needed to fulfill your query is in the cache then that data will be returned. However, regardless of whether or not the full data is in your cache this fetchPolicy will always execute query with the network interface unlike cache-first which will only execute your query if the query data is not in your cache. This fetch policy optimizes for users getting a quick response while also trying to keep cached data consistent with your server data at the cost of extra network requests.