2

My friend and I are working on a React translator app that uses the Lexicala API. We have two selector components: one for the source language and one for the target language. Users will first select the source language, and based on what they choose, a second dropdown menu will populate with a list of target languages that are available. Does anyone have any suggestions for how we would make the state update in the source language selector component affect the second component?

I'm including the code (without the comments) for each component. If you think we're already doing something incorrectly, please let me know.

SourceLanguageSelector.js

import React, {useState, useEffect} from 'react';
import { encode } from "base-64";

let headers = new Headers();

headers.append('Authorization', 'Basic ' + encode(process.env.REACT_APP_API_USERNAME + ":" + process.env.REACT_APP_API_PASSWORD));

const SourceLanguageSelector = () => {

    const [loading, setLoading] = useState(true);

    const [items, setItems] = useState([
        { label: "Loading...", value: "" }
    ]);

    const [value, setValue] = useState();

    

    useEffect(() => {

   
        let unmounted = false;

        async function getLanguages() {

            
            const request = await fetch("https://dictapi.lexicala.com/languages", {
                        method: 'GET', headers: headers
                    });
            const body = await request.json();
            console.log(body);

            
            const sourceLang = body.resources.global.source_languages;
            const langName = body.language_names;

            const compare = (sourceLanguage, languageName) => {
                return sourceLanguage.reduce((obj, key) => {
                    if (key in languageName) {
                        obj[key] = languageName[key];
                    }
                    return obj;
                }, {});
            }

            const sourceLanguageNames = compare(sourceLang, langName);
            
           
            if (!unmounted) {
                setItems(
                    Object.values(sourceLanguageNames).map((sourceLanguageName) => ({
                        label: sourceLanguageName,
                        value: sourceLanguageName
                    }))
                    );
                setLoading(false);
            }
        }
        getLanguages();

   
        return () => {
            unmounted = true;
        }
    }, []);

    return (
        <select 
            disabled={loading}
            value={value}
            onChange={e => setValue(e.currentTarget.value)}>
            {items.map(item => (
                <option key={item.value} value={item.value}>
                    {item.label}
                </option>
            ))}
        </select>
    );

};

export default SourceLanguageSelector;

TargetLanguageSelector.js

import React, { useState, useEffect } from 'react';
import { encode } from 'base-64';

let headers = new Headers();

headers.append('Authorization', 'Basic ' + encode(process.env.REACT_APP_API_USERNAME + ":" + process.env.REACT_APP_API_PASSWORD));

const TargetLanguageSelector = () => {

    
    const [loading, setLoading] = useState(true);

    
    const [items, setItems] = useState([
        { label: "Loading...", value: "" }
    ]);

    
    const [value, setValue] = useState();


    useEffect(() => {

 
        let unmounted = false;

        async function getLanguages() {
            
            const request = await fetch("https://dictapi.lexicala.com/languages", {
                        method: 'GET', headers: headers
                    });
            const body = await request.json();
            console.log(body);


           const targetLang = body.resources.global.target_languages;
           const langName = body.language_names;

           const compare = (targetLanguage, languageName) => {
               return targetLanguage.reduce((obj, key) => {
                   if (key in languageName) {
                       obj[key] = languageName[key];
                   }
                   return obj;
               }, {});
           }

           const targetLanguageNames = compare(targetLang, langName);
            
            
            if (!unmounted) {
                setItems(
                    Object.values(targetLanguageNames).map(target_languages => 
                    ({
                        label: target_languages, 
                        value: target_languages
                    }))
                    );
                setLoading(false);
            }
        }
        getLanguages();

        
        return () => {
            unmounted = true;
        }
    }, []);

    return (
        <select 
            disabled={loading}
            value={value}
            onChange={e => setValue(e.currentTarget.value)}>
            {items.map(item => (
                <option key={item.value} value={item.value}>
                    {item.label}
                </option>
            ))}
        </select>
    );

};

export default TargetLanguageSelector;
Charlie Wallace
  • 1,810
  • 1
  • 15
  • 17
Hunter
  • 21
  • 2

2 Answers2

0

You can coordinate state between the selects through the parent. Pass a callback function to Source that gets called when it's onChange handler fires, passing the selected language up to the parent. In the parent, create a piece of state for the current selected language and pass it as a prop to Target. In target you can have a useEffect with that prop in the dependency array so whenever the selected language changes in Source, Target will make the appropriate api call.

EvanMorrison
  • 1,212
  • 8
  • 13
0

You will have three options to have connection between two separate components.

  1. Make parent as the connector between two children.
e.g, Use a function props to trigger handler in parent. 
This handler will change the state of parent and it will lead to change the
props of another children like how @EvanMorrison has answered.
  1. You can use a global state tree , a state management library like Redux, MobX or Flux which will help you manage a single source of truth. Therefore, when you dispatch an action in one component, the state can be detected at another component and then use an useEffect hook to trigger again using the state as a dependency.

  2. Another pattern is to use Context API, but even for me, I rarely use it although understand the concepts.

You can find more explanation and examples here.

https://reactjs.org/docs/context.html#updating-context-from-a-nested-component

React.js - Communicating between sibling components

Kevin Moe Myint Myat
  • 1,916
  • 1
  • 10
  • 19