In my app, SearchParams.tsx is a form that gets the apiResponse
from the parent and uses entries in this response to set options for it’s select/dropdown menus for the user to then choose from. I have a lot of states in this component, few for setting the options available and others for tracking the options the user has selected (to submit/post on form submit). I am wondering if there is a more elegant/cleaner/concise way to do this (except combining everything in one object)? Or if having separate states tracking everything individually is a good idea in react? Is one approach better than the other? etc.
Note: I am using a third party API so don’t have any control on that front.
Here is a snippet from SearchParams.tsx:
const SearchParams = ({ apiResponse, speciesSelected }: { apiResponse: SpeciesSelectionResponse| {}, speciesSelected: string }) => {
const [desiredAge, setDesiredAge] = useState<string>("")
const [desiredGender, setDesiredGender] = useState<string>("")
const [desiredGeoRange, setDesiredGeoRange] = useState<string>("35")
const [desiredColor, setDesiredColor] = useState<string>("")
const [desiredBreed, setDesiredBreed] = useState<string>("")
const [zipCode, setZipCode] = useState<string>("")
const [species, setSpecies] = useState<string>("")
const [ageOptionsAvailable, setAgeOptionsAvailable] = useState<SubObject[]>([])
const [genderOptionsAvailable, setGenderOptionsAvailable] = useState<SubObject[]>([])
const [geoRangeOptionsAvailable, setGeoRangeOptionsAvailable] = useState<SubObject[]>([])
const [colorOptionsAvailable, setColorOptionsAvailable] = useState<SubObject[]>([])
const [breedOptionsAvailable, setBreedOptionsAvailable] = useState<SubObject[]>([])
useEffect(() => {
if (Object.keys(apiResponse).length === 0 && apiResponse.constructor === Object) {
return
}
if (Object.keys(apiResponse).length !== 0 && apiResponse.constructor === Object) {
setAgeOptionsAvailable((apiResponse as SpeciesSelectionResponse).age)
setGenderOptionsAvailable((apiResponse as SpeciesSelectionResponse).sex)
setGeoRangeOptionsAvailable((apiResponse as SpeciesSelectionResponse).geo_range)
setColorOptionsAvailable((apiResponse as SpeciesSelectionResponse).color_id)
setBreedOptionsAvailable((apiResponse as SpeciesSelectionResponse).breed_id)
setSpecies(speciesSelected)
}
}, [apiResponse])
Everything works as expected. I am just wondering if there is a more elegant/concise way of refactoring this.
2
I would say that you don’t need to wrap inputs in state.
Presumably the other state is displayed, and set by controls on this form. In that case it good to have states for each individually.
const SearchParams = ({ apiResponse, speciesSelected }: { apiResponse: SpeciesSelectionResponse| {}, speciesSelected: string }) => {
const hasResponse = Object.keys(apiResponse).length !== 0 && apiResponse.constructor === Object
const [desiredAge, setDesiredAge] = useState<string>("")
const [desiredGender, setDesiredGender] = useState<string>("")
const [desiredGeoRange, setDesiredGeoRange] = useState<string>("35")
const [desiredColor, setDesiredColor] = useState<string>("")
const [desiredBreed, setDesiredBreed] = useState<string>("")
const [zipCode, setZipCode] = useState<string>("")
const species = hasResponse ? speciesSelected : ""
const ageOptionsAvailable = hasResponse ? apiResponse.age : []
const genderOptionsAvailable = hasResponse ? apiResponse.sex : []
const geoRangeOptionsAvailable = hasResponse ? apiResponse.geo_range : []
const colorOptionsAvailable = hasResponse ? apiResponse.color_id : []
const breedOptionsAvailable = hasResponse ? apiResponse.breed_id : []
// etc
}
you can use useReducer for this kind of state
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'SET_FIELD':
return { ...state, [action.key]: action.value };
default:
return state;
}
};
const Component = (initialState: State) => {
const [state, dispatch] = useReducer(reducer, initialState);
//example on change
const onChange = ({key, value}: Action) => {
dipstach({type: SET_FIELD, action: {key, value} })
}
you can call it whenever you want
onChange({
key: "desiredAge",
value: 1
})
...rest of component body
}
//////////////
Fetch data here.
const hasResponse = Object.keys(apiResponse).length !== 0 && apiResponse.constructor === Object
//you dont need unecessary useffect in Component
...hasResponse ? <Component initialState={{
desiredAge: apiResponse.desiredAge,
desiredGender: apiResponse.desiredGender,
....REST OF API DATA
}} />
//////