I have created a form and I have noticed that when I submit data, they are not writing in the db (with error 400). So I have investigated and I have noticed that one api call that I make in useEffect is done about 5 time during the submit. (I have tried to comment this part and It works!)
I have a first part of form, in which with a select I make a choose, this value is used to make an api call (and there is the problem) to give back some data to use in the form.
return (
<AvForm model={isNew ? {} : userClientAuthorityEntity} onSubmit={saveEntity}>
<AvInput
id="client-application"
data-cy="application"
type="select"
className="form-control"
name="application"
onChange={handleChangeApp} // there i save the value applicationApp
required
value={applicationApp}
>
<option value="" key="0">
Select
</option>
{applicationListAPP ?
applicationListAPP.map(value => {
return (
<option value={value.appCod} key={value.appCod}>
{value.appDescription}
</option>
);
})
: null}
</AvInput>
</AvGroup>
<ShowRoleApp applicationRole={applicationApp} /> // so there I pass the value to make the api call
)
const ShowRoleApp = ({ applicationRole }) => {
const [profili, setProfili] = useState([]);
const [isLoading, setIsLoading] = useState(false);
if (!applicationRole) {
return <div />;
}
// I think that it the problem, because it recall GetProfili
useEffect(() => {
async function init() {
await GetProfili(applicationRole)
.then((res) => {
console.log('res ', res);
setProfili(res);
setIsLoading(true);
})
.catch((err) => console.log('err ', err));
}
init();
}, []);
return isLoading ? (
RenderProfili(profili, applicationRole)
) : (
<div className='d-flex justify-content-center'>
<div className='spinner-border text-primary' role='status'>
<span className='visually-hidden'></span>
</div>
</div>
);
};
const GetProfili = async (appCod) => {
const chiamata = 'myApi' + appCod.toString();
const res = await fetch(chiamata);
const result = res.clone().json();
return result;
};
const RenderProfili = (profili, applicationRole) => {
const ruoliOperatore = profili ? profili.filter(it => it.appCod.toString() === applicationRole.toString()) : null;
return (
<AvGroup>
<Label for="sce-profiloutentepa-pucCod">Profile (*)</Label>
// other code for the form...
So in your opinion how can i do to call the GetProfili without recall every time when I submit the form?
Thank you
You could define GetProfili as a custom hook an manage the useEffect call in it.
It will return the isLoading and profili instances.
Try to change your code like this.
GetProfili:
const GetProfili = (appCod) => {
const [isLoading, setIsLoading] = useState(true)
const [profili, setProfili] = useState([])
const loadProfili = async () => {
const chiamata = 'myApi' + appCod.toString();
const res = await fetch(chiamata);
setProfili(res.json())
setIsLoading(false)
}
useEffect(() => {
loadProfili()
}, [])
return { isLoading, profili };
};
ShowRoleApp:
const ShowRoleApp = ({ applicationRole }) => {
if (!applicationRole) {
return <div />;
}
const { isLoading, profili } = GetProfili(applicationRole)
return isLoading ? (
RenderProfili(profili, applicationRole)
) : (
<div className='d-flex justify-content-center'>
<div className='spinner-border text-primary' role='status'>
<span className='visually-hidden'></span>
</div>
</div>
);
};
I didn't really understand the question but I can say something that might help. The useEffect() hook gets called on every rerender of the component so if it updates 5 times its because some states inside the component get updated 5 times. Also states are updated in child components update the parent.
Related
Whenever I dispatch a search action using context and useReducer for an object in an array stored in local storage, it returns the object, but when I delete the search query from the input box, the list is not returned and the page is blank, can anyone help please?
This is my context:
const NotesContext = createContext(null);
const NotesDispatchContext = createContext(null);
const getStoredNotes = (initialNotes = InitialNotes) => {
return JSON.parse(localStorage.getItem("storedNotes")) || initialNotes;
};
export const NotesProvider = ({ children }) => {
const [NOTES, dispatch] = useReducer(NotesReducer, getStoredNotes());
useEffect(() => {
localStorage.setItem("storedNotes", JSON.stringify(NOTES));
}, [NOTES]);
return (
<NotesContext.Provider value={NOTES}>
<NotesDispatchContext.Provider value={dispatch}>
{children}
</NotesDispatchContext.Provider>
</NotesContext.Provider>
);
};
export const useNotesContext = () => {
return useContext(NotesContext);
};
export const useNotesDispatchContext = () => {
return useContext(NotesDispatchContext);
};
const App = () => {
const [query, setQuery] = useState("");
const dispatch = useNotesDispatchContext();
useEffect(() => {
if (query.length !== 0) {
dispatch({
type: "searchNotes",
query: query,
});
}
}, [query]);
return (
<div className="container">
<header>
<Title title={"Notes"} className={"app_title"} />
<form className="search_container">
<span class="material-symbols-outlined">search</span>
<input
type="search"
placeholder="search notes"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
</form>
</header>
This is my reducer function
case "searchNotes": {
[...NOTES].filter((note) =>
note.title.toLowerCase().includes(action.query)
);
}
The function seems to actually remove the all data from the local storage instead of filtering based on the query string.
Issue
When you dispatch searchNotes you are changing NOTES and the blow useEffect runs. So if the filter resulted to an empty array, there would be nothing in localStorage.
useEffect(() => {
localStorage.setItem("storedNotes", JSON.stringify(NOTES));
}, [NOTES]);
Solution
What you can do is to remove that useEffect in App that has query as dependency and dispatching searchNotes. And filter directly while rendering, something like this:
{
NOTES.filter((note) => note.title.toLowerCase().includes(query)).map((note, index) => (
<div key={index}>{note.title}</div>
))
}
And at this point you can remove searchNotes case from your reducer.
I have the parent Posts.js component which map every object in posts array. In this function I try to filter all notes have same post_id as id of the current mapped post object. All stored in filteredNotes variable. Then I pass it to each child. Now the issue. When I want to add new note in specific post, the view doesn't update (new note was not added to the list) although the database and redux store has been updated successfully.
But when I try to remove that filter function, everything works just fine so I guess the main problem is there. Any idea how to fix this? Thanks
Posts.js
const posts = useSelector((state) => state.post.posts);
const notes = useSelector((state) => state.notes.notes);
useEffect(() => {
dispatch(getPosts());
dispatch(getNotes());
}, []);
const addNoteHandle = (val) => {
dispatch(addNote({new_note: val}));
}
return (
<div className="post__page">
<div className="post__list">
{posts.map((data) => {
let filteredNotes = notes.filter((i) => i.post_id === data.id);
return <Post data={data} notes={filteredNotes} />;
})}
</div>
<PostForm addNewNote={addNoteHandle} />
</div>
);
Post.js
export const Post = ({ data, notes }) => {
return (
<div className="post__item">
<div className="post__title">{data.title}</div>
<div className="post__note">
{notes.map(note => <div>{note.text}</div>)}
</div>
</div>
);
};
NoteForm.js
const NoteForm = ({ addNewNote }) => {
const [text, setText] = useState("");
return (
<div>
<Input value={text} onChange={(e) => setText(e.target.value)} />
<Button type="primary" onClick={() => addNewNote(text)} >
<SendOutlined />
</Button>
</div>
);
};
Action
export const addNote = ({ new_note }) => async (dispatch) => {
try {
const res = await axios.post("http://localhost:9000/api/note", new_note);
dispatch({ type: ADD_NOTE, payload: res.data });
} catch (err) {
dispatch({ type: NOTE_FAIL });
}
};
Reducer
case ADD_NOTE:
return {
...state,
notes: [...state.notes, payload]
};
use useSelector to get the component value from redux store. for some reason hook setText will not work to update the page component. I had a similar problem and could not find any solution. This code may help:
let text ='';
text = useSelector((state) =>
state.yourReducer.text);
Now show your text wherever you want
this will fix the issue until you find real solution
I'm trying to make react not load until after an axios get requests finishes. I'm pretty rough on react all around, so sorry in advance.
I'm getting an array of objects
const { dogBreedsTest } = useApplicationData()
And I need it to be the default value of one of my states
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest);
However, I'm getting an error that my value is coming up as null on the first iteration of my app starting. How can I ensure that my value has completed my request before my app tries to use it?
Here is how I am getting the data for useApplicationData()
const [dogBreedsTest, setDogBreeds] = useState(null);
const getDogBreeds = async () => {
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
setDogBreeds
}
And I am importing into my app and using:
import useApplicationData from "./hooks/useApplicationData";
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest[0]);
const [breedList1, updateBreedList1] = useState(dogBreedsTest[0])
function handleOnDragEnd(result) {
if (!result.destination) return;
const items = Array.from(dogBreeds);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
for (const [index, item] of items.entries()) {
item['rank'] = index + 1
}
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0])
}
return (
<div className="flex-container">
<div className="App-header">
<h1>Dog Breeds 1</h1>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="characters">
{(provided) => (
<ul className="dogBreeds" {...provided.droppableProps} ref={provided.innerRef}>
{breedList1?.map(({id, name, rank}, index) => {
return (
<Draggable key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable>
);
})}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
</div>
)
error: TypeError: Cannot read property 'map' of null
(I am mapping the data later in the program)
const getDogBreeds = async () => {
try {
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds() // -> you are not awaiting this
}, []);
Do this instead
useEffect(() => {
axios.get('https://dog.ceo/api/breeds/list/all')
.then(res => {
const newDogList = generateDogsArray(res.data['message']);
const generatedDogs = selectedDogs(newDogList);
setDogBreeds(generatedDogs);
})
.catch(err => console.log(err));
}, []);
I know this looks awful, but I don't think you should use async/await inside useEffect
Use this in your application
useEffect will update whenever dogBreedsTest is changed. In order to make it work, start with null values and update them to the correct initial values once your async operation is finished.
const { dogBreedsTest } = useApplicationData();
const [dogBreeds, updateDogBreeds] = useState(null);
const [breedList1, updateBreedList1] = useState(null);
useEffect(() => {
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0]);
}, [dogBreedsTest]);
The problem is, that react first render and then run useEffect(), so if you don't want to render nothing before the axios, you need to tell to react, that the first render is null.
Where is your map function, to see the code? to show you it?.
I suppose that your data first is null. So you can use something like.
if(!data) return null
2nd Option:
In your map try this:
{breedList1 === null
? null
: breedList1.map(({id, name, rank}, index) => (
<Draggable
key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable> ))}
You have null, because your axios is async and react try to render before any effect. So if you say to react that the list is null, react will render and load the data from the api in the second time.
Option 1 use the optional chaining operator
dogBreedsTest?.map()
Option 2 check in the return if dogBreedsTest is an array
retrun (<>
{Array.isArray(dogBreedsTest) && dogBreedsTest.map()}
</>)
Option 3 return early
if (!Array.isArray(dogBreedsTest)) return null
retrun (<>
{dogBreedsTest.map()}
</>)
Option 4 set initial state
const [dogBreedsTest, setDogBreeds] = useState([]);
You could also add a loading state and add a loading spinner or something like that:
const [dogBreedsTest, setDogBreeds] = useState(null);
const [loading, setLoading] = useState(true)
const getDogBreeds = async () => {
setLoading(true)
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
setLoading(false)
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
loading,
setDogBreeds
}
Edit
Try to use a useEffect hook to update the states when dogBreedsTest got set.
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest?.[0] ?? []);
const [breedList1, updateBreedList1] = useState(dogBreedsTest?.[0] ?? [])
useEffect(() => {
updateDogBreeds(dogBreedsTest?.[0] ?? [])
updateBreedList1(dogBreedsTest?.[0] ?? [])
}, [dogBreedsTest])
I'm trying to display a list of Item from an API call to a list of components.
Here's my code:
function Content({...props}) {
const [list, setList] = useState([])
const [loading, setLoading] = useState(true)
const [components, setComponents] = useState([])
useEffect(() => {
if (!loading) {
return;
}
API.getInfo((data) => {
setLoading(false)
setComponents([])
setList(data)
console.log(data)
})
})
useEffect(() => {
if (components.length > 0) {
return;
}
let tmp = [...components];
for (const elem in list) {
const info = list[elem]
API.getUserById(info.userid, (data) => {
tmp.push(<InfoItem id={info._id} key={info._id} info={info} module={info.module} since="N/A" user={data.initial ? data.initial : `${data.firstname} ${data.lastname}`} {...props}/>)
setComponents(tmp)
console.log(tmp)
})
}
}, [list])
console.log(components)
return(
<div className="container-fluid">
<div className="row">
<CardHeader title="My tittle"/>
<div className ="col-lg-12">
{loading ?
<Card content={"Loading..."}/>
:
<Card content={
<div style={{height: "62vh", overflow: "hidden"}}>
<div className="list-group h-100" style={{overflowY: "scroll"}}>
{components ? components : <p>Nothing</p>}
</div>
</div>
}/>
}
</div>
</div>
</div>
)
}
As you can see I use one useEffect to handle the result from the API and another one to update the components list. But when I display Content, it's always missing one or many item from the list, even when the list have only 2 elements. And when I display tmp, it's contain all the components as well as when I display the components list. I don't know why but it seems that the update of setComponents doesn't affect the return.
If I try to add some fake elements and fast reload, all the component are poping, I don't know how to force update the list component.
If someone know where that missing elements can came from it will be great. thank you.
I think you need to wait for the async task to finish. Try to fit an await or a .then in the API.getUserById. Your data probably has not yet been retrieved by the time the setComponents(tmp) is executed.
The error is because the tmp array stay the same, even when new item are push so the setComponents doesn't render because it's still the same array, here's what I've done to fix that:
useEffect(() => {
if (!loading) {
return;
}
API.getInfo((data) => {
setLoading(false)
let all = []
for (const elem in data) {
const info = data[elem]
API.getUserById(info.patientid, (data) => {
let tmp = [...all]
tmp.push(<InfoItem id={info._id} key={info._id} info={info} module={info.module} since="N/A" patient={data.initial ? data.initial : `${data.firstname} ${data.lastname}`} {...props}/>)
all.push(tmp[tmp.length - 1])
setComponents(tmp)
console.log(tmp)
})
}
})
})
useEffect(() => {
if (!loading) {
return;
}
API.getInfo((data) => {
setLoading(false)
setComponents([])
setList(data)
console.log(data)
})
},[]);
Below I am trying to fetch data and use the onInputValue function in my other component called Search. It's working fine after first attempt, but I am getting an empty array in my initial button click
const App = () => {
const [results, setResults] = useState([]);
const onInputValue = async (input) => {
const { data } = await nasa.get('/search', {
params: {
q: input,
},
});
if(!results) {
return;
}
setResults(data.collection.items);
console.log(results);
};
return (
<div>
<Search onInputValue={onInputValue} />
</div>
);
};
import React, { useState} from 'react';
const Search = ({ onInputValue }) => {
const [input, setInput] = useState('');
return (
<div className='input-group mb-3'>
<input
type='text'
className='form-control'
placeholder='To infinity and beyond!'
onChange={(e) => setInput(e.target.value)}
/>
<div className='input-group-append'>
<button
onClick={() => {onInputValue(input)}}
className='btn btn-outline-secondary'
type='button'
>
<i className='fas fa-rocket'></i>
</button>
</div>
</div>
);
};
export default Search;
Below is the result I get.
Please advise
setResults is asynchronous if you want to check results you can use useEffect
const App = () => {
const [results, setResults] = useState([]);
useEffect(() => {
console.log(results);
}, [results])
const onInputValue = async (input) => {
const {data} = await nasa.get('/search', {
params: {
q: input,
},
});
if (!results) {
return;
}
setResults(data.collection.items);
};
return (
<div>
<Search onInputValue={onInputValue} />
</div>
);
};
This is due to asynchronous nature of setResults function - after calling it, the updated value of results will be available on the next component render - in your example you're logging in to console immediately after invoking setResults.
It could be because the setResults is batched and therefore move to the next line.
See useState batch updates.
Also
if(!results) { return; }
Is a bit suspect. ![] === false and !['someValue'] === false