Passing parameter onChange using useParams in react-router - reactjs

I am trying to implement search functionality.
I have the navbar component in searchResult component. In navbar there is a search field.
I am trying to call a 5 function simultaneously which update single state. But in my code results are not getting updated.
Navbar.js
<Link to={`/search/${searchField}`} >
<li className="nav-item">
<form id="search-bar">
<input
type="search"
placeholder="Search"
onChange={(e) => setSearchField(e.target.value)}
/>
</form>
</li>
</Link>
SearchResult.js
const { value } = useParams();
const { searchResults, setSearchResults } = useContext(search);
const findMemberFunction = (value) => {
let dataToSubmit = {
referCode: value,
};
dispatch(findMember(dataToSubmit.referCode))
.then((response) => {
let allSearchResult = searchResults;
response.payload.members.map((item) => {
allSearchResult.members.push(item);
});
setSearchResults(allSearchResult);
})
.catch((error) => {
console.log("err", error);
});
};
const findSearchTagFunction = (value) => {
let dataToSubmit = {
name: value,
};
dispatch(findTags(dataToSubmit.name))
.then((response) => {
let allSearchResult = searchResults;
response.payload.tags.map((item) => {
allSearchResult.tags.push(item);
});
setSearchResults(allSearchResult);
})
.catch((error) => {
console.log("err", error);
});
};
const findGroupFunction = (value) => {
let dataToSubmit = {
name: value,
};
dispatch(findGroup(dataToSubmit.name))
.then((response) => {
let allSearchResult = searchResults;
response?.payload?.groups.map((item) => {
allSearchResult.groups.push(item);
});
setSearchResults(allSearchResult);
})
.catch((error) => {
console.log("err", error);
});
};
const findEventFunction = (value) => {
let dataToSubmit = {
name: value,
};
dispatch(findEvent(dataToSubmit.name))
.then((response) => {
let allSearchResult = searchResults;
response.payload.events.map((item) => {
allSearchResult.events.push(item);
});
setSearchResults(allSearchResult);
})
.catch((error) => {
console.log("err", error);
});
};
const findContentFunction = (value) => {
let dataToSubmit = {
name: value,
};
dispatch(findContent(dataToSubmit.name))
.then((response) => {
let allSearchResult = searchResults;
response.payload.contents.map((item) => {
allSearchResult.contents.push(item);
});
setSearchResults(allSearchResult);
})
.catch((error) => {
console.log("err", error);
});
};
useEffect(() => {
findMemberFunction(value);
findSearchTagFunction(value);
findGroupFunction(value);
findEventFunction(value);
findContentFunction(value);
}, [value]);
This is not working as i am expecting to have parameter onChange.

Related

How can I test useEffect with async function in Jest?

I have this function inside a helper:
export const useDAMProductImages = (imageId: string) => {
const {
app: { baseImgDomain },
} = getConfig();
const response: MutableRefObject<string[]> = useRef([]);
useEffect(() => {
const getProductImages = async (imageId: string) => {
try {
const url = new URL(FETCH_URL);
const res = await fetchJsonp(url.href, {
jsonpCallbackFunction: 'callback',
});
const jsonData = await res.json();
response.current = jsonData;
} catch (error) {
response.current = ['error'];
}
};
if (imageId) {
getProductImages(imageId);
}
}, [imageId]);
return response.current;
};
In test file:
import .....
jest.mock('fetch-jsonp', () =>
jest.fn().mockImplementation(() =>
Promise.resolve({
status: 200,
json: () => Promise.resolve({ set: { a: 'b' } }),
}),
),
);
describe('useDAMProductImages', () => {
beforeEach(() => {
jest.clearAllMocks();
cleanup();
});
it('should return empty array', async () => {
const { result: hook } = renderHook(() => useDAMProductImages('a'), {});
expect(hook.current).toMatchObject({ set: { a: 'b' } });
});
});
The problem is that hook.current is an empty array. Seems that useEffect is never called. Can someone explain to me what I'm doing wrong and how I should write the test? Thank you in advance

Update my state after PATCH request - useReducer, context

I'm successfully updating my plant object to my cluster, but it takes a page reload in order for me to get that updated data. I'm assuming that I may need a useEffect to call my fetch again but I'm unsure how I would do that after my PATCH request.
Does anyone have any suggestions to how I would fetch my updated data after I've updated.
Context
import { createContext, useReducer } from 'react'
export const PlantsContext = createContext()
export const plantsReducer = (state, action) => {
switch(action.type) {
case 'SET_PLANTS':
return {
plants: action.payload
}
case 'CREATE_PLANT':
return {
plants: [action.payload, ...state.plants]
}
case 'DELETE_PLANT':
return {
plants: state.plants.filter((p) => p._id !== action.payload._id)
}
case 'UPDATE_PLANT':
return {
plants: state.plants.map((p) => p._id === action.payload._id ? action.payload : p)
}
default:
return state
}
}
export const PlantsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(plantsReducer, {
plants: null
})
return (
<PlantsContext.Provider value={{...state, dispatch}}>
{ children }
</PlantsContext.Provider>
)
}
My 'update' function inside PlantDetails component, setting a new water date
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
}
My Home component where that update should render through after PATCH request
const Home = () => {
const { plants, dispatch } = usePlantsContext();
useEffect(() => {
const fetchPlants = async () => {
console.log("called");
// ONLY FOR DEVELOPMENT!
const response = await fetch("/api/plants");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_PLANTS", payload: json });
}
};
fetchPlants();
}, [dispatch]);
return (
<div className="home">
<div className="plants">
{plants &&
plants.map((plant) => <PlantDetails key={plant._id} plant={plant} />)}
</div>
<PlantForm />
</div>
);
};
export default Home;
usePlantContext
import { PlantsContext } from "../context/PlantContext";
import { useContext } from "react";
export const usePlantsContext = () => {
const context = useContext(PlantsContext)
if(!context) {
throw Error('usePlantsContext must be used inside an PlantsContext Provider')
}
return context
}
Complete PlantsDetails Component
import { usePlantsContext } from "../hooks/usePlantsContext";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { useState } from "react";
import CalendarComponent from "./CalendarComponent";
const PlantDetails = ({ plant }) => {
const [watered, setWatered] = useState(false)
const [newWaterDate, setNewWaterDate] = useState("")
const { dispatch } = usePlantsContext();
const handleClick = async () => {
const response = await fetch("/api/plants/" + plant._id, {
method: "DELETE",
});
const json = await response.json();
if (response.ok) {
dispatch({ type: "DELETE_PLANT", payload: json });
}
};
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
// setWatered(false)
}
return (
<div className="plant-details">
<h4>{plant.plantName}</h4>
<p>{plant.quickInfo}</p>
<p>
{formatDistanceToNow(new Date(plant.createdAt), { addSuffix: true })}
</p>
<span onClick={handleClick}>delete</span>
<div>
<p>next water date: {plant.nextWaterDate}</p>
{/* <input type="checkbox" id="toWater" onChange={() => setWatered(true)}/> */}
<label value={watered} for="toWater">watered</label>
<CalendarComponent setNextWaterDate={setNewWaterDate}/>
</div>
<button onClick={updatePlant}>update</button>
</div>
);
};
export default PlantDetails;
Plant Controller
const updatePlant = async (req, res) => {
const { id } = req.params
if(!mongoose.Types.ObjectId.isValid(id)) {
return res.status(404).json({ error: "No plant" })
}
const plant = await Plant.findByIdAndUpdate({ _id: id }, {
...req.body
})
if (!plant) {
return res.status(400).json({ error: "No plant" })
}
res.status(200).json(plant)
}
Thank you for looking at my question, would appreciate any suggestion.

Change the state when clicking on a button with react

I'm trying to send and see my data status in my console log, when I click on 'Cancel' button, the status will be change by status:cancel, if I click on 'finish' button then the status is status:finish and same idea for the last one with save. Here what I've try to do but the status is not working
export default function App() {
const [data, setData] = useState({
status: ""
});
const [status, setStatus] = useState("");
const saveState = () => {
setStatus("saved");
};
const finishState = () => {
setStatus("finish");
};
const pendingState = () => {
setStatus("pending");
};
useEffect(() => {
axios
.post("")
.then((res) => {
console.log(res);
setInvitations(res.data.invitations[0]);
})
.catch((err) => {
console.log(err);
});
}, []);
function submit(e) {
e.preventDefault();
axios
.post("", {
status: data.status
})
.then((res) => {
console.log(res.data);
});
}
return (
<>
<form onSubmit={(e) => submit(e)}>
<button onClick={saveState}>Save</button>
<button onClick={finishState}> Finish</button>
<button onClick={pendingState}> Cancel</button>
</form>
</>
);
}
you can use simple setsate
export default function App() {
const [data, setData] = useState({
status: "",
});
const [status, setStatus] = useState("");
useEffect(() => {
axios
.post("")
.then((res) => {
console.log(res);
setInvitations(res.data.invitations[0]);
})
.catch((err) => {
console.log(err);
});
}, []);
function submit(e) {
e.preventDefault();
axios
.post("", {
status: data.status,
})
.then((res) => {
console.log(res.data);
});
}
return (
<>
<form onSubmit={(e) => submit(e)}>
<button onClick={() => setStatus({ status: "saved" })}>Save</button>
<button onClick={() => setStatus({ status: "finish" })}> Finish</button>
<button onClick={() => setStatus({ status: "pending" })}>
{" "}
Cancel
</button>
</form>
</>
);
}
You are using setStatus to change the status, but you are using axios.post() on your data.status
You need to either setData in your 3 functions
const saveState = () => {
setData({status:"saved"});
};
const finishState = () => {
setData({status:"finish"});
};
const pendingState = () => {
setData({status:"pending"});
};
or you can change axios.post to:
function submit(e) {
e.preventDefault();
axios
.post("", {
status: status //This is the change
})
.then((res) => {
console.log(res.data);
});
}

props change does not re-render child component

im passing a variable and two functions that changes the state of the variable as props in a child component. when i execute the functions the variable changes its state but the child component does not re-render, knowing that im using the same code in another class that calls the same child component and its working fine.
Here's the functions and the render of the child component.
onRowClickHandle = async (product) => {
BlockTimer.execute(() => {
this.props.onViewProductScreen({ product });
}, 1000);
};
async componentDidMount(){
await this.fetchReadLaterBooks();
}
async fetchReadLaterBooks(){
const user = await AsyncStorage.getItem('username');
const isLoggedIn = await AsyncStorage.getItem('isLoggedIn');
if (isLoggedIn == 1) {
await fetch(Config.backendAPI+`/readlater.php?username=${user}&test=1&select`)
.then((response) => {
return response.json();
})
.then((json) => {
if(json.length != this.state.prodList.length){
json.map((product, index) => {
this.state.prodList.push(product.id)
});
this.setState({
prodList:this.state.prodList,
isLoading:false,
});
}
this.forceUpdate();
})
.catch((error) => alert(error));
}
}
removeReadLater = async (id) => {
const user = await AsyncStorage.getItem('username');
this.setState({
prodList:this.state.prodList.filter((productId) => productId !== id),
});
await fetch(Config.backendAPI+`/readlater.php?username=${user}&idDoc=${id}&delete`)
.then((response) => response.json())
.catch((error) => alert(error));
}
addReadLater = async (id) =>{
try{
const user = await AsyncStorage.getItem('username');
//insertion dans la liste actuelle des readlater.
const joined = this.state.prodList.concat(id);
this.setState({
prodList:joined,
});
//insertion dans la base
await fetch(Config.backendAPI+`/readlater.php?username=${user}&idDoc=${id}&insert`)
.then((response) => response.json())
.catch((er) => alert(er));
}catch(error){
console.log(error);
}
};
renderItem = ({ item }) => {
return (
<ProdList
addReadLater={this.addReadLater}
removeReadLater={this.removeReadLater}
readLaterBooks={this.state.prodList}
item={item}
onRowClickHandle={this.onRowClickHandle}
/>
);
};
render() {
const {
theme: {
colors: { background, text,
dark: isDark },
},
} = this.props;
if(!this.state.isLoading){
return (
<View style={{flex:1 ,backgroundColor:background}}>
<FlatList
data={this.props.products}
renderItem={this.state.prodList ? this.renderItem : null}
/>
</View>
);
}else{
return <LogoSpinner fullStretch />;
}
}
}

useEffect after update state

My parent component use hook useEffect for get data from API and pass props to child component.
const ParentComoponent = () => {
const [adsData, setAdsData] = useState([]);
useEffect(() => {
api
.get(`MyUrl`, { headers: authHeader() })
.then((res) => {
console.log(res);
setAdsData(res.data.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return <Child adsData={adsData} />;
};
My Child component has handleDeleteClick function for delete request from API
const Child = () => {
const [deletedItem, setDeletedItem] = useState("");
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, { params: { id: id } })
.then((res) => console.log(res))
.catch((err) => console.log(err));
};
return (
<div>
// array.map Items list
<a
href=""
onClick={(e) => handleDeleteClick(e, ads.id)}
className="tables__link"
>
Delete
</a>
</div>
);
};
Delete request works successfully, but my list not updated.
How update my items list after deleted item?
You would need to pass another function that is called when a delete is executed. Something like:
const ParentComoponent = () => {
const [adsData, setAdsData] = useState([]);
const fetchData = () => {
api
.get(`MyUrl`, { headers: authHeader() })
.then((res) => {
console.log(res);
setAdsData(res.data.data);
})
.catch((err) => {
console.log(err);
});
};
const onDelete = () => {
fetchData();
};
useEffect(() => {
fetchData();
}, []);
return <Child adsData={adsData} onDelete={fetchData} />;
};
const Child = (props) => {
const [deletedItem, setDeletedItem] = useState("");
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, { params: { id: id } })
.then((res) => {
console.log(res);
props.onDelete();
})
.catch((err) => console.log(err));
};
return (
<div>
// Items list
<a
href=""
onClick={(e) => handleDeleteClick(e, ads.id)}
className="tables__link"
>
Delete
</a>
</div>
);
};
Put your delete function in the parent and pass it to the child. Then after deleting, update your list in the parent.
<ParentComponent>
const [adsData, setAdsData] = useState([]);
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, {params: {id: id}})
.then(res => {
console.log(res)
//TODO:: Implement list.pop or similar
})
.catch(err => console.log(err));
};
useEffect(() => {
api.get(`MyUrl`, { headers: authHeader() })
.then(res => {
console.log(res);
setAdsData(res.data.data);
})
.catch(err => {
console.log(err);
})
}, []);
return (
<Child
adsData={adsData}
handleClick={handleDeleteClick}
/>
)
</ParentComponent>
return (
<div>
// array.map Items list
<a href="" onClick={(e) =>
handleDeleteClick(e, ads.id)}className="tables__link">Delete</a>
</div>
)```

Resources