Search code examples
javascriptreactjsjsxuse-effectrecoiljs

React useEffect hook does not call after recoil atom updated


I'm using recoiljs as my state manager for my react project, and one of my components doesn't call it's useEffect when a recoil atom changes from another file. Here is my main component that reads from an atom.

import React, {useState, useEffect} from 'react'
import '../css/MeadDeadline.css'
import {getNearestDate} from '../chromeAPI/retrieveDeadlineJSON'

import DeadlineList from '../atoms/deadlinelist'
import {useRecoilValue} from 'recoil'

export default function MainDeadline() {
    // get the date and the stuff from chrome storage
    const [school, setSchool] = useState("");
    const [date, setDate] = useState("");
    let [deadlinelist, setDeadlineList] = useRecoilValue(DeadlineList);

    useEffect(() => {
        const nearest = getNearestDate(deadlinelist);
        const len = nearest.length;

        if (len === 0){
            setSchool("No schools registered");
            setDate("");

        } else if (len === 1){
            setSchool(nearest[0].school);
            setDate(nearest[0].date);
            
        } else {
            // we need to render a lot of stuff
            console.log("error");
        }

    }, [deadlinelist]);

    return (
        <>
            <div className="MainDeadline">
                <div className='school'>{school}</div>
                <div classNmae='date'>{date}</div>
            </div>
        </>
    )
}

Here is my atom file

import {atom} from 'recoil'

const DeadlineList = atom({
    key: "deadlinelist",
    default: []
}); 
export default DeadlineList;

and here is the form that I'm submitting in

import React, {useState} from 'react'
import '../css/InputForm.css'
import checkList from '../utils/checkList'
import checkDate from '../utils/checkDate'
import {storeNewDeadline} from '../chromeAPI/storeNewDeadline'

import {useRecoilState} from 'recoil'
import DeadlineList from '../atoms/deadlinelist'
import SchoolList from '../atoms/schoollist'

export default function InputForm () {
    const [inputschool, setInputSchool] = useState('');
    const [inputdate, setInputDate] = useState('');
    const [invalidschool, setInvalidSchool] = useState(false);
    const [invaliddate, setInvalidDate] = useState(false);
    const [badschool, setBadSchool] = useState('');
    const [baddate, setBadDate] = useState('');

    const [schoollist, setSchoolList] = useRecoilState(SchoolList);
    const [deadlinelist, setDeadlineList] = useRecoilState(DeadlineList);

    const validateForm = () => {
        // check to make sure its not in the list
        const valschool = checkList(schoollist, inputschool);
        if (!valschool){
            setInvalidSchool(true);
            setBadSchool(inputschool);
        } else {
            setInvalidSchool(false);
            setBadSchool("");
        }
        // check to make sure the date hasnt been reached yet
        const valdate = checkDate(inputdate);
        if (!valdate){ // add MSIN1DAY becauase the day value is 0 indexed so conflicts with Date() and date input
            setInvalidDate(true);
            setBadDate(inputdate);
        }
        else {
            setInvalidDate(false);
            setBadDate("");
        }

        return !invalidschool && !invaliddate; // want both to be valid
    }

    const handleSubmit = async(event) => {
        event.preventDefault();
        
        // validate the form
        if (validateForm()){
            storeNewDeadline(inputschool, inputdate);

            // change schoollist state
            let slist = schoollist;
            slist.push(inputschool);
            setSchoolList(slist);

            // change deadlinelist state
            let dlist = deadlinelist;
            dlist.push({
                "school": inputschool,
                "date": inputdate
            });
            setDeadlineList(dlist);

            console.log(deadlinelist, schoollist);
        }
    }

    const handleChange = (event, fieldname) => {
        switch (fieldname) {
            case "inputschool":
                setInputSchool(event.target.value);
                break;
            
            case "inputdate":
                setInputDate(event.target.value);
                break;
            
            default:
                break;
        }
    }

    return (
        <form className='InputForm' onSubmit={handleSubmit}>
            <h3>Enter New School</h3>

            <div id='inputname' className='Inputer'>
                <p>School Name</p>
                <input 
                    type='text'
                    onChange={e => {handleChange(e, 'inputschool')}}
                    value={inputschool}
                    required 
                />
                {invalidschool ? <p>{badschool} is already registered</p> : null}
            </div>

            <div id='inputdate' className='Inputer'>
                <p>Deadline Date</p>
                <input
                    type='date'
                    onChange={e => {handleChange(e, 'inputdate')}}
                    value={inputdate}
                    required
                />
                {invaliddate ? <p>{baddate} is invalid</p> : null}
            </div>

            <div id='inputsubmit' className='Inputer'>
                <p>Submit</p>
                <input type='submit' required></input>
            </div>
            
        </form>
    )
}

If you want to just see file for file the github is here The main component is src/components/MainDeadline.jsx , src/atoms/deadlinelist , src/components/InputForm.jsx

My main problem is when the user inputs something in the form, it's supposed to update the state, but the main component doesn't update.

Please tell me if I can improve my code in any way this is my first react project.


Solution

  • When dealing with arrays in state hooks, you need to clone the array as you do the set function.

    Also, instead of this: let [deadlinelist, setDeadlineList] = useRecoilValue(DeadlineList);

    I would do this: const [deadlinelist, setDeadlineList] = useRecoilState(DeadlineList);