I am using react usestate() and I want to update device state(It is an object)
my problem is when ShowRelays component renders for the first time device is an empty object and It does not get updated during first rendering, but for the next renders everything is fine
How can I update device state for the first time rendering?
(sorry for my bad english)
.
function ShowRelays(props) {
const [device, setDevice] = useState({})
let reduxDevices = useSelector(state => state.devicesReducer.devices)
let findDevice = () => {
let myDevice = reduxDevices.find(x => x._id === props.id)
setDevice(prevState => {
return {
...prevState,
...myDevice
}
})
}
useEffect(() => {
if (props.show) {
findDevice()
}
}, [props.show])
return
(
<div>
test
</div>
)
}
myDevice object is like:
{active: true, name: "device1", id: "deviceId"}
You can pass a function to your useState-hook which will calculate your initial value for device.
see lazy init state
function ShowRelays(props) {
let reduxDevices = useSelector(state => state.devicesReducer.devices);
const [device, setDevice] = useState(() => {
return reduxDevices.find(x => x._id === props.id)
});
return <div>test</div>;
}
An other possible solution for your problem without using a separate state could be the following (directly select the right device from your selector function):
function ShowRelays(props) {
const device = useSelector(state => {
return state.devicesReducer.devices.find(x => x._id === props.id);
});
return <div>test</div>;
}
I have the following scenario, i am mapping files with a Media Card Component
const [files, setFiles] = useState([]);
const thumbs = files.map((file, i) => (
<MediaCard
onClick={() => handleCardClick(i)}
checked={file.checked}
/>
));
Then i have a method which make my MediaCard Component checked in case it is not.
const handleCardClick = (index) => {
const newFiles = files.map((file, i) => {
if (i === index) file.checked = !file.checked
return file
});
setFiles(newFiles);
}
Now i need a button with a new method called for example allSelection which make checked all my files mapped in the MediaCard Component, how can i achieve it?
This is the function you need:
const allSelection = () => {
setFiles((files) =>
files.map((file) => {
file.checked = true;
return file;
})
);
};
I got stuck with the following and haven't found any answer after a lot of research.
What I want to do: simply getting users inluding their images from a firestore-DB with react and the useeffect-hook and displaying them.
The DB-structure looks as follows:
https://i.stack.imgur.com/sDcrv.png
So the pictures are a subcollection of the users-collection.
After getting the users from the users-collection, I'm doing a second request for adding the users images to this specific user using Object.assign. After every forEach-run over the users-collection I'm setting the users-array with setUsers((oldUsers) => [...oldUsers, currentUser]);. Logging the users-array shows uses INCLUDING their images.
The problem: When trying to render the images, they are always undefined.
Workaround: Pressing a button that calls a function for re-setting the users:
const reRenderUsers = () => {
if (userDataLoaded === false) {
setUserDataLoaded(true);
}
const copy = [...users];
setUsers(copy);
};
^ This solves the problem and all images where shown.
Question: Is there any possibility showing the images instantly without the need of "re-rendering" the users? Am I using the useEffect-hook wrong for example? I'm thankful for any advice. Many thanks in advance!
Here the full code:
const [users, setUsers] = useState([]);
const [userDataLoaded, setUserDataLoaded] = useState(false);
useEffect(() => {
const unsubscribe = database.collection("users").onSnapshot((snapshot) => {
snapshot.forEach((doc) => {
const currentUser = {
id: doc.id,
...doc.data(),
};
database
.collection("users")
.doc(currentUser.id)
.collection("pictures")
.get()
.then((response) => {
const fetchedPictures = [];
response.forEach((document) => {
const fetchedPicture = {
id: document.id,
...document.data(),
};
fetchedPictures.push(fetchedPicture);
});
currentUser.pictures = [];
Object.assign(currentUser.pictures, fetchedPictures);
})
.catch((error) => {
console.log(error);
});
setUsers((oldUsers) => [...oldUsers, currentUser]);
});
});
return () => {
unsubscribe();
};
}, []);
const reRenderUsers = () => {
if (userDataLoaded === false) {
setUserDataLoaded(true);
}
const copy = [...users];
setUsers(copy);
};
return (
<div>
{!userDataLoaded ? (
<button onClick={reRenderUsers}> load users </button>
) : null}
{users.map((user, index) => (
<div key={user.id}>
{user.pictures && <img src={user.pictures[0].imageUrl}></img>}
</div>
))}
</div>
);
}
export default User;
This is because you are calling setUser before the firebase response completes the callback chain. You need to update the state right after the loop inside the success callback completed. I have updated useEffect to update it right after the callback
useEffect(() => {
const unsubscribe = database.collection("users").onSnapshot((snapshot) => {
snapshot.forEach((doc) => {
const currentUser = {
id: doc.id,
...doc.data(),
};
database
.collection("users")
.doc(currentUser.id)
.collection("pictures")
.get()
.then((response) => {
const fetchedPictures = [];
response.forEach((document) => {
const fetchedPicture = {
id: document.id,
...document.data(),
};
fetchedPictures.push(fetchedPicture);
});
currentUser.pictures = fetchedPictures;
setUsers((oldUsers) => [...oldUsers, currentUser]);
})
.catch((error) => {
console.log(error);
});
//dont need this here
//setUsers((oldUsers) => [...oldUsers, currentUser]);
});
});
return () => {
unsubscribe();
};
}, []);
Good Luck
I am creating a Memory Game and I can't understand how can these two lines order matters in the code:
const timer = setTimeout(() => {
if (picks.length === 2) {
//this order that works
setPicks([])
setCards(cards => cards.map((c) => ({ ...c, isFlipped: false })));
}
}, 500)
const timer = setTimeout(() => {
if (picks.length === 2) {
//this order that doesn't work
setCards(cards => cards.map((c) => ({ ...c, isFlipped: false })));
setPicks([])
}
}, 500)
In the first case, matched state receive the correct updates so why similar cards match, but in the second case it seems that matched state doesn't get correct values.
I can't understand how the order of these two lines affect matched state
const App = () => {
const icons = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
const shuffle = cards => [...cards].sort(() => Math.random() - 0.5);
const shuffledCards = useMemo(() => {
return shuffle(icons).map((icon, i) => ({
icon: icon,
id: i,
isFlipped: false
}));
});
const [picks, setPicks] = useState([]);
const [cards, setCards] = useState(shuffledCards);
const [matched, setMatched] = useState([]);
const handleClick = id => {
!picks.includes(id) && picks.length !== 2 && setPicks([...picks, id]);
};
useEffect(() => {
setCards(cards =>
cards.map((c, i) =>
picks.includes(c.id) ? { ...c, isFlipped: true } : c
)
);
const matches = cards.reduce((matches, { icon, isFlipped }) => {
!matches[icon] && (matches[icon] = 0);
isFlipped && matches[icon]++;
return matches;
}, {});
Object.entries(matches).forEach(([icon, count]) => {
count === 2 && !matched.includes(icon) && setMatched([...matched, icon]);
});
const timer = setTimeout(() => {
if (picks.length === 2) {
//the problem is here, that order doesn't work
setCards(cards => cards.map(c => ({ ...c, isFlipped: false })));
setPicks([]);
}
}, 500);
return () => clearTimeout(timer);
}, [picks]);
return (
<div class="game">
<Deck
cards={cards}
handleClick={handleClick}
picks={picks}
matched={matched}
/>
</div>
);
};
const Deck = ({ numbers, cards, ...props }) => {
return (
<div class="deck">
{cards.map((c, i) => {
return <Card i={i} card={c} {...props} />;
})}
</div>
);
};
const Card = ({ handleClick, picks, card, i, matched }) => {
const { icon, isFlipped, id } = card;
return (
<div
className={`card${isFlipped ? " flipped" : ""} ${
matched.includes(icon) ? "matched" : ""
}`}
onClick={() => handleClick(id)}
>
<div class="front" />
<div class="back">{icon}</div>
</div>
);
};
ISSUE :
Here main issue is with the below line position in code:
const matches = cards.reduce((matches, { icon, isFlipped }) => {
So to understand the issue, first, we need to understand what is wrong with the above line position, please do read comments to understand the issue better
setCards(cards => // <--------- 1.
cards.map((c, i) => // <--------- 2.
picks.includes(c.id) ? { ...c, isFlipped: true } : c
)
);
// 1. current state of cards
// 2. returning updated state of cards
// so, if you are aware of async behavior or setState, then you know
// the below line will always point to snapshot of cards when the useEffect run
// and not to the 1. or 2.
// So here cards is pointing to snapshot when useEffect run
const matches = cards.reduce((matches, { icon, isFlipped }) => {
Now, let's see with both cases
useEffect(() => {
// Here you will always get the snapshot of cards values, which is currently available
setCards(cards => // <--------- 1.
cards.map((c, i) => // <--------- 2.
picks.includes(c.id) ? { ...c, isFlipped: true } : c
)
);
// so if you understand correctly, above code block doesn't make any sense for the below line
const matches = cards.reduce((matches, { icon, isFlipped }) => {
....
const timer = setTimeout(() => {
if (picks.length === 2) {
//this order that works
setPicks([]) // <---- this will trigger useEffect , and send the cards value from 2.
setCards(cards => cards.map((c) => ({ ...c, isFlipped: false })));
}
}, 500)
const timer = setTimeout(() => {
if (picks.length === 2) {
//this order that works
setCards(cards => cards.map((c) => ({ ...c, isFlipped: false }))); // <------------- 3.
setPicks([]) // <---- this will trigger useEffect , and send the cards value from 3.
}
}, 500)
},[picks]); // <----- useEffect is dependent on picks
DEBUGGING DEMO :
You can change the order as per your case and check the console and see what are the values you are getting for cards when you switch the order
Solution :
Hope this will make your doubts clear, now If we are clear with issue, what's the solution then, here we can solve it 2 ways but the basic is common in both way and that is Always work with updated cards value, in that case order doesn't matter any longer
FIRST SOLUTION :
You can put the match condition code block inside the setCards(cards => { and work with latest cards values
useEffect(() => {
setCards(cards => {
const updated = cards.map((c, i) =>
picks.includes(c.id) ? { ...c, isFlipped: true } : c
)
// take whole code block and put it inside the `setCards`
// and directly work with latest update value
const matches = updated.reduce((matches, { icon, isFlipped }) => { // <---- HERE
!matches[icon] && (matches[icon] = 0);
isFlipped && matches[icon]++;
return matches;
}, {});
Object.entries(matches).forEach(([icon, count]) => {
count === 2 && !matched.includes(icon) && setMatched([...matched, icon]);
});
return updated;
});
const timer = setTimeout(() => {
if (picks.length === 2) {
setCards(cards => cards.map(c => ({ ...c, isFlipped: false })));
setPicks([]);
}
}, 500);
return () => clearTimeout(timer);
}, [picks]);
WORKING DEMO :
SECOND SOLUTION : ( and I would suggest this one )
You can create a useEffect for cards value, so that you will always get the updated cards value and based on that you can set the match values
useEffect(() => {
setCards(cards =>
cards.map((c, i) =>
picks.includes(c.id) ? { ...c, isFlipped: true } : c
)
);
const timer = setTimeout(() => {
if (picks.length === 2) {
setCards(cards => cards.map(c => ({ ...c, isFlipped: false })));
setPicks([]);
}
}, 500);
return () => clearTimeout(timer);
}, [picks]);
useEffect(() => {
const matches = cards.reduce((matches, { icon, isFlipped }) => {
!matches[icon] && (matches[icon] = 0);
isFlipped && matches[icon]++;
return matches;
}, {});
Object.entries(matches).forEach(([icon, count]) => {
count === 2 && !matched.includes(icon) && setMatched([...matched, icon]);
});
}, [cards]);
WORKING DEMO :
The code block you are asking about is responsible for resetting the card state everytime two cards are picked.
const timer = setTimeout(() => {
if (picks.length === 2) {
setCards(cards => cards.map(c => ({ ...c, isFlipped: false })));
setPicks([]);
}
}, 500);
The state of the picked cards picks must be reset by passing it an empty array through the hook setPicks before the card state is set with the hook setCards since picks can only be set if there is not exactly 2 picks made. Resetting the picks length to zero is required in the current logic you have because of what you have in your click handler.
const handleClick = id => {
!picks.includes(id) && picks.length !== 2 && setPicks([...picks, id]);
};
I have a script that works fine in the gatsby dev server but when trying to run gatsby build I get an error stating
"document" is not available during server side rendering.
The error fires on this code snippet
const useActiveElement = () => {
const [active, setActive] = useState(document.activeElement)
I assume that this error is also present any place I use document in this file, maybe outside of the useEffect places I use document. However, I have not been able to properly convert the code to using react hooks in a way that would allow Gatsby to build. Any suggestions on what I should do?
The full file (minus imports)
const useActiveElement = () => {
const [active, setActive] = useState(document.activeElement)
const handleKeyup = (e) => {
if (e.keyCode === 9) setActive(document.activeElement)
}
useEffect(() => {
document.addEventListener("keyup", handleKeyup)
return () => {
document.removeEventListener("keyup", handleKeyup)
}
}, [])
return active
}
const Layout = ({
children,
crumbLabel,
subStatus,
parentPageLabel,
parentPageLink,
}) => {
const focusedElement = useActiveElement()
const data = useStaticQuery(graphql`
query SiteTitleQuery {
sanitySiteSettings {
title
}
}
`)
useEffect(() => {
const prevTabbedElements = document.getElementsByClassName("tabbed")
for (let i = 0; i < prevTabbedElements.length; i++) {
prevTabbedElements[i].classList.remove("tabbed")
}
focusedElement.value && focusedElement.value.classList.add("tabbed")
focusedElement.classList.add("tabbed")
}, [focusedElement])
return (
<>
<Header siteTitle={data.sanitySiteSettings.title}/>
<nav>
{subStatus ? (
<Parentcrumbs
crumbLabel={crumbLabel}
parentPageLabel={parentPageLabel}
parentPageLink={parentPageLink}
/>
) : (
<Breadcrumbs crumbLabel={crumbLabel}/>
)}
</nav>
<main>{children}</main>
<Footer/>
</>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
At the initial render point, your document is not defined yet so:
const useActiveElement = () => {
const [active, setActive] = useState('')
const handleKeyup = (e) => {
if (e.keyCode === 9) setActive(document.activeElement)
}
useEffect(() => {
setActive(document.activeElement);
document.addEventListener("keyup", handleKeyup)
return () => {
document.removeEventListener("keyup", handleKeyup)
}
}, [])
return active
}
Init the useState as empty and fill in in your componentDidMount (useEffect with empty deps).