Fetching Translations from Server using React Hooks
Tags: react, translations, javascript
Published: 2020-08-20 18:29:53

---
title: Fetching Translations from Server using React Hooks
published: true
description: Creating a useTranslations() hook in React to fetch translation data in one per-page request, rather than resorting to loads of requests spamming your server
tags: react,translations,javascript
cover_image: https://i.imgur.com/9rC57i2.jpg
---

We recently had a bit of a challenge trying to set up a semi-efficient way to bring translations into React from our server.

We had the idea to create a `useTranslations()` hook, and to utilise the order in which components mount in order to collate the translation tags into an array - Then we can use this array to send out a single request to the server for each page to get all the required translations, and cache them in localStorage. To illustrate, usage will look like this;

**App.jsx**
```jsx
const App = () => (
  <TranslationProvider>
    <MyPage />
  </TranslationProvider>
)
```

**MyPage.jsx**
```jsx
const MyPage = () => {
  const { translate, fetchTranslations } = useTranslations();
  // fetch translations on mount
  useEffect(fetchTranslations, []);
  return (
    <>
      <h1>{ translate('hello_world') }</h1>
      <MyComponent />
    </>
  );
}
```

**MyComponent.jsx**
```jsx
const MyComponent = () => {
  const { translate } = useTranslations();
  return (
    <p>{ translate('description_1') }</p>
  );
}
```

This came with a few challenges along the way, but we got there in the end and learned a **ton**, so I thought I'd share our solution for anyone needing anything similar.

## Setting up our boilerplate

When making hooks that require a context/state, I like to use this little template. It's simple, keeps everything in one place and is really easy to extend.

**TranslationState.jsx**
```jsx
const TranslationContext = createContext({});

export const TranslationProvider = ({ children }) => {
  const state = {
    // ...
  };

  return (
    <TranslationContext.Provider value={state}>
      {children} 
    </TranslationContext.Provider>
  );
}

export const useTranslations = () => {
  const context = useContext(TranslationContext);
  if (!context) {
    throw new Error('useTranslationState must be used within the TranslationProvider');
  }
  return context;
}
```

## Fetching Translations

To begin with, we're going to create a state for the translations themselves. The format we're going to store them in through this example is `{ 'tag_name':'Translation value.' }` - but you can tailor this however you like.

**TranslationState.jsx**
```js
//...
export const TranslationProvider = ({ children }) => {
  // You could pull the default value from localStorage
  // if you plan to cache the result on the client.
  const [translations, setTranslations] = useState({});
  const state = {
    translations,
  };
// ...
```

We're also going to create the function that fetches the translations from the server. We'll call this `fetchTranslations()` and insert this into the state object so we can access it from the hook.

I've created a mocky endpoint, with some dummy-data to play with;
https://run.mocky.io/v3/28656dd4-871b-48fb-8a86-464cca940c76

**TranslationState.jsx**
```js
//...
  const [translations, setTranslations] = useState({});
  const fetchTranslations = () => {
    fetch('https://run.mocky.io/v3/28656dd4-871b-48fb-8a86-464cca940c76', {
      method: 'POST',
      //TODO: body should be an array of required translations,
    }).then(
      (data) => data.json()
    ).then(
        // we update the state of translations with the
        // new translations recieved from the server.
        setTranslations({
          ...translations,
          ...newTranslations,
        });
        // You could also store these translations in
        // localStorage here.
    });
  };
  const state = {
    translations,
    fetchTranslations,
  }
//...
``` 

## Collating Translations and Rendering

This was the tricky part! 

**The Problem:**

Our initial idea was to add the tags to the translations state, with the value of false, and send those tags to the server in fetchTranslations - however this messes up the order of operations! When we update the state, it means the parent will re-mount every time the children require a translation, meaning if we have 6 translations, we're going to send out 6 requests.

**The Solution:**

React has the `useRef()` hook! This hook creates an object that will **not** re-mount components when it updates! Solving our order of operations issue!

**TranslationState.jsx**
```js
//...
  const translationsRef = useRef({})
  const [translations, setTranslations] = useState(translationsRef.current);

  const fetchTranslations = () => {
    fetch('https://run.mocky.io/v3/28656dd4-871b-48fb-8a86-464cca940c76', {
      method: 'POST',
      body: Object.entries(translationsRef.current)
        .filter(([, value]) => value === false)
        .map(([tag]) => tag)
    })
      .then((data) => data.json())
      .then((newTranslations) => {
        translationsRef.current = {
          ...translationsRef.current,
          ...newTranslations,
        };
        setTranslations(translationsRef.current);
      });
  };
  
  const translate = (tag, defaultValue = 'Loading...') => {
    if (typeof translationsRef.current[tag] === 'undefined') {
      translationsRef.current[tag] = false;
    }
    // You could return something fancy like;
    // https://material-ui.com/components/skeleton/#animations
    return translationsRef.current[tag] || defaultValue; 
  };

  const state = {
    translations,
    fetchTranslations,
    translate,
  }
//...
```

And there we go!

If you found the walkthrough a little hard to understand, sorry about that! This is my first Dev.to post, I'm sure I'll improve. You can try out the hook here:

{% codesandbox usetranslations-react-hook-qikul %}

Please let me know what you think in the comments, and if you have any better solutions to the problem, do let me know!