useEffect and async() - reactjs

In the useEffect() hook, I am basically trying to add an 'id' to each 'item'object mapped to tempData, by incrementing the lastIndex state in each iteration. However, all the item.id s that have been mapped returned 0 (the initial state value).
I am guessing there is something wrong with invoking the setLastIndext function in the iterations? Thanks.
const SearchAppointments = React.memo(() => {
const [data, setData] = useState([ ])
const [lastIndex, setLastIndex] = useState(0)
useEffect( () => {
const fetchData = async() => {
var response = await fetch('../data.json');
var result = await response.json()
var tempData = result.map( item => {
item.id = lastIndex;
setLastIndex(lastIndex => lastIndex + 1);
return item
})
setData(tempData)
};
fetchData();
}, [])
return (
<div>
</div>
);
})

setLastIndex is async function the value of lastIndex will only be updated in next render, but result.map is sync function ==> lastIndex always 0 in result.map
You can try this:
const SearchAppointments = React.memo(() => {
const [data, setData] = useState([ ])
// You not really need this lastIndex state for setting id for your data item, but somehow you want it after setData you can keep this and set to the last index of the item in fetched data
const [lastIndex, setLastIndex] = useState(0)
useEffect( () => {
const fetchData = async() => {
var response = await fetch('../data.json');
var result = await response.json()
var tempData = result.map( (item, index) => ({...item, id: index}))
setLastIndex(tempData.length -1)
setData(tempData)
};
fetchData();
}, [])
return (
<div>
</div>
);
})

You will try this:
const SearchAppointments = React.memo(() => {
const [data, setData] = useState([]);
const fetchData = async() => {
const response = await fetch('../data.json');
const result = await response.json();
setData(result.map( (item, index) => ({...item, id:index})))
};
useEffect( () => {
fetchData();
}, [])
return (
<div>
</div>
);
})```

Do you really need lastIndex?
There's data.length.
It can be used inside setData(lastData=>...)

Related

React useEffect does not fetch paramter into React useState

Why does my article state doesnt have the same Parameter like my cart.filter element.
What am I doing wrong, using the useState Hook.
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
};
setArticle(cart.filter((e) => e.id === id));
fetchCartAndPrice();
}, []);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);
}
In the moment that you are trying set the articles don't have carts yet. You need wait the cart update creating an exclusive useEffect to cart. Something like this:
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
};
fetchCartAndPrice();
}, []);
useEffect(() => {
setArticle(cart.filter((e) => e.id === id));
}, [cart]);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);
When you trigger your function setArticle() the async function which fetch the cart didn't finished yet ... So it can't "filter" the (still empty) cart ...
You need to execute that filter thing after the cart is set :
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
};
}, []);
useEffect(() => {
// --> ensure we are not in the "init step"
if (cart.length) {
setArticle(cart.filter((e) => e.id === id));
// Not sur where this one belongs ... :
fetchCartAndPrice();
}
}, [cart]);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);
Another maner to do so is to set the article at the same place of the cart :
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
setArticle(sess.cart.filter((e) => e.id === id));
};
}, []);
Gabriel Furlan gave a great solution.
I would use the async declaration at the top level of the useEffect hook.
Ex.
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(async () => {
const sess = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
}, []);
useEffect(() => {
setArticle(cart.filter((e) => e.id === id));
}, [cart]);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);

React data content is disappearing on callback

there is something strange happening with my code. My variable data (useState) is randomly empty when I call my callback when onpopstate event is fired.
I have 2 components and 1 hook used like that:
const Parent = props => {
const {downloadData} = useData();
const [data, setData] = useState([]);
const [filteredData, setFilteredData] = useState();
const loadData = async () => setData(await downloadData());
useEffect(() => {
loadData();
}, []);
return <FilterPage data={data} onDataChange={data => setFilteredData(data)} />
}
const FilterPage = ({data, onDataChange} => {
const {saveHistoryData} = useHistoryState('filter', null, () => {
updateFilters();
});
const filter = (filterData, saveHistory = true) => {
let r = data; // data is randomly empty here
...
if(saveHistory)saveHistoryData(filterData);
onDataChange(r);
}
});
// my hook
const useHistoryState = (name, _data, callback) => {
const getHistoryData = () => {
const params = new URLSearchParams(window.location.search);
try{
return JSON.parse(params.get(name));
}catch(err){
return null;
}
}
const saveHistoryData = (data) => {
const params = new URLSearchParams(window.location.search);
params.set(name, JSON.stringify(data || _data));
window.history.pushState(null, '', window.location.pathname + '?' + params.toString());
}
const removeHistoryData = () => {
const params = new URLSearchParams(window.location.search);
params.delete(name);
window.history.pushState(null, '', window.location.pathname + '?' + params.toString());
}
const watchCallback = () => {
callback(getHistoryData());
};
useEffect(() => {
let d = getHistoryData();
if(d)watchCallback();
window.addEventListener('popstate', watchCallback);
return () => window.removeEventListener('popstate', watchCallback);
}, []);
return {getHistoryData, saveHistoryData, removeHistoryData};
}
Any suggestions please
Edit
I'm sorry is not the entire code, just a draft. I download the data using async function. The data is loading fine but is empty only if we call the callback from the hook.
You need to use setData to populate data
First of all you are not calling setData() anywhere.
You are using data but not setData and you are using setFilteredData but not filteredData.
Furthermore it doesn't look like updateFilters() exist within FilterPage.
You are passing onDataChange to <Filterpage> but you are not using the property, only ({data}) which explains why it's empty. You might want to update the FilterPage signature: const FilterPage = ({data, onDataChange}) => {} and use the onDataChange

How can I get the data back with useState?

const noticeList = useSelector(state => state.noticeReducer.list) //현재 페이지에 띄워질 공지 리스트
//page
const [current, setCurrent] = useState(0); //현재 페이지
const pageInfo = useSelector(state => state.noticeReducer.pageInfo); //전체 페이지 정보
const [keyword, setkeyword] = useState(null); //키워드 state
const [searchedList, setsearchedList] = useState(noticeList); // 검색 할때만 사용하므로 여기에 사용
const [active, setactive] = useState("");
console.log(searchedList)
const Search = () => {
const data = axios.post('/noticeList',{
keyword : keyword,
})
.then(res => res.data)
.catch(err => console.log(err));
setsearchedList(data)
}
useEffect(() => {
return (
dispatch(getNoticeList(current+1,keyword)) //공지사항 목록 받아오기
)
}, [dispatch, current])
//화면에 출력하기 위해 map 함수를 활용
let homeNotice = searchedList.map(
item =>
{
return(
<NoticeDetail key = {item.noticeId} title = {item.title} active = {active} setactive = {setactive} content = {item.content}/>
)
}
)
I saved the data in Redux in the state with useEffect.
I want to overwrite the data in the same state when searching in the search function. What should I do?
Uncaught TypeError: searchedList.map is not a function
You are not using promises correctly you should set the data with the result of the promise, not with the promise:
const Search = () => {
axios
.post('/noticeList', {
keyword: keyword,
})
.then(({ data }) => setsearchedList(data))
.catch((err) => console.log(err));
};
I am assuming the api call resolves with data being an array.

How to get the current state inside socket.io on callback function

const useChat = () => {
const [messages, setMessages] = useState([]);
const socketRef = useRef();
const { chatId } = useSelector(state => state.chatin)
const { chatList } = useSelector(state => state.chatin)
const dispatch = useDispatch()
useEffect(() => {
socketRef.current = io(socketClient);
socketClient.on('chat', (data) => {
const targetMessage = (messages) => messages.findIndex(item => item.message_number === data.message_number);
console.log('targetMessage', targetMessage)
if (targetMessage !== -1) {
messages[targetMessage].is_hide = true;
}
setMessages((messages) => [...messages, data]);
});
return () => {
socketRef.current.disconnect();
};
}, []);
whenever I got new socket data, I wanna change 'messages' data, but can't access it, because it always shows initial data value.After that I have a question about how can I set it?
You can move the if condition inside setMessages function, this way you will get access to the current state:
socketClient.on('chat', (data) => {
setMessages((messages) => {
const targetMessage = messages.findIndex(item => item.message_number === data.message_number);
if (targetMessage !== -1) {
messages[targetMessage].is_hide = true;
}
return [...messages, data]
});
});

state doesn't clear in useEffect

I wanted to re-render my component and update array of events when filters are changing.
const filters = useSelector(state => state.mapRedux.filters)
const [allEvents, setAllEvents] = useState([]);
const getAllEvents = async (start) => {
let myEventsArray = [];
await firebase.firestore().collection('wydarzenie')
.where('sport', 'in', createFiltersTable())
.where('miasto', '==', currentCity)
.orderBy("data_rozpoczecia")
.startAfter(start)
.limit(limit)
.get().then(snapshot => {
if (snapshot.size < limit) setShowMore(false)
snapshot.forEach(doc => {
let info = doc.data()
let el = {
id: doc.id,
...info
}
myEventsArray.push(el)
});
})
let new_array = allEvents.concat(myEventsArray)
setAllEvents(new_array);
}
useEffect(() => {
setAllEvents([])
getAllEvents(new Date());
}, [filters])
And that works, but I don't why setAllEvents([]) doesn't clear my events array. Instead new array is joins with old one and I get duplicate of some elements.
Here is what you can do to prevent stale closures but not run the effect too many times:
const AllEvents = (props) => {
const currentCity = useSelector(
(state) => state.mapRedux.city.name
);
const [allEvents, setAllEvents] = useState([]);
const [limit, setLimit] = useState(6);
const [showMore, setShowMore] = useState(true);
// filtry
const filters = useSelector(
(state) => state.mapRedux.filters
);
const createFiltersTable = React.useCallback(() => {
const tmp = Object.values(filters);
const values = [];
tmp.map((el) => {
if (el.active) values.push(el.events_name);
});
return values;
}, [filters]); //re create this function when filters change
const getAllEvents = React.useCallback(
async (start) => {
let myEventsArray = [];
await firebase
.firestore()
.collection('wydarzenie')
.where('sport', 'in', createFiltersTable())
.where('miasto', '==', currentCity)
.orderBy('data_rozpoczecia')
.startAfter(start)
.limit(limit)
.get()
.then((snapshot) => {
if (snapshot.size < limit) setShowMore(false);
snapshot.forEach((doc) => {
let info = doc.data();
let el = {
id: doc.id,
...info,
};
myEventsArray.push(el);
});
});
setAllEvents((allEvents) =>
//use callback to prevent allEvents being a dependency
allEvents.concat(myEventsArray)
);
},
//re create getAllEvents when createFiltersTable, currentCity
// or limit changes
[createFiltersTable, currentCity, limit]
);
useEffect(() => {
setAllEvents([]);
getAllEvents(new Date());
//effect will run when filters change or when
// getAllEvents change, getAllEvents will change
// when filters, currentCity or limit changes
}, [filters, getAllEvents]);
return ...;
};

Resources