Search code examples
reactjsreact-hooksreact-forms

React useState hook - I want to render certain component when only press submit button not when onChange


I'm new to react.

Now trying to make form with react hooks, and I want to render Cloud component only when press submit button. But it rendered every onChange called.

I know that onChange re-rendered cause also useState hook.

But have no idea how to render only when press submit button.

My final goal is when write name and press enter, if value is not contained in api, setShake make shake True and if True, put shake-cloud class in Cloud.js.

REACT IS TOO DIFFICULT :(

Thanks for help tho :)

App.js

import React, { useState, useEffect } from "react";
import "./App.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSearch } from "@fortawesome/free-solid-svg-icons";
import "./search.css";
import PageTitle from "./component/PageTitle";
import Cloud from "./component/Cloud";
import Loading from "./component/Loading";

//https://api.color.pizza/v1/
//data.colors[0].name

const App = () => {
    const [isLoading, setIsLoading] = useState(false);
    const [colorNames, setColorNames] = useState("");
    const [search, setSearch] = useState("");
    const [query, setQuery] = useState("");
    const [cloudHex, setCloudHex] = useState("ivory");
    const [shake, setShake] = useState(false);

    useEffect(() => {
        getColorLists();
    }, []);

    const getColorLists = async () => {
        const res = await fetch(`https://api.color.pizza/v1/`);
        const data = await res.json();
        await setColorNames(data);
        setIsLoading(true);
    };

    const isColor = () => {
        let makeUpper =
            query.search(/\s/) == -1
                ? query.charAt(0).toUpperCase() + query.slice(1)
                : query
                      .split(" ")
                      .map((i) => i.charAt(0).toUpperCase() + i.slice(1))
                      .join(" ");

        for (let i = 0; i < colorNames.colors.length; i++) {
            if (colorNames.colors[i].name == makeUpper) {
                setCloudHex(colorNames.colors[i].hex);
                return;
            } else if (i == colorNames.colors.length - 1) {
                return makeShake();
            }
        }
    };

    const updateSearch = (e) => {
        setSearch(e.target.value);
    };
    const getSearch = (e) => {
        e.preventDefault();
        setQuery(search);
        isColor();
    };

    const makeShake = async () => {
        await setShake(true)
        await setShake(false)
    }

    return (
        <>
            {!isLoading ? (
                <Loading />
            ) : (
                <div className="App">
                    <div className="app-wrap">
                        <PageTitle />
                        <div className="search-wrap">
                            <form onSubmit={getSearch} className="search-form">
                                <input
                                    className="search-bar"
                                    type="text"
                                    value={search}
                                    onChange={updateSearch}
                                />
                                <button type="submit" className="search-button">
                                    <FontAwesomeIcon
                                        icon={faSearch}
                                        className="search"
                                    />
                                </button>
                            </form>
                        </div>
                        <Cloud cloudhex={cloudHex} shake={shake} />
                    </div>
                </div>
            )}
        </>
    );
};

export default App;

Cloud.js

import React, {useEffect} from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCloud } from "@fortawesome/free-solid-svg-icons";
import './cloud.css';

const Cloud = ({cloudhex, shake}) => {

    useEffect(() => {
        
    }, [])

    console.log(shake)
    return (
        <div className={`cloud-wrap ${ shake ? "shake-cloud":''}`}>
            <span className="cloudhexname">{cloudhex}</span>
            <FontAwesomeIcon icon={faCloud} className="cloud" style={{color:`${cloudhex}`}} />
        </div>
    );
};

export default Cloud;

Solution

  • A good approach in this case is to use useRef() Hook to store our search field value, instead of using useState(). Because useRef() Hook does not force a re-render while useState() does. This approach is known as un-controlled way to use input field.

    You basically need to make few modifications in your code which are as follows:

    const search = useRef("");
    

    Then remove onChange={updateSearch} and value={search} from input and use a property ref={search}. So that your input looks like below:

    <input 
        className="search-bar"
        type="text"
        ref={search}
    />
    

    Then in the submit handler, you can get the value of the input field using search.current.value. So your getSearch() would look like

    const getSearch = (e) => {
        e.preventDefault();
        setClicked(true);
        setQuery(search.current.value);
        isColor();
    };
    

    Assuming user has typed an input. If not then you can set a validation before using setQuery() in the getSearch() form submit handler.

    if(search.current.value){
       setQuery();
    }
    

    Note: If you have any other controlled inputs in your project, you can change then to un-controlled inputs using refs and this way re-renders would not happen in your code.