When I use modalOpen in a onClick function it wont fetch api on the 1st click causing the code to break but it will on 2nd click what can cause it
// get anime result and push to modal function
const modalAnime = async () => {
const { data } = await fetch(`${base_url}/anime/${animeId}`)
.then((data) => data.json())
.catch((err) => console.log(err));
SetAnimeModalData(data);
};
I am trying to get the fetch to work on the first click but it doesn't until second or third click
const modalOpen = (event) => {
SetAnimeId(event.currentTarget.id);
SetModalVisible(true);
modalAnime();
console.log(animeId);
};
const modalClose = () => {
SetModalVisible(false);
SetAnimeId("");
};
return (
<div className="app">
<Header
searchAnime={searchAnime}
search={search}
SetSearch={SetSearch}
mostPopular={mostPopular}
topRated={topRated}
/>
{loadingState ? (
<ResultLoading />
) : (
<Results
animeResults={animeResults}
topRated={topRated}
mostPopular={mostPopular}
modalOpen={modalOpen}
/>
)}
{modalVisible ? <AnimeInfoModal modalClose={modalClose} /> : <></>}
</div>
);
the modal opens fine but the ID isn't captured until the second or third click
I have more code but Stack Overflow won't let me add it.
SetAnimeId() won't update the animeId state property until the next render cycle. Seems like you should only update the visible and animeId states after you've fetched data.
You should also check for request failures by checking the Response.ok property.
// this could be defined outside your component
const fetchAnime = async (animeId) => {
const res = await fetch(`${base_url}/anime/${animeId}`);
if (!res.ok) {
throw res;
}
return (await res.json()).data;
};
const modalOpen = async ({ currentTarget: { id } }) => {
// Await data fetching then set all new state values
SetAnimeModalData(await fetchAnime(id));
SetAnimeId(id);
SetModalVisible(true);
};
Related
I would like to ask if I have the variable useState in the component which is used as a condition that determines the element inside it will appear or not. How to mock the variable? So that I can test the element inside the condition if the value is 'login'.
const [dataHistory,seDataHistory] = useState("")
const [data,setData] = useState("firstTimeLogin")
const func2 = () => {
getData({
item1: "0",
}).
then((res) => {
funct();
})
}
const funct = () =>
{setData("login")}
return
{data === "firstTimeLogin" && (
<div><button onClick="funct2()">next</button></div>
)}
{data === "login" && (
<div>flow login</div>
)}
Firstly, you need to add data-testid for your button
{data === "firstTimeLogin" && (
<div><button onClick="funct2" data-testid="next-button">next</button></div>
)}
You called onClick="funct2()" which is to trigger funct2 immediately after re-rendering, so I modified it to onClick="funct2".
Note that you also can use next content in the button for the event click but I'd prefer using data-testid is more fixed than next content.
In your test file, you should mock getData and call fireEvent.click to trigger funct2()
import { screen, fireEvent, render } from '#testing-library/react';
//'/getData' must be the actual path you're importing for `getData` usage
jest.mock('/getData', () => {
return {
getData: () => new Promise((resolve) => { resolve('') }) // it's mocked, so you can pass any data here
}
})
it('should call funct', async () => {
render(<YourComponent {...yourProps}/>)
const nextButton = await screen.findByTestId("next-button"); //matched with `data-testid` we defined earlier
fireEvent.click(nextButton); //trigger the event click on that button
const flowLoginElements = await screen.findByText('flow login'); //after state changes, you should have `flow login` in your content
expect(flowLoginElements).toBeTruthy();
})
You can create a button and onClick of the button call funct()
so when i do my login and i try to redirect to this page it appears me that error it must be something with useEffect i dont know
here is my code
useEffect(() => {
let canUpdate = true;
getVets().then((result) => canUpdate && setVets(result));
return function cleanup() {
canUpdate = false;
};
}, []);
const getVets = async () => {
const url = 'http://localhost:8080/all/vet';
const response = await fetch(url);
const data = await response.json();
setVets(data);
};
// const { appointmentType, animalID, room, hora, notes } = this.state;
return (
<React.Fragment>
<div class='title'>
<h5>2 médicos vetenários disponíveis</h5>
</div>
<div>
{vets.map((data) => (
<ObterMedicos
key={data.name}
name={data.name}
specialty={data.specialty}
/>
))}
</div>
</React.Fragment>
);
}
vets might not have data on the first render and when the code tries to execute map operation on it, it gets undefined.map, which is not allowed.
You can either set vets to empty array at the time of defining the state
const [vets,setVets] = useState([]);
or just check on vets using Optional chaning (?) before using the map function:
{vets?.map((data) => (
<ObterMedicos
key={data.name}
name={data.name}
specialty={data.specialty}
/>
))}
I am learning react and I am trying to use a text input in a dynamic fetch request
My component is defined as ...
export default testpage = () => {
const [state, setState] = React.useState({})
let handleChange = (event) => {
setState({input: event.target.value})
}
async function buttonClick (input) {
console.log(state.input)
await fetch(`http://localhost:8080/api/${input}`)
.then(response => response.json())
.then(data => setState({...state, data}))
render(
<input type={'text'} onChange={handleChange.bind(this)} />
<Button onClick={() => buttonClick(state.input)}>test</Button>
)
}
My problem relates to useState updating asynchronously. If I enter a number ie. 4 into the input box and then click the button. The first time I click the button the fetch fails because undefined is passed to the fetch statement because the state hasn't been updated. If I click the button a second time the fetch succeeds. I have read into the useEffect hook but I am unable to figure out how to apply it to my situation.
Change the code to keep input's value directly in the state. The state value not need to be an object - it can be a string, number or null if that’s all you need.
const TestPage = () => {
const [postId, setPostId] = useState(null);
async function buttonClick() {
await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}/comments`)
.then(response => response.json())
.then(data => console.log(data));
}
return (
<div>
<input onChange={e => setPostId(e.target.value)} />
<button onClick={buttonClick}>test</button>
</div>
);
};
The comonent already works as expected - it downloads data on every button click. It requires a display logic and a proper error handling, but I leave it for clarity.
You mentioned useEffect and here is the example of how you can use it:
function Test() {
const [postId, setPostId] = useState(null);
const [data, setData] = useState([]);
useEffect(() => {
async function getComments() {
if (Number(postId)) {
await fetch(
`https://jsonplaceholder.typicode.com/posts/${postId}/comments`
)
.then(response => response.json())
.then(data => setData(data));
} else { setData([]); }
}
getComments();
}, [postId]);
const comments = data
? data.map(comment => <li key={comment.id}>{comment.body}</li>)
: [];
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<input type={"text"} onChange={e => setPostId(e.target.value)} />
{comments.length > 0 ? <ul>{comments}</ul> : <span>Write correct post ID (number 1-100)</span>}
</div>
);
}
But useEffect changes how you interact with your component. It runs an effect after rendering new state, meaning it runs right after changing input's value. Meaning, you don't need the <button> at all.
Because you begin request on button click it is better to use useCallback hook. It returns the same function on every button click as long as postId (input's value) doesn't change. You can use this function the same as you used buttonClick in first example:
const TestPage = () => {
const [postId, setPostId] = useState(null);
const handleClick = useCallback(() => {
async function getData() {
await fetch(
`https://jsonplaceholder.typicode.com/posts/${postId}/comments`
)
.then(response => response.json())
.then(data => console.log(data));
}
getData();
}, [postId]);
return (
<div>
<input onChange={e => setPostId(e.target.value)} />
<button onClick={handleClick}>test</button>
</div>
);
};
I have the following code :
export default function ProjectView({filteredProjects,revenueGroups}) {
const [projects,setProjects] = useState();
useEffect(() => {
const aap = filteredProjects.map(filteredObject => {
getProjectFields(filteredObject.projectId).then(res => {
filteredObject.rekt = res.data[0].id;
})
return filteredObject;
})
setProjects(aap);
},[filteredProjects])
And the rendered component :
return (
<div className='main'>
{projects.map(project => (
<div className='view-container' key={project._id}>
{console.log(project)}
</div>
))}
</div>
)
This works fine , when i console.log(project) like above it shows the following :
{
projectName: "privaye"
rekt: "project:1b1126ebb28a2154feaad60b7a7437df"
__proto__: Object
}
when i console.log(projectName) it shows the name, but when i console.log(project.rekt) it's undefined...
eventhough its there when i console.log(project)
EDITED
I didn't notice the was a promise inside :P
useEffect(() => {
fetchThings()
},[filteredProjects])
const fetchThings = async () => {
const promArr = filteredProjects.map(filteredObject => {
return getProjectFields(filteredProject.project.id)
})
const filteredObjects = await Promise.all(promArr)
const projectsMod = filteredObjects.map(fo => ({
...fo,
rekt: fo.data[0].id,
}))
}
Maybe an approach like this will help you with the asyncronous problem
console.log isn't rendering anything so maybe React doesn't try to refresh this part of the DOM when projects state is updated.
You can stringify the object to check the content of the variable inside the return
return (
<div className='main'>
{projects.map(project => (
<div className='view-container' key={project._id}>
<span>{ JSON.stringify(project) }</span>
</div>
))}
</div>
)
You are running promise, after this promise will return value you set rekt, but promise will work god knows when, and most probably you check value before this promise resolved.
getProjectFields(filteredObject.projectId).then(res => {
// THIS CODE CAN RUN AFTER RENDERING
filteredObject.rekt = res.data[0].id;
})
so first you wait till promises will complete and only then set new state
const ourPreciousPromises = filteredProjects.map(filteredObject =>
getProjectFields(filteredObject.projectId).then(res => {
filteredObject.rekt = res.data[0].id;
return {...filteredObject}
})
})
Promise.all(ourPreciousPromises).then(newObjects =>
setProjects(newObjects)
)
I am learning reactjs and trying to implement small things for practice. The idea is simple, to add records (feedbackTasks) to the database and list these records (when first time page is loaded and later when a new record is added). Please see the image below.
The main component is ManageFeedbackTasks. I keep the list of the feedbackTask items in its state (st_feedbackTaskList). The update of this list is performed through add_to_st_feedbackTask function. If the first time this list is generated, all the fetched data (coming from PrintFeedbackTasks component) is set to the st_feedbackTaskList. If not, only the added item (coming from ShowAddingFeedbackTaskForm) is inserted in the list.
export function ManageFeedbackTasks() {
const [st_feedbackTaskList, setFeedbackTaskList] = useState([]);
const add_to_st_feedbackTask = (data) => {
if (st_feedbackTaskList.length == 0) {
setFeedbackTaskList(data);
} else {
const { id, title, description } = data;
setFeedbackTaskList([...st_feedbackTaskList, { id, title, description }]);
}
}
return (
<>
<ShowAddingFeedbackTaskForm onAddingItem={add_to_st_feedbackTask} />
<PrintFeedbackTasks onListingFeedbackTasks={add_to_st_feedbackTask} feedbackTasks={st_feedbackTaskList} />
</>
);
}
Below is the PrintFeedbackTasks function. This function receives the feedbackTasks list from the main component ManageFeedbackTasks. This list is first time fetched from the database using fetchFeedbackTasks. Inside fetchFeedbackTasks, props.onListingFeedbackTasks(response.data) sends the fetched list back to the main component to update the state (st_feedbackTaskList).
const PrintFeedbackTasks = (props) => {
const [st_isInitialized, setInitialized] = useState(false);
const fetchFeedbackTasks = () => {
axios.get('api/FeedbackTask/Index')
.then(response => props.onListingFeedbackTasks(response.data))
.catch(error => console.log(error));
}
useEffect(() => {
if (!st_isInitialized) {
fetchFeedbackTasks();
}
setInitialized(true);
});
return (
<React.Fragment>
{
props.feedbackTasks.map(taskItem =>....
}
</React.Fragment>
);
}
The component below displays the Add form and handles the form submission. When a new item is added, this new item is again sent back to the main component using props.onAddingItem.
const ShowAddingFeedbackTaskForm = (props) => {
const [st_title, setTitle] = useState('');
const [st_description, setDescription] = useState('');
const handleSubmit = async (event) => {
event.preventDefault();
await axios(...)
.then(function (response) {
setTitle('');
setDescription('');
//This will update the list of the feedback task in the main component
props.onAddingItem({
id: response.data,
title: st_title,
description: st_description
});
//GET THE ID HERE
console.log(response.data);
}).catch(function (error) {
console.log(error);
});
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder="Title..."
type="text"
value={st_title}
onChange={(event) => setTitle(event.target.value)}
/>
<input
placeholder="Description..."
type="text"
value={st_description}
onChange={(event) => setDescription(event.target.value)}
/>
<button>Add Feedback Task</button>
</form>
);
}
I wonder if this way of lifting and managing the state is robust. Any suggestions to improve the code? Also, I wonder if I should put these components into their own pages (for example, one page or adding a record and another one for listing). Would this make more sense in the react world?
The idea to lift the state up to the parent is correct. However due to your code structure you could be causing a lot of re-renders and a few performance optimizations can be made in your solution. One more thing is that instead of fetching feedbackTasks in PrintFeedbackTasks component you should do it in the parent itself. Also useEffect takes a second parameter which you can use to execute it on initial mount
You can use useCallback hook to memoize functions too.
ManageFeedbackTasks
export function ManageFeedbackTasks() {
const [st_feedbackTaskList, setFeedbackTaskList] = useState([]);
const fetchFeedbackTasks = useCallback(() => {
axios.get('api/FeedbackTask/Index')
.then(response => props.onListingFeedbackTasks(response.data))
.catch(error => console.log(error));
}, []);
useEffect(() => {
fetchFeedbackTasks();
}, []);
const add_to_st_feedbackTask = useCallback((data) => {
setFeedbackTaskList(prevTaskList => {
if (prevTaskList.length == 0) {
return data;
} else {
const { id, title, description } = data;
return [...prevTaskList, { id, title, description }];
}
});
}, [])
return (
<>
<ShowAddingFeedbackTaskForm onAddingItem={add_to_st_feedbackTask} />
<PrintFeedbackTasks onListingFeedbackTasks={add_to_st_feedbackTask} feedbackTasks={st_feedbackTaskList} />
</>
);
}
PrintFeedbackTasks
const PrintFeedbackTasks = (props) => {
return (
<React.Fragment>
{
props.feedbackTasks.map(taskItem =>....
}
</React.Fragment>
);
}
As far as the idea to split show and update TaskList is concerned it as product decision which can be made depending on how long is the list of fields that the user needs to fill at once