I have integrated google map auto-completed, it's working well, even i have console logged, every data is appearing as expected but the problem is, when i select, it doesn't update the state
Here you go for my component
import Autocomplete from "react-google-autocomplete";
const TestForm = () => {
const [profile, setProfile] = useState({});
const onPlaceSelected = (place) => {
console.log(place)
setProfile({...profile, "test": "test"})
console.log(profile, )
};
const handleProfileChange = (e) => {
setProfile({...profile, [e.target.name]: e.target.value})
};
return (
<>
<input onChange={handleProfileChange} type="text"></input>
<Autocomplete
apiKey="apikey"
defaultValue={formData.profile.location || ""}
onPlaceSelected={onPlaceSelected}
/>
</>
);
};
You may notice, i used two different method to update state, one for general input fields and another for autocomplete lib, but general input field state is updateing but autocomplete state is not updating, i have console logged the place, i see it is appearing, only problem is when i select a location, the selected location doesnt add my state, what is the issue? why it's behaving so weirds? can anyone help me?
You cant log the state right after you updated it, because it's async. Try logging the state change in useEffect hook
const [profile, setProfile] = useState({});
const onPlaceSelected = (place) => {
console.log(place)
setProfile({...profile, "test": "test"})
};
useEffect(() => { console.log(profile) }, [profile])
Related
I'm reusing a couple of external components to create my custom Combobox in strapi app.
Values are received from server so I need to add options dynamically.
Currently there is the following code:
import React, { useState, useEffect } from "react";
import {
Combobox,
ComboboxOption
} from "#strapi/design-system";
export default function ComboboxCustom({
valuesList,
valueSelected
}) {
const [value, setValue] = useState('');
const combo = (<Combobox label="Country" value={value} onChange={setValue}>
{valuesList.map((entry) => {
return(
<ComboboxOption value="{entry.id}">{entry.name}</ComboboxOption>
);
})}
</Combobox>);
// setValue(valueSelected)
return combo;
}
And everything goes good until I try so set 'selected' option basing on another set of data. In static world I could just say useState(valueSelected) and it will work. But as code generated dynamically, there is no related option yet, so I get failure like "Failed to get 'props' property of undefined".
I tried to put this combobox into a variable and set state between creation and returning it (commented setValue line before the return statement) but then app gets in a loop and returns "Too many re-renders".
Does anyone has an idea of how to change/rewrite this to be able to set selected value for dynamically created combobox?
So I assume that the values are dynamically fetched and passed to the ComboboxCustom.
I think you can add setValue(valueSelected) inside an useEffect.
onChange of the prop valueSelected.something like,
useEffect(() => {
setValue(valueSelected)
}, [valueSelected])
Also handle the return when the value is not yet loaded. like before doing valuesList.map, first check if valueList ? (render actual) : (render empty)
Hope this helps!!
Thanks,
Anu
Finally I got working solution based on answer from #Anu.
Cause valuesList is got as GET-request from another hook, I have to check values are already present (first hook hit gives [] yet) and bind Combobox state updating to change of valuesList also. Though I don't fell like this solution is perfect.
import React, { useState, useEffect } from "react";
import {
Combobox,
ComboboxOption
} from "#strapi/design-system";
export default function ComboboxCustom({
valuesList,
valueSelected,
}) {
const [value, setValue] = useState('');
let combo = null;
useEffect(() => {
if(combo && combo?.props?.children?.length > 0 && valuesList.length > 0) {
setValue(valueSelected)
}
}, [valueSelected, valuesList])
combo = (<Combobox label="Country" value={value?.toString()} onChange={setValue}>
{valuesList.map((entry) => {
return(
<ComboboxOption value={entry?.id?.toString()}>{entry.name}</ComboboxOption>
);
})}
</Combobox>);
return combo;
}
After that I decided avoid creating custom component based on already published as I'll need to add and process event listeners that are added for us in the existing components. So I placed this code directly into my modal and it also works:
const [countries, setCountries] = useState([]);
const [deliveryCountryValue, setDeliveryCountryValue] = useState('');
useEffect(async () => {
const countriesReceived = await countryRequests.getAllCountries();
setCountries(countriesReceived);
}, []);
useEffect(() => {
// If there is no selected value yet, set the one we get from order from server
const valueDelivery = deliveryCountryValue != '' ? deliveryCountryValue : order.country?.id;
if(countries.length > 0) {
setDeliveryCountryValue(valueDelivery);
order.country = countries.find(x => x.id == valueDelivery);
}
}, [deliveryCountryValue, countries])
<Combobox key='delivery-combo' label="Country" value={deliveryCountryValue?.toString()} onChange={setDeliveryCountryValue}>
{countries.map((entry) => {
return(
<ComboboxOption key={'delivery'+entry.id} value={entry?.id?.toString()}>{entry.name}</ComboboxOption>
);
})}
</Combobox>
I have some problem with updating the editModal (react bootstrap modal).
after dispatching the action it works and I'm able to fetch single pet data as an object.
and redux dev tools confirms that
But modal doesn't open at first attempt and I need to click the (edit button: doesn't matter which one) again to show filled input data ,and each time I click the edit button for first pet or second pet it doesn't update modal and it shows data related to the previously clicked edit
and as you see I clicked the second edit and data has fetched as redux dev tools shows correctly but Modal still shows first pet details
thanks for your help :)
here's my code
const PetScreen = () => {
const dispatch = useDispatch()
//part of state that comes from store.js
const petDetailByUser = useSelector((state) => state.petDetailByUser)
//when action dispatch petDetails fills with single pet Details ex:_id,editPetName,...
const { petDetailLoading, petDetails } = petDetailByUser
const [editPetName, setEditPetName] = useState('')
//edit button dispatch getPetDetail and get pet data as an object
const editButtonHandler = (id) => {
dispatch(getPetDetails(id))
if (petDetails) {
setEditPetName(petDetails.editPetName)
//shows edit modal
handleEditShow()
}
}
//this field is inside react modal body
<Form.Control
type='text'
placeholder='Pet Name'
name='editPetName'
value={editPetName}
onChange={(e) => setEditPetName(e.target.value)}
>
</Form.Control>
Just after dispatch(getPetDetails(id)), the editPetName variable has not been refreshed yet so it contains the name of the previous pet.
You can fix it by using an effect to update editPetName when petDetails is updated.
const petDetailByUser = useSelector((state) => state.petDetailByUser)
const { petDetailLoading, petDetails } = petDetailByUser;
const [editPetName, setEditPetName] = useState('');
useEffect(() => {
if (petDetails) {
setEditPetName(petDetails.editPetName);
handleEditShow();
}
}, [petDetails]);
const editButtonHandler = (id) => dispatch(getPetDetails(id));
I verushc an array from one component to another component.
The initial array is filled by a DB and is not empty.
If I try to map over the array in my second component, it is empty (length = 0);
However, after I wrote a value in a search box to filter the array, all articles appear as intended.
What is that about?
export default function Einkäufe({ alleEinkäufe, ladeAlleEinkäufe, url }) {
const [searchTerm, setSearchTerm] = React.useState("");
const [searchResults, setSearchResults] = React.useState(alleEinkäufe);
const listeFiltern = (event) => {
setSearchTerm(event.target.value);
};
React.useEffect(() => {
setSearchResults(alleEinkäufe);
}, []);
React.useEffect(() => {
const results = alleEinkäufe.filter((eink) =>
eink.artikel.toLowerCase().includes(searchTerm.toLowerCase())
);
setSearchResults(results);
}, [searchTerm]);
[...]
{searchResults.map((artikel, index) => {
return ( ... );
})}
}
The problem is with your useEffect hook that sets the list of searchResults, it's not rerun when alleEinkäufe property is updated. You need to add alleEinkäufe as it's dependency.
React.useEffect(() => {
setSearchResults(alleEinkäufe);
}, [alleEinkäufe]);
My bet is that the parent component that renders Einkäufe is initially passing an empty array which is used as searchResults state and then never updated since useEffect with empty dependencies array is only run once on the component's mount.
I would also advise you to use English variable and function names, especially when you ask for assistance because it helps others to help you.
Your search term intially is "". All effects run when your components mount, including the effect which runs a filter. Initially, it's going to try to match any article to "".
You should include a condition to run your filter.
React.useEffect(() => {
if (searchTerm) {
const results = alleEinkäufe.filter((eink) =>
eink.artikel.toLowerCase().includes(searchTerm.toLowerCase())
);
setSearchResults(results);
}
}, [searchTerm]);
BTW, "" is falsy.
Hello I am building photo gallery where I would like to add feature that user will be able filter by Category. I tried some solutions but there are two bugs that I am not able to fix. First is that if I go to the GalleryPage (using Swtich) it does NOT render dynamically added buttons from FilterButton component. I have to click one more time on the link and then it DOES render the buttons. I dont know why it does not work on the first render.
Other issue is that I am able to filter by category but it causes the infinite loop in the useEffect and I dont know how to fix it.
I have got GalleryPage component where I am getting data from API and parsing the data for using later in other components. Here it seems that is all working fine.
const GalleryPage = () => {
const url = 'someurl';
const [data, setData] = useState([]);
const [categoryList, setCategoryList] = useState([]);
const [category, setCategory] = useState('All');
useEffect(() => {
const fetchData = async () => {
const result = await axios(url,);
setData(result.data)
result.data.forEach(item => {
imageUrl.push(item.image)
if (categoryList.indexOf(item.group) === -1) {
categoryList.push(item.group)
}
})
}
fetchData();
}, [])
return (
<FilterButton setCategory={setCategory} categoryList={categoryList}/>
<Gallery data={data} category={category}/>
)
}
If I go to the GalleryPage the h3 and 'All' button is rendered. But I have to click on the link one more time to render the buttons inside the map function:
const FilterButton = ({setCategory, categoryList}) => {
return(
<h3>Gallery</h3>
<button onClick={()=> setCategory('All')}>All</button>
{categoryList.map(item => (
<button key={item} onClick={()=> setCategory(item)}>{item}</button>
))}
)
};
export default FilterButton;
And here I am not able to fix the infinite loop:
const Gallery = ({data, category}) => {
const [photos, setPhotos] = useState([]);
useEffect(()=>{
let temp = []
if (category === 'All'){
setPhotos(data)
}else{
data.map(item => {
temp.push(item)
})
}
setPhotos(temp)
})
return(
photos.map((item =>
<img key={item.id} src={item.image}/>
))
)
};
export default Gallery;
If I add empty array to the useEffect it does not work at all. Also I am using styled components and framer motion but it should not have affect on this I hope.
First, I see that you're never setting your state for categoryList.
After modifying categoryList, you should call setCategoryList() with the new category list. This way, the state variable will be 'remembered' when the component is re-rendered.
You can read about the useState hook here.
Additionally, for the useEffect hook, the 'empty array' you pass in at the end is actually an array of variables to 'watch' for changes. If you pass an empty array, the useEffect will only run once, at the first page load. However, you can pass in something like [category] so that the useEffect is only called when the category variable is modified, which I persume is what you want to do.
Use React for ephemeral state that doesn’t matter to the app globally
and doesn’t mutate in complex ways. For example, a toggle in some UI
element, a form input state. Use Redux for state that matters globally
or is mutated in complex ways. For example, cached users, or a post
draft.
My redux state is only representative of what has been saved to my backend database.
For my use case there is no need for any other part of the application to know about a record in an adding/editing state..
However, all my communication with my API is done through redux-thunks. I have found, getting data from redux into local state for editing is tricky.
The pattern I was trying to use:
const Container = () => {
// use redux thunk to fetch from API
useEffect(() => {
dispatch(fetchThing(id));
}, [dispatch, id]);
// get from redux store
const reduxThing = useSelector(getThing);
const save = thing => {
dispatch(saveThing(thing));
};
return (
{!fetching &&
<ThingForm
defaults={reduxThing}
submit={save}
/>}
);
};
const ThingForm = ({defaults, submit}) => {
const [values, setValues] = useState({ propA: '', propB: '', ...defaults});
const handleChange = { /*standard handleChange code here*/ };
return (
<form onSubmit={() => submit(values)}>
<input type="text" name="propA" value={values.propA} onChange={handleChange} />
<input type="text" name="propB" value={values.propB} onChange={handleChange} />
</form>
);
};
How I understand it, ThingForm is unmounted/mounted based upon "fetching." However, it is a race condition whether or not the defaults get populated. Sometimes they do, sometimes they don't.
So obviously this isn't a great pattern.
Is there an established pattern for moving data into local state from redux for editing in a form?
Or should I just put my form data into redux? (I don't know if this would be any easier).
EDIT: I think this is essentially what I am fighting: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
But no recommendation really clearly fits. I am strictly using hooks. I could overwrite with useEffect on prop change, but seems kind of messy.
EDIT:
const Container = () => {
// use redux thunk to fetch from API
useEffect(() => {
dispatch(fetchThing(id));
}, [dispatch, id]);
// get from redux store
const reduxThing = useSelector(getThing);
const save = thing => {
dispatch(saveThing(thing));
};
return (
{!fetching &&
<ThingForm
defaults={reduxThing}
submit={save}
/>}
);
};
const ThingForm = ({defaults, submit}) => {
const [values, setValues] = useState({ propA: '', propB: '', ...defaults});
const handleChange = { /*standard handleChange code here*/ };
useEffect(() => {
setValues({...values, ...defaults})
}, [defaults]);
const submitValues = (e) => {
e.preventDefault();
submit(values)
}
return (
<form onSubmit={submitValues}>
<input type="text" name="propA" value={values.propA} onChange={handleChange} />
<input type="text" name="propB" value={values.propB} onChange={handleChange} />
</form>
);
};
What you are doing is the right way, there's no reason why you should put the form data in the redux store. Like you said, "there is no need for any other part of the application to know about a record in an adding/editing state"
And that's correct.
The only problem you have is here:
{!fetching &&
<ThingForm
defaults={reduxThing}
submit={save}
/>}
Assuming fetching is true on every dispatch:
Instead of trying to hide the component (unmounting essentially), you should maybe use a spinner that overlays the page?
I don't know the rest of your code to comment on a better approach.
You also don't have to add dispatch to the dependency array
useEffect(() => {
dispatch(fetchThing(id));
}, [id]);
From the react docs:
React guarantees that dispatch function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.