I am trying to use redux value to set an initial state of react component using useState.when I am trying to set the state of setIsStar it says currentChannelName is null. How can I avoid this? or is there any other way
const currentChannel = useSelector(state => state.channel.currentChannel);
const currentChannelName = currentChannel.name;
const [isStar, setIsStar] = useState({
[currentChannelName]: true
});
Possible solution is to combine it with useEffect:
const currentChannel = useSelector(state => state.channel.currentChannel);
const currentChannelName = currentChannel.name;
useEffect(() => {
if(currentChannelName) {
setIsStar({[currentChannelName]: true});
}
}, [currentChannelName]); // listen only to currentChannelName changes
const [isStar, setIsStar] = useState(currentChannelName ? {
[currentChannelName]: true
}: {});
`
You should avoid this as it dilutes your state across two separate domains.
Keep app-wide state in your redux store, keep local component state in your components.
If you really do want to do this, you'll probably just have to deal with the initial case where your component has mounted but the store is not populated with the data you need.
const currentChannel = useSelector(state => state.channel.currentChannel);
const currentChannelName = currentChannel.name;
const [isStar, setIsStar] = useState(currentChannelName && {
[currentChannelName]: true
});
The easiest solution would be:
// Redux state
const user = useSelector((state) => state.user);
const [firstName, setFirstName] = useState(user.firstName || '');
Related
I am newbie in React Native and I am trying to store and get an array with AsyncStorage in ReactNative.
I have two problems.
First, I do not know why but when I storage data, it only works the second time but I am calling first the set of useState.
const handleAddTask = () => {
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
storeData(taskItems);
};
Second, how can I call the getData function to get all the data and show it? Are there something like .onInit, .onInitialize... in ReactNative? Here is my full code
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const handleAddTask = () => {
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
storeData(taskItems);
};
const completeTask = (index) => {
var itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
setTaskItems(itemsCopy);
storeData(taskItems);
}
const storeData = async (value) => {
try {
await AsyncStorage.setItem('#tasks', JSON.stringify(value))
console.log('store', JSON.stringify(taskItems));
} catch (e) {
console.log('error');
}
}
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#tasks')
if(value !== null) {
console.log('get', JSON.parse(value));
}
} catch(e) {
console.log('error get');
}
}
Updating state in React is not super intuitive. It's not asynchronous, and can't be awaited. However, it's not done immediately, either - it gets put into a queue which React optimizes according to its own spec.
That's why BYIRINGIRO Emmanuel's answer is correct, and is the easiest way to work with state inside functions. If you have a state update you need to pass to more than one place, set it to a variable inside your function, and use that.
If you need to react to state updates inside your component, use the useEffect hook, and add the state variable to its dependency array. The function in your useEffect will then run whenever the state variable changes.
Even if you're update state setTaskItems([...taskItems, task]) before save new data in local storage, storeData(taskItems) executed before state updated and save old state data.
Refactor handleAddTask as below.
const handleAddTask = () => {
Keyboard.dismiss();
const newTaskItems = [...taskItems, task]
setTaskItems(newTaskItems);
storeData(newTaskItems);
};
I currently have a component (A) that has many local state variables, and also uses useSelector((state) => state.app.<var>. Some of the local state variables rely on that global state, and I need to render one local variable onto the screen.
code example:
const ComponentA = () => {
const globalState = useSelector((state) => state.app.globalState);
// CASE 1: WORKS
const localState1 = 'hello' + globalState + 'world';
// CASE 2: DOESN't WORK
const [localState1, setLocalState1] = useState(null);
const [lcoalState2, setLocalState2] = useState(null);
useEffect(() => {
}, [localState1]);
useEffect(() => {
setLocalState1('hello' + globalState + 'world')
}, [localState2]);
return (
.... code changes
<p>{localState1}</p>
);
}
Case 1 results in the localState1 properly being updated and rendered on the screen, but in Case 2 localState1 is not updated on the screen.
I have no idea why setting localState1 to a regular variable instead of a local state variable works. I thought that a change in local state would cause a re-render on the DOM, meaning I could visually see the change. Could someone explain why the local state case fails to update and how to fix it?
You need to make your useEffect aware of globalState change by adding it to dependencies array (anyway you should get a linting warning when you forget it, like in your case):
const ComponentA = () => {
const globalState = useSelector((state) => state.app.globalState);
const [localState1, setLocalState1] = useState(null);
useEffect(() => {
setLocalState1("hello" + globalState + "world");
}, [globalState]);
return <p>{localState1}</p>;
};
Moreover, you don't really need a state for it, just implement the selector according to your needs, it always will update on state.app.globalState change:
const ComponentA = () => {
const globalStateString = useSelector(
(state) => `hello ${state.app.globalState} world`
);
return <p>{globalStateString}</p>;
};
When I call fetchProducts I want productPage state to be updated before it runs the next line of code since the next line requires the updated state.
The state only gets updated after the function has finished running. No matter where in the fetchProducts function I put console.log(productPage) it returns the state as it was before the function was called.
const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])
const fetchProducts = async () => {
setProductPage(productPage + 1) // DOES NOT UPDATE UNTIL AFTER FUNCTION RUNS
const newProductsData = await fetch(`/api/getproducts?page=${productPage}`)
const newProductsArr = await newProductsData.json()
setDisplayProducts(displayProducts.concat(newProductsArr))
}
Is this possible? I've found workarounds to the problem but it feels hacky and not ideal.
I'm using next.js also, I'm not sure if that would make a difference. I can't use normal variables as they reset on each render.
setProductPage will update the state and trigger a new render. productPage is not the state, it's just a variable holding the value of the state the moment you used useState. It will never change, it will always have the same value. You think it changes, but in reality, the function is simply executed again, but now productPage assigned with the new value.
So what can you do? You have two options.
The one it use useEffect, that will see if productPage changes (every time the function is executed = re-renders), and if it is, it will fetch the data and use setDisplayProducts to re-render the component with a new value in displayProducts. This is nice if you plan to have other ways of updating the productPage and you want them to also trigger the fetch.
const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])
const fetchProducts = async () => {
setProductPage(productPage + 1);
}
useEffect(() => {
const getDisplayProducts = async () => {
const newProductsData = await fetch(`/api/getproducts?page=${productPage}`)
const newProductsArr = await newProductsData.json()
setDisplayProducts(displayProducts.concat(newProductsArr))
};
getDisplayProducts();
}, [productPage]);
The second one is just store the new value in a variable, pass the variable to the setProductPage but use the variable in the fetch and not productPage.
const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])
const fetchProducts = async () => {
const nextProductPage = productPage + 1;
setProductPage(nextProductPage);
const newProductsData = await fetch(`/api/getproducts?page=${nextProductPage}`)
const newProductsArr = await newProductsData.json()
setDisplayProducts(displayProducts.concat(newProductsArr));
};
Setting a state is an async operation and you can't use it like a sync operation.
So, you must use useEffect hook, which runs after state changes.
import React, {useState, useEffect} from 'react';
const [productPage, setProductPage] = useState(1)
const [displayProducts, setDisplayProducts] = useState([])
const fetchProducts = async () => {
setProductPage(productPage + 1)
}
useEffect(() => {
const newProductsData = await fetch(`/api/getproducts?page=${productPage}`)
const newProductsArr = await newProductsData.json()
setDisplayProducts(displayProducts.concat(newProductsArr))
}, [productPage]); // <-- monitor productPage for changes
I'm trying to create a user profile page for my app, for this page I'm trying to get a doc with some user information from firestore, and populate fields that the user can click on and to change. When I added the setTags function to the db call below, it resulted in an infinite loop. Can anyone explain why this happens and how to fix it? I'm very new to using react hooks.
const UserProfile = ({history}) => {
const description = useRef('');
const name = useRef('');
const {currentUser} = useContext(AuthContext);
let [tags, setTags] = useState('');
let uData;
db.collection('Users').doc(currentUser.uid).get().then(doc => {
console.log("checking db")
console.log(doc.data())
uData = doc.data();
description.current = uData.description;
name.current = uData.name
setTags(uData.tags)
})
}
Functional components are run on every render. That means that on every rerender, you open the database to search for the current user, then set the tags. Crucially, updating the state always causes a rerender! This is a core feature of React that allows implicit updating and is one of the main reasons to use React in the first place.
If you want something to be run only once, you can still do that, but you need to simulate componentDidMount() from a class component. So, you can use useEffect:
// Add this import in
import { useEffect } from 'react';
const UserProfile = ({history}) => {
const description = useRef('');
const name = useRef('');
const {currentUser} = useContext(AuthContext);
let [tags, setTags] = useState('');
let uData;
useEffect(() => {
db.collection('Users').doc(currentUser.uid).get().then(doc => {
console.log("checking db")
console.log(doc.data())
uData = doc.data();
description.current = uData.description;
name.current = uData.name
setTags(uData.tags)
})
}, []); // Empty dependency list means this will only run on first render
}
I need to change my received data from redux store to another variable (and then modify it).
At the moment, I receive data from store after API call and it is stored offices, but it is not set to my officeData variable. Does anyone how can I solve that?This is my code:
const dispatch = useDispatch();
const offices = useSelector((state) => state.office.offices)
const [officeData, setOffices] = useState(offices);
debugger;
useEffect(()=> {
dispatch(getOffices());
setOffices(offices);
debugger
}, [dispatch])
If you don't even enter your useEffect i think it's because you give dispatch as a dependency.
Effect are triggered when component mount but also when component update (prop or state). So you could do something like that :
import { useEffect } from "react";
const dispatch = useDispatch();
const offices = useSelector((state) => state.office.offices);
const [officeData, setOffices] = useState(undefined);
const [didMount, setDidmount] = useState(false);
// When component mount, load your Offices data
useEffect(() => {
if(!didMount){
dispatch(getOffices());
setOffices(offices);
} else {
setDidmount(true);
}
});
useEffect(() => {
if(didMount) {
// When you update officeData, do your thing
}
}, [officeData]);
I don't know the behavior of useSelector, but i guess it does not trigger a rendering. Maybe you could have a useEffect with offices as dependency, just be careful not to loop !