How to properly update a state records in reacts - reactjs

The Code below successfully displays record from Atlassian Jira Storage API
//import goes here
const fetchRec = async () => {
const data = await storage.query().where('key', startsWith('Mykeysxxxxx')).getMany();
return data.results;
};
const App = () => {
const [projects] = useState(fetchRec);
return (
<div>
// display or map projects records here
{projects.map(project => (
<div>
<b>Fullname: {project.fullname}</b>
<b>Email: {project.email}</b>
</div>
))}
</div>
);
}
Here is my Issue. I need to refresh the Records when new data is inserted.
So I implemented the code below. when I click on refresh Records button, the new inserted record is not updated
<Button text="Refresh Records" onClick={async () => { await reloadRec(); }} />
async function reloadRec() {
fetchRec().then(projects);
//const [projects] = useState(async () => await fetchRec());
}
Here is the full code efforts so far
// Import goes here
const fetchRec = async () => {
const data = await storage.query().where('key', startsWith('Mykeysxxxxx')).getMany();
return data.results;
};
async function reloadRec() {
fetchRec().then(projects);
//const [projects] = useState(async () => await fetchRec());
}
const App = () => {
const [projects] = useState(fetchRec);
return (
<div>
// display or map projects records here
{projects.map(project => (
<div>
<b>Fullname: {project.fullname}</b>
<b>Email: {project.email}</b>
</div>
))}
<Button text="Refresh Records" onClick={async () => { await reloadRec(); }} />
</div>
);
}

This is the problem:
async function reloadRec() {
fetchRec().then(projects);
//const [projects] = useState(async () => await fetchRec());
}
In the above function, projects is undefined. And even if it was the same variable as defined in the App function, it's not a function that the then clause would invoke.
I think this is closer to what you want and follows the standard practices.
const App = () => {
const [projects, setProjects] = useState([]);
const fetcher = async () => {
const data = await storage.query().where('key', startsWith('Mykeysxxxxx')).getMany();
setProjects(data.results);
};
// do the initial fetch with an effect
useEffect(() => {
fetcher();
}, []);
return (
<div>
// display or map projects records here
{projects.map(project => (
<div>
<b>Fullname: {project.fullname}</b>
<b>Email: {project.email}</b>
</div>
))}
<Button text="Refresh Records" onClick={fetcher} />
</div>
);
}

InOrder to update information on the DOM you should update a state and DOM will show latest information. You bind and setRecord with fetchData function
Extanding
const App = () => {
const [projects,setProjects] = useState([]);
const fetchRecords = async() => {
const data = await /*... your data base query */
// Assuming projects is an array of project
setProjects(data.projects);
}
useEffect(()=>{
fetchRecords();
},[])
return (
<div>
// display or map projects records here
{projects.map(project => (
<div>
<b>Fullname: {project.fullname}</b>
<b>Email: {project.email}</b>
</div>
))}
<Button text="Refresh Records" onClick={fetchRecords} />
</div>
);
}
So at first when Component is mount you will see Projects list and then when you click on reload fetchRecords is again called resulting in state change and will reflect on dom

Related

I cannot filter the items in redux with reselect

I am working on a react-redux project. My problem is that I cannot filter the items that come from an API according to the user input. I used reselect library but did not work.
Here is my SearchComponent:
function SearchComponent({ onClose, isOpen }) {
const dispatch = useDispatch()
const searchAnimes = useSelector(inputItems)
const filtered = createSelector(inputItems, (items, e) => {
const filterText = e.target.value.toLowerCase()
const filterWords = filterText.split("")
items.filter((item) => {
return filterWords.every((word) =>
item.title.toLowerCase().includes(word)
)
})
})
useEffect(() => {
dispatch(fetchInputData())
}, [dispatch])
return (
"... Some code"
<input
type="text"
placeholder="You can search for `Kyoukai no Kanata` for example"
onChange={filtered}
/>
{searchAnimes.map((el, id) => (
<div className="searchInput" key={id}>
<img src={el.images.jpg.small_image_url} alt="" />
<p>{el.title}</p>
</div>
))}
</div>
</Modal>
)
}
Here is my slice:
export const fetchInputData = createAsyncThunk(
"anime/fetchInputData",
async () => {
const response = await axios.get(`${process.env.REACT_APP_API_KEY}?limit=5`)
// console.log(response.data.data)
return response.data.data
}
)
Btw, there is no problem with selector. Thanks in advance!
const filterWords = filterText.split("")
will break up your string by characters, not by words. I believe you want:
const filterWords = filterText.split(" ")

How to refactor this call to refresh state

I want to know how improve this calls in order to not repeat always the same sentence to refresh the state...
I don't need any huge refactor, only inputs like: you need to put this call inside a function and call it when you want... something like this...
export const CategoriesPage = () => {
const [categories, setCategories] = useState<Category[]>([]);
const [showModal, setShowModal] = useState(false);
const handleCreateCategory = (newCategory: CategoryCreate, file: File) => {
createCategoryHelper(newCategory, file)
.then(() => {
getCategoriesHelper().then(setCategories);
})
.finally(() => handleClose());
};
const handleDeleteCategory = (categoryId: Id) => {
SwalHelper.delete().then(() => {
deleteCategoryHelper(categoryId).then(() =>
getCategoriesHelper().then(setCategories)
);
});
};
const handleClose = () => {
setShowModal(false);
};
const handleModal = () => {
setShowModal(true);
};
useEffect(() => {
getCategoriesHelper().then(setCategories);
}, []);
return (
<>
<PageTitle title="Categories" />
<FilterBar>
<Button type="button" background="green" onClick={handleModal}>
+ Add new
</Button>
</FilterBar>
{showModal && (
<ModalPortal onClose={handleClose}>
<CreateCategoryForm
createCategory={(category, file: File) => {
handleCreateCategory(category, file);
}}
/>
</ModalPortal>
)}
<ListGrid columns={3}>
{categories.map((category) => {
const { id: categoryId } = category;
return (
<CategoryCard
key={categoryId}
{...category}
onClick={() => handleDeleteCategory(categoryId)}
/>
);
})}
</ListGrid>
</>
);
};
When component is mounting, on useEffect, fills the state with response in order to create a list.
When a category is created, I call to setState again to refresh the list.
Same on delete, on then, refresh again to update the list.
Three times calling the same sentence
getCategoriesHelper().then(setCategories)
This is getCategoriesHelper:
export const getCategoriesHelper = async () => {
const service = new CategoryServiceImplementation(apiConfig);
const uploadImageService = new AmplifyS3Service();
const repository = new CategoryRepositoryImplementation(
service,
uploadImageService
);
const useCase = new GetCategoriesUseCaseImplementation(repository);
return await useCase.getCategories();
};
Is there any way to make this code much cleaner and reusable?
Thanks in advance!
Everything is write, and all calls are made as they are designed to do

React, unexpected multiple result when using map and fetch

I am building Weather App, my idea is to save city name in localStorage, pass a prop to child component, then iterate using map and display each in seperate child of the first child
The problem is that displayed data doubles/triples on render(depending on component when render occurs) so when I have for example city London and add city Berlin it will render:
London,London,Berlin
The problem is not in AddCity component, it's working correctly but in this mix of asynchronous setState/fetching and maping
Please see the code below
App(parent component)
const App = () => {
const [cities, setCities] = useState([]);
const addCity = (newCity)=>{
console.log('adding')
setCities([...cities, newCity]);
let cityId = localStorage.length;
localStorage.setItem(`city${cityId}`, newCity);
}
useEffect(() => {
loadCityFromLocalStore()
}, [])
const loadCityFromLocalStore =()=>{
setCities([...cities, ...Object.values(localStorage)])
}
return (
<div>
<Header />
<AddCity addCity={addCity}/>
<DisplayWeather displayWeather={cities}/>
</div>
)
}
DisplayWeather (first child)
const DisplayWeather = ({displayWeather}) => {
const apiKey = '4c97ef52cb86a6fa1cff027ac4a37671';
const [fetchData, setFetchData] = useState([]);
useEffect(() => {
displayWeather.map(async city=>{
const res =await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData((fetchData=>[...fetchData , data]));
})
}, [displayWeather])
return (
<>
{fetchData.map(data=>(
<ul>
<Weather
data={data}/>
</ul>
))}
</>
)
}
Weather component
const Weather = ({data}) => {
return (
<li>
{data.name}
</li>
)
}
It looks like the problem comes from calling setFetchData for cities that you already added previously.
One easy way to fix it would be to store fetch data as an object instead of a dictionary so that you just override the data for the city in case it already exists (or maybe even skip the fetch as you already have the data).
For example:
const [fetchData, setFetchData] = useState({});
useEffect(() => {
displayWeather.map(async city=>{
const res = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData((fetchData=> ({...fetchData, [city]: data})));
})
}, [displayWeather])
Then, to map over fetch data you can just use Object.values:
return (
<>
{Object.values(fetchData).map(data=>(
<ul>
<Weather
data={data}/>
</ul>
))}
</>
)
If you want to skip already fetched cities you can do something like this instead:
useEffect(() => {
displayWeather.map(async city=>{
if (!fetchData[city]) {
const res = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData((fetchData=> ({...fetchData, [city]: data})));
}
})

Rerender component after function in provider ends

I'm trying to set components with 3 functionalities. Displaying PokemonList, getting random pokemon and find one by filters. Getting random pokemon works great but since 2 days I'm trying to figure out how to set pokemon list feature correctly
Below full code from this component.
It's render when click PokemonsList button inside separate navigation component and fire handleGetPokemonList function in provider using context.
The problem is that I can't manage rerender components when PokemonList is ready. For now i need to additionally fire forceUpadte() function manually (button onClick = () => forceUpdate())
I tried to use useEffect() in PokemonList component but it didn't work in any way.
I was also sure that after fetching data with fetchData() function I can do .then(changeState of loading) but it didn't work also.
What Am I missing to automatically render data from fetch in provider in PokemonList component? I'm receiving error about no data exist but if I use forceUpdate then everything is ok
Complete repo here: https://github.com/Mankowski92/poke-trainer
handleGetPokemonList function in provider below
const handleGetPokemonList = () => {
setCurrentPokedexOption('pokemonList');
async function fetchData() {
setImgLoaded(false);
let res = await fetch(`${API}?offset=0&limit=6/`);
let response = await res.json();
response.results.forEach((item) => {
const fetchDeeper = async () => {
let res = await fetch(`${item.url}`);
let response = await res.json();
let eachPoke = {
id: response.id,
name: response.name,
artwork: response.sprites.other['officialartwork'].front_default,
stats: response.stats,
};
fetchedPokemons.push(eachPoke);
};
fetchDeeper();
});
setPokemonList(fetchedPokemons);
if (fetchedPokemons) {
return setLoading(false);
}
}
fetchData()
.then((res) => setLoading(res))
.catch((err) => console.log('error', err));
};
PokemonList component below
import React, { useContext, useState, useCallback } from 'react';
import { StyledPokemonListContainer } from './PokemonList.styles';
import { PokemonsContext } from '../../../providers/PokemonsProvider';
const PokemonList = () => {
const ctx = useContext(PokemonsContext);
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
const { handleSetImgLoaded } = useContext(PokemonsContext);
return (
<>
{ctx.currentPokedexOption === 'pokemonList' ? (
<StyledPokemonListContainer>
{ctx.pokemonList && ctx.pokemonList.length ? (
ctx.pokemonList.map((item, i) => (
<div className="each-pokemon-container" key={i}>
<div className="poke-id">{item.id}</div>
<div className="poke-name">{item.name}</div>
<img className="poke-photo" onLoad={() => handleSetImgLoaded()} src={item ? item.artwork : ''} alt="" />
</div>
))
) : (
<div className="render-info">Hit rerender button</div>
)}
{/* {ctx.pokemonList ? <div>{ctx.pokemonList[0].name}</div> : <div>DUPPSKO</div>} */}
<div className="buttons">
<button onClick={() => console.log('PREVOIUS')}>Previous</button>
<button className="rerender-button" onClick={() => forceUpdate()}>
RERENDER
</button>
<button onClick={() => console.log('NEXT')}>Next</button>
</div>
</StyledPokemonListContainer>
) : null}
</>
);
};
export default PokemonList;

Why the Search Function for Random user api is not working?

I'm working on random user api, the fetching of user name and pagination is working fine but not the search event. Please help.
I pushed my code on stackblitz, to help you guys to debug it easily.
here's the link: https://stackblitz.com/edit/search-and-pagination-in-react-by-react-hooks?file=src/App.js
below in image you can see that the name i mentioned in search box is present in api but its not comming on first place.
Working example in here.
const App = () => {
const [myApi, setMyApi] = useState([]);
const [data, setData] = useState([]); // add your data to here
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(10);
const [searchUser, setSearchUser] = useState("");
useEffect(() => {
fetch("https://randomuser.me/api/?results=50")
.then(data => data.json())
.then(json_result => {
setData(json_result.results); // set your data to state
let myApi = renderData(json_result.results); // render your component
setMyApi(myApi); // set it to state
});
}, []);
const renderData = (data) => {
return data.map((item, idx) => {
return (
<div key={idx}>
<img src={item.picture.thumbnail} alt="" /> {item.name.first}
<hr />
</div>
);
});
}
// get current post
const indexOfLastPost = currentPage * postsPerPage; // 1 * 10 = 10
const indexOfFirstPost = indexOfLastPost - postsPerPage; // 10 - 10 = 0
const currentPosts = myApi?.slice(indexOfFirstPost, indexOfLastPost); // 0 to 10
// search users by user input
const handleSearchInput = event => {
setSearchUser(event.target.value);
const newData = renderData(data.filter(item => item.name.first.toLowerCase().includes(event.target.value))); // render filtered data
setMyApi(newData); // and set it to state
};
const paginate = pageNumber => setCurrentPage(pageNumber);
return (
<div>
<Search onChange={handleSearchInput} />
<Pagination
postsPerPage={postsPerPage}
totalPosts={myApi?.length}
paginate={paginate}
/>
{currentPosts}
</div>
);
};
const Search = ({ onChange }) => {
return (
<div>
<input
type="text"
autoFocus={true}
placeholder="search users"
onChange={onChange}
/>
</div>
);
};
Since you're useEffect has [] (empty array) as the dependency, you're user fetching logic will only be called once i.e. on the initial rendering. You can add searchUser as useEffect's dependency so you can fetch users whenever the searchUser text changes.

Resources