reactjs how to implement server-side filtration of data - javascript

I have to get a users from backend and push the result array into a property of third-party component. I can't push the result into the component's state, like setUsersList, because it leads to infinity render-loop. So I decided to use a function which will put the data from backend into a variable and return variable's value.
<InputAutocomplete
...
value={selectedUserName}
onChange={(value: string) => setSelectedUserName(value)}
options={getFilteredUsers(selectedUserName)}
/>
My function is:
const getFilteredUsers = (typedValue: any): any[] => {
console.log('***getting into getFilteredUsers method')
if (!typedValue) {
return []
}
let usersList: any[]
repository.GetUsers(typedValue).then((response) => {
if (response.StatusCode !== null) {
response.Message.then((errorText) => showNotificationPopup(errorText, consts.ERROR_OCCURED, 'error'))
return
}
response.Data.then((users) => {
console.log('filling usersList')
usersList = (users.map((u) => {
return {key: u.Id, value: `${u.Name} - ${u.Position} - ${u.DopOfficeName}` }
})
)
})
})
console.log('leaving function')
return usersList
}
It works unacceptable for me (although it works as expected):
***getting into getFilteredUsers method
leaving function (with empty array)
filling usersList (too late)
The property "options" of third's party component doesn't accept Promise<any[]>, it can only accept any[], thus I can't mark my function as async and await the result from repository before to leave the function.
How to prevent leaving function before I'll get the result?
Maybe I've made a wrong decision at all. If I'm wrong I need a help, how to implement server-side refilling of autocomplete input component.

I can't push the result into the component's state, like setUsersList, because it leads to infinity render-loop
This should not be the case.
const SomeComponent = () => {
const [options, setOptions] = React.useState([]);
const [username, setUsername] = React.useState([]);
React.useEffect(() => {
if (!typedValue) return [];
repository.GetUsers(typedValue).then((response) => {
if (response.StatusCode !== null) {
response.Message.then((errorText) => showNotificationPopup(errorText, consts.ERROR_OCCURED, 'error'));
return;
}
response.Data.then((users) => {
console.log('filling usersList');
const usersList = users.map(u => {
return { key: u.Id, value: `${u.Name} - ${u.Position} - ${u.DopOfficeName}`};
});
setOptions(usersList);
})
})
console.log('leaving function')
}, [username]);
return (
<InputAutocomplete
value={username}
onChange={(value) => setUsername(value)}
options={options}
/>
);
};
Above code should not go in infinite loop.

Related

Prevent render before useEffect with search params

I have a component Browse that used to display search result requested from SearchBar component.
First search query when results state is null
Found some tracks now results state have array of tracks in
I make another search but changing the filter type from By Tracks to By Users (The results should have array of users objects)
I got an error because the render was called before the initResults() function so the results has always array of tracks object inside so I got error of undefined property
The output when I search for the first time :
> init useEffect
> render
The output when I searched another time inside the Browse Component :
> render
> init useEffect
How can I refresh the useEffect when search params change
Any idea ? Thank's !
Browse.js
export default function Browse() {
const [results, setResults] = UseSafeState(null)
const [isLoading, setIsLoading] = UseSafeState(true)
const location = useLocation()
const searchParams = new URLSearchParams(location.search)
const search = searchParams.get('search')
const identifierFilter = searchParams.get('identifier_filter')
const initResults = () => {
setIsLoading(true)
SearchAPI.search(search, identifierFilter)
.then((response) => {
setResults(response.data)
setIsLoading(false)
})
.catch((error) => {
setResults(null)
setIsLoading(false)
})
}
const renderTrackBrowse = () => {
return results.map((result, key) => <div key={key}>{result.track.name}</div>)
}
const renderUserBrowse = () => {
return results.map((result, key) => <div key={key}>{result.user.username}</div>)
}
const renderPage = () => {
console.log('render')
switch (identifierFilter) {
case FiltersTypesSearchBar.TRACKS.name:
return renderTrackBrowse()
case FiltersTypesSearchBar.ARTIST_NAME.name:
return renderUserBrowse()
default:
}
}
useEffect(() => {
console.log('init useEffect')
initResults()
}, [location, search, identifierFilter])
return <div className="main double-contained browse">{isLoading ? <Loader /> : renderPage()}</div>
}
Effects will always execute after the execution of the whole function. Even useLayoutEffect does not get invoked early enough to fix your issue. But with a tiny change, your code would already work.
const renderPage = () => {
console.log('render')
// do not continue execution if no results exist
if (!results) return;
switch (identifierFilter) {
case FiltersTypesSearchBar.TRACKS.name:
return renderTrackBrowse()
case FiltersTypesSearchBar.ARTIST_NAME.name:
return renderUserBrowse()
default:
}
}
Also one of your dependencies in the useEffect seems to be irrelevant and could cause more requests to be done as necessary. This should be sufficient.
useEffect(() => {
console.log('init useEffect')
initResults()
}, [search, identifierFilter])

Updating React state after axios PUT request without having to reload page

On my current application, if a user tries to enter an existing name that has a different number, it will prompt the user if they want to update that entry with the new number. If yes, the entry is updated using an axios PUT request. My issue is that I can only get it to change on the front end by reloading the page (it updates successfully on db.json) instead of it updating immediately after the user confirms. On my useEffect method I tried adding [persons] as the second argument and it seemed to work, but found out that it loops the GET requests infinitely. I have a similar function for when deleting an entry so I'm sure it must be something that has to be added to setPersons
Update methods
const addEntry = (event) => {
event.preventDefault();
const newPersonEntry = {
name: newName,
number: newNumber,
}
const all_names = persons.map(person => person.name.toUpperCase())
const all_numbers = persons.map(person => person.number)
const updatedPerson = persons.find(p => p.name.toUpperCase() === newName.toUpperCase())
const newPerson = { ...updatedPerson, number: newNumber };
if (newName === '') {
alert('Name entry cannot be blank')
return
}
if (newNumber === '') {
alert('Number entry cannot be blank')
return
}
if (all_numbers.includes(newNumber)) {
alert('That number already exists')
return
}
if (newNumber.length < 14) {
alert('Enter a valid number')
return
}
if (all_names.includes(newName.toUpperCase())) {
if (window.confirm(`${newName} already exists, replace number with the new one?`)) {
console.log(`${newName}'s number updated`)
personService
.update(updatedPerson.id, newPerson)
.then(res => {
setPersons() //something here
})
return
}
return
}
personService
.create(newPersonEntry)
.then(person => {
setPersons(persons.concat(person))
setNewName('')
setNewNumber('')
})
}
//PUT exported as personService
const update = (id, newObject) => {
const request = axios.put(`${baseURL}/${id}`,newObject)
return request.then(response => response.data)
}
Other code
const App = () => {
const [persons, setPersons] = useState([])
useEffect(() => {
personService
.getAll()
.then(initialPersons => {
setPersons(initialPersons)
})
}, [])
...
//Display method
const filteredNames = persons.filter(person => person.name.toLowerCase().includes(filter.toLowerCase()))
const row_names = () => {
return (
filteredNames.map(person =>
<p key={person.id}>{person.name} {person.number} <button onClick={() => handleDelete(person)}>delete</button></p>));
}
...
//Render
return (
<div>
<h2>Phonebook</h2>
<h2>Search</h2>
<SearchFilter value={filter} onChange={handleFilterChange} />
<h2>Add Entry</h2>
<Form onSubmit={addEntry}
name={{ value: newName, onChange: handleNameChange }}
number={{ value: newNumber, onChange: handleNumberChange }}
/>
<h2>Numbers</h2>
<DisplayPersons persons={row_names()} />
</div>
)
}
The solution here is a little bit tricky but doable . You need to split your logic into two parts like this :
const [dataChanged , setDataChanged] = useState(false)
useEffect(()=>{
// Rest of your logic here
} , [dataChanged])
useEffect(()=>{
// Your logic will run only one time
// on Success we change the dataChanged state so the other useEffect will
// run basically you can run the rest of your logic in the other
// useEffect so the infinite loop won't happen
// setDataChanged( (prev) => !prev )
} , [])
Was able to use map method that worked
personService
.update(updatedPerson.id, newPerson)
.then(res => {
setPersons(persons.map(p => p.id !== updatedPerson.id ? p : res))
})

Show filtered result from api in React/Typescript

I have a component that fetches data and then I am setting that data via state. If I want to filter that data based on certain criteria or fields from the api, would I do the filter method? or is this not advisable for state objects?
https://stackblitz.com/edit/react-x7tlxr?file=src/App.js
So far I am doing this: but it's not working as the filtering does not happen.
const [fetchData, setFetchData] = React.useState<any>([]);
const [loading, setLoading] = React.useState<boolean>(true);
const [isError, setIsError] = React.useState<boolean>(false);
const url: string = 'https://xxxxxx';
useEffect(() => {
let mounted = true;
const loadData = async (): Promise<any> => {
try {
const response = await axios(url);
if (mounted) {
setFetchData(response.data);
setLoading(false);
setIsError(false);
console.log('data mounted')
}
} catch (err) {
setIsError(true)
setLoading(false);
setFetchData([]);
console.log(err);
}
};
loadData();
return () => {
mounted = false;
console.log('cleaned');
};
},
[url]
);
function to filter based on onClick:
onClick={(idx: number) => {
const resultInnerNew = fetchData.filter((statusPoint: any) => statusPoint.status === 'New');
setFetchData(resultInnerNew)
}
binding to template:
{isError ? <p className="mt-5">There is an error fetching the data!</p> : <div className="container"></div>}
{loading ? <div>Loading ...</div> : <div className="cards row card-container mt-5">
// data here
</div>
}
You could use an arrow function to update the state and filter the data as below,
onClick={(idx: number) => {
setFetchData(data => data.filter(statusPoint => statusPoint.status === 'New')
}}
A quick review on Array.prototype.filter(), this code will only keep the values which have statusPoint.status equal to 'New' in your array.

react useState() not changing the value(an object) in the first render

I am using react usestate() and I want to update device state(It is an object)
my problem is when ShowRelays component renders for the first time device is an empty object and It does not get updated during first rendering, but for the next renders everything is fine
How can I update device state for the first time rendering?
(sorry for my bad english)
.
function ShowRelays(props) {
const [device, setDevice] = useState({})
let reduxDevices = useSelector(state => state.devicesReducer.devices)
let findDevice = () => {
let myDevice = reduxDevices.find(x => x._id === props.id)
setDevice(prevState => {
return {
...prevState,
...myDevice
}
})
}
useEffect(() => {
if (props.show) {
findDevice()
}
}, [props.show])
return
(
<div>
test
</div>
)
}
myDevice object is like:
{active: true, name: "device1", id: "deviceId"}
You can pass a function to your useState-hook which will calculate your initial value for device.
see lazy init state
function ShowRelays(props) {
let reduxDevices = useSelector(state => state.devicesReducer.devices);
const [device, setDevice] = useState(() => {
return reduxDevices.find(x => x._id === props.id)
});
return <div>test</div>;
}
An other possible solution for your problem without using a separate state could be the following (directly select the right device from your selector function):
function ShowRelays(props) {
const device = useSelector(state => {
return state.devicesReducer.devices.find(x => x._id === props.id);
});
return <div>test</div>;
}

How to get value of useState variable in function that passed to another component

The best way to describe question is my code:
function EstateParamsList({ estateType, category }) {
const [isLoading, setIsLoading] = useState(true)
const [params, setParams] = useState({})
const [showPopUp, setShowPopUp] = useState(false)
useEffect(() => {
if (category && typeof category.id !== undefined) {
return db.collection(`dictionaries/ESTATE_PARAMS/${estateType}/${category.id}/params`).onSnapshot(response => {
const paramsObject = {}
response.forEach(param => {
paramsObject[param.id] = {
...convertParamObjetcToFieldsConfig(param.data()),
change: fieldChangedHandler
}
})
setParams(paramsObject)
setIsLoading(false)
})
} else {
setIsLoading(false)
}
}, [category])
console.log(params)
const fieldChangedHandler = (event, fieldIdentifier) => {
if(params)
console.log(params)
}
So i have params variable, that im init with object, that i'm getting async from firebase. Implementation of initializing you can see in useEffect method. For every object i want to pass ref for the function "fieldChangedHandler", for managing value of inputs.
fieldChangedHandler is a method of my EstateParamsList. But there i cant get value of params!
Question is WHY? I'm calling fieldChangedHandler only after everything was rendered, and async request was done.
Below is console log of params. Why in func is empty params?
Calling:
const renderParamsAsFields = params => {
const fields = []
for (const key in params) {
fields.push(<Field {...params[key]} changed={event => params[key].change(event, key)} />)
}
return fields.length ? fields : <div className='EstateParamsManager-EmptyValues'>Нет параметров</div>
}
Why not use curried function?
const createFieldChangedHandler = (
params,
fieldIdentifier
) => event => {
if (params) console.log(params);
};
function EstateParamsList({ estateType, category }) {
//... other code
useEffect(() => {
//...other code
change: createFieldChangedHandler(
paramsObject,
param.id
),
//...other code
}, [category, estateType]);//fixed missing dependency
When you call the change function you should already have the right values in scope:
<Field {...params[key]} changed={params[key].change} />

Categories

Resources