Deep copy data update problem in react native - javascript

I am using this
const HomeProfile = ({data}) =>{
var tempData = {}
const onOptionClick = ( clicked_index) => {
tempData = { ...data[0].atpData }
tempData.selected_index = clicked_index
}
const addInProps = () =>{
data[0].atpData=tempData
}
const checkData = () =>{
console.log('tempData',tempData)
}
return(
<View>
<Text></Text>
</View>
)
}
When check data in checkData function it is giving me the empty data. But if i am using this hooks like
const [tempData,settempData] = usestate({})
My Problem:
It is working good as i am using it with var tempData = {} but problem in that after adding data. Still getting empty Data in the checkData() functions
If i am using with const [tempData,settempData] = usestate({}) then it is changing my data in the data[0].atpData without hitting the function so i am not getting how to resolve it. But i am getting data checkData() function as i required.

You should use useState hook, so you need initialize it as empty object, and call setter inside your method as you desire:
const HomeProfile = ({data}) =>{
const [tempData, setTempData] = useState({})
const onOptionClick = ( clicked_index) => {
setTempData({...data[0].atpData, selected_index: clicked_index})
}
const addInProps = () =>{
data[0].atpData=tempData
}
const checkData = () =>{
console.log('tempData',tempData)
}
return(
<View>
<Text></Text>
</View>
)
}

you can use useEffect hook when detect the object change

Related

Empty Object on React useEffect

In my project I have the component ExportSearchResultCSV. Inside this component the nested component CSVLink exports a CSV File.
const ExportSearchResultCSV = ({ ...props }) => {
const { results, filters, parseResults, justify = 'justify-end', fileName = "schede_sicurezza" } = props;
const [newResults, setNewResults] = useState();
const [newFilters, setNewFilters] = useState();
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(true)
const [headers, setHeaders] = useState([])
const prepareResults = () => {
let newResults = [];
if (results.length > 1) {
results.map(item => {
newResults.push(parseResults(item));
}); return newResults;
}
}
const createData = () => {
let final = [];
newResults && newResults?.map((result, index) => {
let _item = {};
newFilters.forEach(filter => {
_item[filter.filter] = result[filter.filter];
});
final.push(_item);
});
return final;
}
console.log(createData())
const createHeaders = () => {
let headers = [];
newFilters && newFilters.forEach(item => {
headers.push({ label: item.header, key: item.filter })
});
return headers;
}
React.useEffect(() => {
setNewFilters(filters);
setNewResults(prepareResults());
setData(createData());
setHeaders(createHeaders());
}, [results, filters])
return (
<div className={`flex ${justify} h-10`} title={"Esporta come CSV"}>
{results.length > 0 &&
<CSVLink data={createData()}
headers={headers}
filename={fileName}
separator={";"}
onClick={async () => {
await setNewFilters(filters);
await setNewResults(prepareResults());
await setData(createData());
await setHeaders(createHeaders());
}}>
<RoundButton icon={<FaFileCsv size={23} />} onClick={() => { }} />
</CSVLink>}
</div >
)
}
export default ExportSearchResultCSV;
The problem I am facing is the CSV file which is empty. When I log createData() function the result is initially and empty object and then it gets filled with the data. The CSV is properly exported when I edit this component and the page is refreshed. I tried passing createData() instead of data to the onClick event but it didn't fix the problem. Why is createData() returning an empty object first? What am I missing?
You call console.log(createData()) in your functional component upon the very first render. And I assume, upon the very first render, newFilters is not containing anything yet, because you initialize it like so const [newFilters, setNewFilters] = useState();.
That is why your first result of createData() is an empty object(?). When you execute the onClick(), you also call await setNewFilters(filters); which fills newFilters and createData() can work with something.
You might be missunderstanding useEffect(). Passing something to React.useEffect() like you do
React.useEffect(() => {
setNewFilters(filters);
setNewResults(prepareResults());
setData(createData());
setHeaders(createHeaders());
}, [results, filters]) <-- look here
means that useEffect() is only called, when results or filters change. Thus, it gets no executed upon initial render.

Asynchronously update state hook in react

I am trying to set a state generated by useState in an async function, but I found if I do like this, react would render my component infinitely.
This is a demo I made
export const App = () => {
const [nodes, setNodes] = useState([])
// some async refresh code, like http request, like axios.get("/list-nodes").then ...
const refresh = async () => {
let arr = []
for (let i = 0; i < 10; i++) {
arr.push(Math.random())
}
setNodes(arr)
}
refresh();
return (
<div>
{
nodes.map(v =>
<div>
value: {v}
</div>)
}
</div>
)
}
In the code, the rendering is continuously happening and the numbers are keeping changing.
May I ask how I am able to set a state in an async function correctly?
You need to use a useEffect hook to fetch data on the first render only. If not, the component fetches at every render, which happens every time the state is updated, which render the component ...
export const App = () => {
const [nodes, setNodes] = useState([]);
useEffect(()=>{
//Self calling async function
//Be carefull to add a ; at the end of the last line
(async () => {
let data = await fetch(url)
let json = await data.json()
setNodes(data)
})()
},[])
return (
<div>
{ nodes.map(node => <div>Value: {node}</div>) }
</div>
)
}
You can use useEffect
export default function App() {
const [nodes, setNodes] = useState([]);
const refresh = async () => {
let arr = [];
for (let i = 0; i < 10; i++) {
arr.push(Math.random());
}
setNodes(arr);
};
useEffect(() => {refresh(); }, []);
return (
<div className="App">
{nodes.map((v) => (
<div>value: {v}</div>
))}
</div>
);}

How to trigger a function automatically when the data in an array changes?

i just started React js and im trying to create a simple recipe web with API.
I am trying to create a page that will display the data of favorite recipes from an array in local storage using map() like below.
const FavRecipes = () => {
const [recipeArray, setRecipeArray] = useState([]);
const refreshData = () => {
const existedFavRecipe = localStorage.getItem("FavRecipes");
const data = existedFavRecipe !== null ? JSON.parse(existedFavRecipe) : [];
setRecipeArray(data);
}
return (
<FavRecipesContainer>
{recipeArray.map( e => (
<>
<FavRecipeImage src ={e.image} />
<FavRecipeTitle>{e.title}</FavRecipeTitle>
</>
))}
</FavRecipesContainer>
)
}
The problem is I want the function of refreshData to get triggered automatically everytime the data in the array changes because i will create a delete button that can delete the favorite recipes. I am thinking of using useEffect() but I dont know how to do it. Is there any suggestion to solve this? Would appreciate it!
I want the function of refreshData to get triggered automatically everytime the data in the array changes because i will create a delete button that can delete the favorite recipes..
The problem is that there is no event that fires when local storage is changed by other code in the same window (the storage event only fires when storage is changed in other windows).
There are dodgy solutions like these, but really just make sure that your deletion code calls refreshData as part of its logic. Or actually, you don't even need refreshData, you could maintain the array locally in the component and just echo it to local storage:
const FavRecipes = () => {
const [recipeArray, setRecipeArray] = useState([]);
// Initial data load
useEffect(() => {
const existedFavRecipe = localStorage.getItem("FavRecipes");
const data = existedFavRecipe !== null ? JSON.parse(existedFavRecipe) : [];
setRecipeArray(data);
}, []);
// Deletion
const deleteRecipe = (recipe) => {
setRecipeArray(recipes => {
recipes = recipes.filter(r => r !== recipe);
localStorage.setItem("FavRecipes", JSON.stringify(recipes));
return recipes;
});
};
return (
<FavRecipesContainer>
{recipeArray.map( recipe => (
<>
<FavRecipeImage src ={recipe.image} />
<FavRecipeTitle>{recipe.title}</FavRecipeTitle>
<button onClick={() => deleteRecipe(recipe)}>X</button>
</>
))}
</FavRecipesContainer>
);
};
If you also want to listen for changes in other windows (users do that):
const FavRecipesKey = "FavRecipes";
const FavRecipes = () => {
const [recipeArray, setRecipeArray] = useState([]);
// Initial data load and watch for changes in other windows
useEffect(() => {
function refreshData() {
const existedFavRecipe = localStorage.getItem(FavRecipesKey);
const data = existedFavRecipe !== null ? JSON.parse(existedFavRecipe) : [];
setRecipeArray(data);
}
function storageEventHandler({key}) {
if (key === FavRecipesKey) {
refreshData();
}
}
refreshData();
window.addEventListener("storage", storageEventHandler);
return () => {
window.removeEventListener("storage", storageEventHandler);
};
}, []);
// Deletion
const deleteRecipe = (recipe) => {
setRecipeArray(recipes => {
recipes = recipes.filter(r => r !== recipe);
localStorage.setItem(FavRecipesKey, JSON.stringify(recipes));
return recipes;
});
};
return (
<FavRecipesContainer>
{recipeArray.map( recipe => (
<>
<FavRecipeImage src ={recipe.image} />
<FavRecipeTitle>{recipe.title}</FavRecipeTitle>
<button onClick={() => deleteRecipe(recipe)}>X</button>
</>
))}
</FavRecipesContainer>
);
};
This is a tough one. The closest one in React camp is useMutableSource. https://github.com/reactjs/rfcs/blob/main/text/0147-use-mutable-source.md
However useMutableSource is a bit too advanced. So maybe we should think of the problem in another way. For instance, if you can know the time or component who invokes localStorage.setItem, then you can turn it in a context.
Define a context
Create a file that you can share to other components.
const RecipeContext = React.createContext()
export default RecipeContext
Import it to set it
When you want to set the content, import the context and write it via current.
import RecipeContext from './RecipeContext'
const AComponent = () => {
const recipe = React.useContext(RecipeContext)
recipe.current = recipeArray
}
Import it to read it
When you want to read out the current value, import the context and read it via current.
import RecipeContext from './RecipeContext'
const BComponent = () => {
const recipe = React.useContext(RecipeContext)
const onClick = () => {
console.log(recipe.current)
}
}
You should be able to use RecipeContext as a "global" variable similar to localStorage. Even better if you have any default value, you can set it at the time you create it.
const RecipeContext = React.createContext(defaultRecipeArray)
You don't even need a provider <RecipeContext.Provider />, because you are using it as a "global" context, a very special usage.

Multiple rendering problem and how can I use the useEffect here

Filtering data by using this function, if I am calling this function in useEffect than its pushes to search results and not working well.
const AdvanceSearch = (props) => {
const [region, setRegion] = useState("");
const [searchStuhl, setSearchStuhl] = useState("");
const filterData = (async ()=> {
const filtereddata = await props.data.filter((item) => {
return (
item.region.toLowerCase().includes(region.toLowerCase())
&& item.stuhl.toLowerCase().includes(searchStuhl.toLowerCase())
)}
) await props.history.push({
pathname: '/searchResults/',
state:
{
data:filtereddata
}
})
})
//If the props. history.push is pass here instead of the above function then its sending the empty array and not the filtered data
const handleSubmit = async (e) => {
e.preventDefault()
await filterData();
}
when you are changing the navigation URL with some data and there is multiple rendering then the following problem would be there.
Check your route configuration for the path. is it configured to hold the changed path: in this scenario, you get fluctuated UI or we can say multiple renders
yes you can use useEffect hooks to change the path and set the data here is the peace of code. here whenever your props.data will be changed filteredData will run and it will return the value when data will be available.
const filteredData = useCallback(() => {
if(props.data){
const filteredData = props.data.filter((item) => (
item.region.toLowerCase().includes(region.toLowerCase())
&&item.stuhl.toLowerCase().includes(searchStuhl.toLowerCase())
));
return filteredData
}
},
[props && props.data]);
useEffect(()=> {
const data = filteredData();
if(data){
props.history.push({
pathname:'/search-results',
state:{data}
});
}
},[filteredData])
Try to remove async / await from the function. You don't need them to filter an array.

How to use useState with React-redux-firebase to re-render

I'm exploring hooks with react-redux-firebase but my "setDataProducts" is behaving oddly.
I'm using useEffect() like I could use componentDidMount() but not sure if this is the right way.
export default function ProductList() {
const [dataProducts, setDataProducts] = useState([]);
const firestore = useFirestore();
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
data.docs.forEach((product) => {
setDataProducts([...dataProducts, product.data()]);
console.log(product.data());
});
};
useEffect(() => {
fetchProducts();
}, []);
return (
<div>
{isLoaded &&
dataProducts.map((product) => {
return (
<div>
<h4>{product.title}</h4>
<h3>{product.price}</h3>
</div>
);
})}
</div>
);
}
I cannot render the both products I have in Firestore. Only One is rendering... So I dont understand. Should not it rerender when state is updated ?
Thanks for reply
We can see there was not rerendering
I think it is because you called setDataProducts again before dataProducts updated.
Please replace fetchProducts method with my code following:
const fetchProducts = async () => {
const response = firestore.collection("products");
const data = await response.get();
const newProducts = data.docs.map((product) => product.data());
console.log(newProducts);
setDataProducts([...dataProducts, ...newProducts]);
};

Categories

Resources