AtomFamily
For example, you have list of elements. We want to avoid that single elemenet got changed, whole list got re-render. Also want to share the single element state to other component. We can use atom
(for list) and atomFmaily
(for each element)
[PS] Using Redux with selector can also achieve this
for the list itself, you can use atom
to model it.
for each elements in the list, can use atomFamily
, it is a high order function which accept a param, for list of elements, the params would be the id
of each element.
It is easy to get each element by using useRecoilState(elementAtom(id))
, and those state are shareable with other component.
// list
export const elementsAtom = atom<number[]>({
key: 'elementsState',
default: [],
})
// each element
export const elementAtom = atomFamily<Element, number>({
key: 'elementState',
default: {
style: {
position: {top: 50, left: 50},
size: {width: 50, height: 50},
},
},
})
const [element, setElement] = useRecoilState(elementAtom(id))
SelectorFamily
Benefits of Selectors:
- Get and Set logic combined
- Same API as Atoms
- Shareable, self-containaed state
- Async data fetching
- Has caching built-in for the same params
- Handle race condition for you when data fetching
Reaons to use SelectorFamily
:
- Similar concept as
factory
- The params should be
serialisable
- Recoil compare the param by
value
not byreference
selectorFamily({userId: 1}) === selectorFamily({userId: 1})
- In
Menu
&Post
both calluserState
, but data fetch only once
- Recoil compare the param by
export const editPropertiesState = selectorFamily<number, {path: string; id: number}>({
key: 'editPropertiesState',
get:
({path, id}) =>
({get}) => {
const element = get(elementAtom(id))
return lodash_get(element, path)
},
set:
({path, id}) =>
({get, set}, newValue) => {
const element = get(elementAtom(id))
const newElement = produce(element, (draft) => {
lodash_set(draft, path, newValue)
})
set(elementAtom(id), newElement)
},
})
How to refetch data by using selector/selectorFamily
const weatherState = selectorFamily({
key: 'weather',
get:
(userId: number) =>
async ({get}) => {
get(weatherRequestIdState(userId))
const user = get(userState(userId))
const weather = await getWeather(user.address.city)
return weather
},
})
// #region refetch
/* Refetch request by using selector */
// 1. create a request Id atom
const weatherRequestIdState = atomFamily({
key: 'weatherRequestId',
default: 0,
})
// 2. create a custom hook to increase the request id
const useRefetchWeather = (userId: number) => {
const setRequestId = useSetRecoilState(weatherRequestIdState(userId))
return () => setRequestId((id) => id + 1)
}
// 3. call get(weatherRequestIdState(userId)) in selectFamily where requrest need to be refetch
// 4. Use useRefetchWeather(userId) hook
// #endregion
export const UserWeather = ({userId}: {userId: number}) => {
const weather = useRecoilValue(weatherState(userId))
const userData = useRecoilValue(userState(userId))
const refetch = useRefetchWeather(userId)
return (
<>
<Text>
<b>Weather for {userData.address.city}: </b> {weather} C
</Text>
<Text onClick={refetch}>(Refresh weather)</Text>
</>
)
}
UseRecoilCallback
In a case that when we add a new item into list, by the same time, we want to upate the newly added item.
In this project, we have element list, when we add a new image rectangle, we want to push a new rectangle into the list, then update that new rectangle to set image prop.
The problem is by the time we add new rectangle intot the list, we don't have rectangle instance yet. Therefore we cannot set image prop.
useRecoilCallback(({set, get}) => (param) => {...})
const elements = useRecoilValue(elementsAtom)
const newId = elements.length
const insertElement = useRecoilCallback(({set}) => (type: 'rectangle' | 'image') => {
// add a new rectangle into the list
set(elementsAtom, (elements) => [...elements, elements.length])
if (type === 'image') {
// newId will be the newly added rectangle
set(elementAtom(newId), {
style: defaultStyle,
image: getRandomImage(),
})
}
})
[PS] found this solution also works
const [elements, setElements] = useRecoilState(elementsAtom)
const newId = elements.length
const [_, setNewElement] = useRecoilState(elementAtom(newId))
function setNewItem(type: 'rectangle' | 'image') {
setElements((els) => [...els, els.length])
if (type === 'image') {
// newId will be the newly added rectangle
setNewElement((el) => ({
...el,
style: defaultStyle,
image: getRandomImage(),
}))
}
}
Intermediate Selectors
What is the problem for the following code?
const imageInfoState = selector({
key: 'imageInfoState',
get: ({get}) => {
const id = get(selectElementAtom)
if (id === null) return
const element = get(elementAtom(id))
return element.image?.id
if (imageId === undefined) return
return callApi('image-details', {queryParams: {seed: imageId}})
},
})
标签:set,return,get,Overview,element,Recoil,const,id
From: https://www.cnblogs.com/Answer1215/p/16783161.html