React useEffect hook does not call after recoil atom updated - javascript

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.

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);

Related

How to write value to localStorage and display it in input on reload?

I have an input on the page, initially it is empty. I need to implement the following functionality: on page load, the component App fetches from localStorage a value of key appData and puts it in the input. That is, so that in the localStorage I write the value to the input and when reloading it is displayed in the input. How can i do this?
I need to use useEffect
import { useEffect, useState } from "react";
export default function App() {
const [userData, setUserData] = useState("");
useEffect(() => {
localStorage.setItem("Userdata", JSON.stringify(userData));
}, [userData]);
return (
<div>
<input value={userData} onChange={(e) => setUserData(e.target.value)}></input>
</div>
);
}
Use the change event to write to the localStorage, then use an init function in the useState hook.
import { useState } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const [ userData, setUserData ] = useState(loadUserData);
const handleUserDataUpdate = e => {
const userData = e.target.value;
setUserData(userData);
saveUserData(userData);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input id="testInput" value={ userData } onChange={ handleUserDataUpdate } />
</div>;
}
If you need an example using uncontrolled inputs, here is one using useEffect :
import { useEffect } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const inputRef = useRef();
useEffect(() => {
inputRef.current.value = loadUserData();
}, []); // initial load
const handleUpdateUserData = () => {
saveUserData(inputRef.current.value);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input ref={ inputRef } id="testInput" onChange={ handleUpdateUserData } />
</div>;
}
You can set a default value for the input inside state.
const [userData, setUserData] =
useState(JSON.parse(localStorage.getItem('Userdata')) || '');
So when the component mounts (after reload), the initial userData value is taken directly from the localStorage. If it's empty, the fallback value will be set ('').
Note: Make sure to add also the onChange handler to the input.

How to receive data through props React and send it back?

I'm getting a placeholder value through props in my input component and I need to send the input value back to the main class. I'm using React but I'm not getting it. Follow my code.... The value I need to send is the value of 'usuario'
import React, { useState } from 'react';
import { EntradaDados } from './styled';
const PesqDados = ({placeholder, usuario}) => {
const [usuario, SetUsuario] = useState('')
const setValor =(e)=>{
SetUsuario(e.target.value);
}
console.log(usuario);
return(
<EntradaDados
onChange={setValor}
placeholder={placeholder}
>
</EntradaDados>
);
}
export default PesqDados;
You need to add a callback prop (onUsuarioChange) to your PesqDados component and call it with the new usuario. You have two options:
Call it from a useEffect with usuario as dependency (assuming usuario could get updated from somewhere other than setValor.
Call it from setValor, assuming that's the only place where usuario is going to get updated from.
This is how this should look:
import React, { useState } from 'react';
import { EntradaDados } from './styled';
const PesqDados = ({
placeholder,
usuario,
onUsuarioChange
}) => {
const [usuario, setUsuario] = useState('');
// Option 1:
useEffect(() => {
onUsuarioChange(usuario);
}, [usuario]);
const setValor = (e) => {
const nextUsuario = e.target.value;
setUsuario(nextUsuario);
// Option 2:
onUsuarioChange(nextUsuario);
};
return (
<EntradaDados
onChange={ setValor }
placeholder={ placeholder } />
);
}
export default PesqDados;
After studying properly, I found that I don't need to implement the function in the component page. I just needed to create a hook that calls the component's OnChange property on the component's page and then create a function just in the place where the component is installed. In this case, App.js.
Page Component....
const PesqDados = ({placeholder, Dados}) => {
return(
<EntradaDados
onChange={Dados}
placeholder={placeholder}
>
</EntradaDados>
);
}
export default PesqDados;
Page App.js
function App() {
const [usuario, SetUsuario] = useState('Aguardando Dados...')
const setValor =(e)=>{
SetUsuario(e.target.value);
}
const teste = ()=>{
alert("O usuário digitado foi : "+usuario)
};
return (
<>
<div className='divRepos'>
<div className='bloco'>
<div className='pesquisar'>
<p>{usuario}</p>
<PesqDados
placeholder={"Digite um username válido"}
Dados={setValor}
/>
<Button nomeBotao={"Pesquisar Perfil"}
onClick={teste}/>
</div>
...

LocalStorage doesn't set items into itself

I've got a bug with LocalStorage on react.js. I try to set a todo into it, but it doesn't load. This is the code:
import React, { useState, useRef, useEffect } from 'react';
import './App.css';
import TodoList from './TodoList';
const { v4: uuidv4 } = require('uuid');
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useState([]);
const TodoNameRef = useRef()
useEffect(() => {
const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
if (storedTodos) setTodos(storedTodos)
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
}, [todos])
function HandleAddTodo(e){
const name = TodoNameRef.current.value
if (name==='') return
setTodos(prevTodos => {
return[...prevTodos, { id:uuidv4(), name:name, complete:false}]
})
TodoNameRef.current.value = null
}
return (
<>
<TodoList todos={todos}/>
<input ref={TodoNameRef} type="text" />
<button onClick={HandleAddTodo}>Add todo</button>
<button>clear todo</button>
<p>0 left todo</p>
</>
)
}
export default App;
This is TodoList.js
import React from 'react'
import Todo from './Todo';
export default function TodoList({ todos }) {
return (
todos.map(todo =>{
return <Todo key ={todo.id} todo={todo} />
})
)
}
And as last Todo.js:
import React from 'react'
export default function Todo({ todo }) {
return (
<div>
<label>
<input type="checkbox" checked={todo.complete}/>
{todo.name}
</label>
</div>
)
}
What the code has to do is load a todo into the local storage, and after refreshing the page reload it into the document. The code I implemented
I just started with react but I hope anyone can pass me the right code to make it work. If anyone need extra explenation, say it to me.
Kind regards, anonymous
Try to decouple your local storage logic into it's own react hook. That way you can handle getting and setting the state and updating the local storage along the way, and more importantly, reuse it over multiple components.
The example below is way to implement this with a custom hook.
const useLocalStorage = (storageKey, defaultValue = null) => {
const [storage, setStorage] = useState(() => {
const storedData = localStorage.getItem(storageKey);
if (storedData === null) {
return defaultValue;
}
try {
const parsedStoredData = JSON.parse(storedData);
return parsedStoredData;
} catch(error) {
console.error(error);
return defaultValue;
}
});
useEffect(() => {
localStorage.setItem(storageKey, JSON.stringify(storage));
}, [storage]);
return [storage, setStorage];
};
export default useLocalStorage;
And you'll use it just like how you would use a useState hook. (Under the surface it is not really more than a state with some side effects.)
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useLocalStorage(LOCAL_STORAGE_KEY, []);
const handleAddTodo = event => {
setTodos(prevTodos => {
return[...prevTodos, {
id: uuidv4(),
name,
complete: false
}]
})
};
return (
<button onClick={HandleAddTodo}>Add todo</button>
);
}
You added the getItem and setItem methods of localStorage in two useEffect hooks.
The following code intializes the todo value in localStorage when reloading the page.
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
}, [todos])
So you need to set the todo value in HandleAddTodo event.
I edited your code and look forward it will help you.
import React, { useState, useRef, useEffect } from 'react';
import './App.css';
import TodoList from './TodoList';
const { v4: uuidv4 } = require('uuid');
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useState([]);
const TodoNameRef = useRef()
useEffect(() => {
const storageItem = localStorage.getItem(LOCAL_STORAGE_KEY);
const storedTodos = storageItem ? JSON.parse(storageItem) : [];
if (storedTodos) setTodos(storedTodos)
}, []);
function HandleAddTodo(e){
const name = TodoNameRef.current.value;
if (name==='') return;
const nextTodos = [...todos, { id:uuidv4(), name:name, complete:false}];
setTodos(nextTodos);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(nextTodos));//replace todos to nextTodos
TodoNameRef.current.value = null
}
return (
<>
<TodoList todos={todos}/>
<input ref={TodoNameRef} type="text" />
<button onClick={HandleAddTodo}>Add todo</button>
<button>clear todo</button>
<p>0 left todo</p>
</>
)
}
export default App;
There is no need of adding the second useEffect.
You can set your local Storage while submitting in the handleTodo function.
Things you need to add or remove :
Remove the Second useEffect.
Modify your handleTodo function :
const nextTodos = [...todos, { id:uuidv4(), name:name,complete:false}];
setTodos(nextTodos);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(nextTodos));
Note: Make sure you won't pass todos instead of nextTodos as we know setTodos is an async function There might be a chance we are setting a previous copy of todos

How to search field CARDS in React using React Hooks

I couldn't find how to make the field (input) work to filter the searches into cards:
can someone help me to operate the input field ?
**Notes.js**
import React, { useEffect, useState } from 'react'
import NoteCard from '../components/NoteCard'
import Masonry from 'react-masonry-css'
import Notess from '../components/CardList'
export default function Notes({details }) {
const [searchField, setSearchField] = useState("");
const [notes, setNotes] = useState([]);
const filteredNote = notes.filter(
note => {
return (
note.title.toLowerCase().includes(searchField.toLowerCase())
);});
const handleChange = e => {
setSearchField(e.target.value);
};
return (
<Container>
<input type='search' onChange = {handleChange} />
<Notess filter={filteredNote} />
</Container>
) }
someone can help me to operate the input field ?

Initial value for useState isn't updating when using Virtual Keyboard

Beginner here. Trying to get react-simple-keyboard working with Gatsby & React.
I initialise my form with some state (firstName: "Johnn"). This should be the initial state. I want the user to be able to modify this name, and save the modified version in state.
I initialise my state here:
const [inputs, setInputs] = useState({
firstName: "Johnn"
})
When I click on the field and press a button on the virtual keyboard (a letter, say), it deletes the content of the whole field and puts the letter there, instead of adding the letter to whats already in there. Also: Clicking on the field and pressing backspace (on the react-simple-keyboard) does not do anything. Why is this?
import React, { useRef, useState, useContext, useEffect } from "react"
import styled from "styled-components"
import ReactDOM from "react-dom"
import Keyboard from "react-simple-keyboard"
import "react-simple-keyboard/build/css/index.css"
import Layout from "#components/layout"
import { useForm } from "react-hook-form"
import { Flex, Box } from "rebass/styled-components"
import Input from "#atoms/Input"
import {
GlobalDispatchContext,
GlobalStateContext,
} from "../context/GlobalContextProvider"
function App() {
const dispatch = useContext(GlobalDispatchContext)
const state = useContext(GlobalStateContext)
const [inputs, setInputs] = useState({
firstName: "Johnn",
// firstName: state.customers[state.currentCustomer].firstName,
})
const [layoutName, setLayoutName] = useState("default")
const [inputName, setInputName] = useState("default")
const [isShiftPressed, setShiftPressed] = useState(false)
const [isCaps, setCaps] = useState(false)
const [isKeyboardVisible, setKeyboardVisible] = useState(false)
const { register, handleSubmit, errors } = useForm()
const keyboard = useRef()
const onChangeAll = newInputs => {
/**
* Here we spread the inputs into a new object
* If we modify the same object, react will not trigger a re-render
*/
setInputs({ ...newInputs })
}
const handleShift = () => {
const newLayoutName = layoutName === "default" ? "shift" : "default"
setLayoutName(newLayoutName)
}
const onKeyPress = button => {
if (isShiftPressed === true && !isCaps) {
setShiftPressed(false)
handleShift()
}
if (button === "{lock}") {
setCaps(true)
}
if (button === "{shift}" || button === "{lock}") {
setShiftPressed(true)
handleShift()
}
}
const onChangeInput = event => {
const inputVal = event.target.value
setInputs({
...inputs,
[inputName]: inputVal,
})
keyboard.current.setInput(inputVal)
}
const getInputValue = inputName => {
return inputs[inputName] || ""
}
return (
<Layout>
<Flex flexDirection="column" style={{ height: "100%" }}>
<form onSubmit={handleSubmit(onSubmit)}>
<Input
id="firstName"
name="firstName"
value={getInputValue("firstName")}
onFocus={() => {
setInputName("firstName")
}}
placeholder={"First Name"}
onChange={onChangeInput}
/>
</form>
<Keyboard
keyboardRef={r => (keyboard.current = r)}
inputName={inputName}
layoutName={layoutName}
onChangeAll={onChangeAll}
onKeyPress={onKeyPress}
/>
</Flex>
</Layout>
)
}
export default App
You might need to use useEffect hook set the initial keyboard value, and on subsequent changes and also remove keyboard.current.setInput(inputVal).
const {firstName} = input;
useEffect(() => {
keyboard.current.setInput(firstName);
}, [firstName]);
This will make sure that the initial and subsequent changes of firstName is set in keyboard instance.
Code Sandbox: https://codesandbox.io/s/distracted-aryabhata-j3whs?file=/src/index.js

Categories

Resources