DEV Community

Cover image for Fill  select options depending on other select field using Axios, React Hook Form and jsonplaceholder API
danilo95
danilo95

Posted on

Fill select options depending on other select field using Axios, React Hook Form and jsonplaceholder API

A classic problem for developers,
They give us a list of states, from which state the user chooses we will show the cities, and within the cities another element.
today we will do something similar using jsonplaceholder:

what is jsonplaceholder?
It is a free api to use, which will allow us to play with its GETs, in this way this example will be the most real to a real problem in a real problem.

** More information here **
https://jsonplaceholder.typicode.com/

our objective is
that when loading our component it will show a list of authors, when selecting an author a second select will be filled with the information of the posts of that author, when choosing a post we will show the comments of said post on the screen.

let’s do it

we will need these libraries first

yarn add axios (to make requests)
yarn add react-hook-form (for handling forms)
yarn add styled-components (powerful css library)

first lets create our style.js file with styled Components

import styled from 'styled-components';
export const SelectInput = styled.select`
    width: 150px;
    height: 51px;
    font-size: 14px;
    line-height: 16px;
    background: #f3f3f3;
    border-radius: 5px;
    padding: 15px;
    border: none;
    font-weight: bold;
    color: #656363;
`;

export const InputContainer = styled.div`
    width: 100%;
    border-style: dotted;
    padding: 30px 0px 30px 0px;
    display: flex;
    justify-content: center;
`;

export const InputWrapper = styled.div`
    padding: 0px 10px 0px 10px;
`;

export const Email = styled.p`
    font-weight: bold;
`;

export const Comment = styled.p`
    font-style: italic;
`;
Enter fullscreen mode Exit fullscreen mode

now lets create our request file, to manage all the gets from jsonplaceholder

import axios from 'axios';

const url = axios.create({
    baseURL: 'https://jsonplaceholder.typicode.com/',
});

export const getUsers = (body) => {
    let result = url
        .get('/users')
        .then((response) => {
            return response.data;
        })
        .catch((error) => {
            return error;
        });

    return result;
};

export const getUsersPosts = (id) => {
    let result = url
        .get(`/posts?userId=${id}`)
        .then((response) => {
            return response.data;
        })
        .catch((error) => {
            return error;
        });

    return result;
};

export const getUsersComments = (id) => {
    let result = url
        .get(`/comments?postId=${id}`)
        .then((response) => {
            return response.data;
        })
        .catch((error) => {
            return error;
        });

    return result;
};
Enter fullscreen mode Exit fullscreen mode

axios.create: this allow us to create a configuration for every requests,in this case i set the baseurl so, anytime i need to make a get y just call url.get and write the endpoint and queryparams.

at the end axios will return all the data in response.data otherwise return a object call error.

lets create our app component

import React, { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { getUsers, getUsersPosts, getUsersComments } from './Requests';
import {
    SelectInput,
    InputContainer,
    InputWrapper,
    Email,
    Comment,
} from './style';

function App() {
    const [user, setUsers] = useState([]);
    const [userPosts, setUsersPosts] = useState([]);
    const [comments, setComments] = useState([]);
    const [errorRequest, setErrorRequest] = useState(false);
    const { register, handleSubmit, watch } = useForm();
    let watchUser = watch('userId');
    let watchPost = watch('userPost');

    return (
        <div>
            <InputContainer>
                <InputWrapper>
                    <SelectInput name="userId" ref={register()}>
                        <option value="">Choose a user</option>
                        {user.map((value) => (
                            <option value={value.id} key={value.id}>
                                {value.username}
                            </option>
                        ))}
                    </SelectInput>
                </InputWrapper>
                <InputWrapper>
                    <SelectInput name="userPost" ref={register()}>
                        <option value="">Choose a post</option>
                        {userPosts.map((value) => (
                            <option value={value.id} key={value.id}>
                                {value.title}
                            </option>
                        ))}
                    </SelectInput>
                </InputWrapper>
            </InputContainer>
            <h1>Comments</h1>
            {comments.map((value) => (
                <div key={value.id}>
                    <Email>{value.email}</Email>
                    <Comment>{value.body}</Comment>
                    <hr />
                </div>
            ))}
        </div>
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

our app would look like this

App UI

important things:

import { getUsers, getUsersPosts, getUsersComments } from ‘./Requests’: in this way, we call our request, by the way, we will need to use async await logic in our useEffect to manage the result in every change.

let watchUser = watch(‘userId’): we are using react hook form, so we need to use the watch function, for every select value, in this way we save the value everytime that the select change.
lets fill our first select, we need the user when the component is ready so we need to use useEffect

useEffect(() => {
        async function fetchInitialData() {
            const response = await getUsers();
            response.errors ? setErrorRequest(true) : setUsers(response);
        }

        fetchInitialData();
    }, []);
Enter fullscreen mode Exit fullscreen mode

In this case, we will use useEfect to fill our first select, as I mentioned we will call our request function at this moment, but it will be inside an async await, if you do not do it like that, the value that I will be receiving at the beginning of the component is a promise of pending state, with async await we make sure that this is fulfilled and resolved.

after that we take advantage of axios, this if there is an error create the error object, we validate if it exists otherwise we proceed to fill our first select.

useEffect(() => {
        setUsersPosts([]);
        setComments([]);
        async function fetchPosts() {
            const response = await getUsersPosts(watchUser);
            response.errors ? setErrorRequest(true) : setUsersPosts(response);
        }
        if (watchUser) {
            fetchPosts();
        }
    }, [watchUser]);
Enter fullscreen mode Exit fullscreen mode

the second useEffect is very similar but with two variants, in our arrangement of dependencies put watchUser since this is the variable that contains the value of the user every time it changes, the second is to clean the posts and comments.
this so that at the time the new values are being loaded, we will not see the old ones on the screen, the correct way would be to place a loading status, but that improvement is on your side.
Our second use Effect will be similar, only that we will not clear our previous values since the previous function already performed that task.
We will only change the value of our dependency array for watchPost.

if (watchUser) {
            fetchPosts();
        }
Enter fullscreen mode Exit fullscreen mode

this little piece of code, help us to avoid send request when the user has no values, so we need to keep clean our calls, otherwise we will produce so many unnecessary requests for the api.

final result

Complete code:

Github Repo

Top comments (0)