I'm getting the state to populate my form but is not the actual state, apparently it is the initial state.
import React from "react";
import store from "../store";
const Invoice = () => {
console.log(store.getState().login);
return (
<div className="span7">
<h4 className="title">
<span className="text">
<strong>Datos</strong> de facturaciĆ³n
</span>
</h4>
...
However, my redux chrome DevTools is showing the actual state (the right one), why the difference?
Decide on 1 source of truth for your state - if you believe it should live in redux, then select the state from redux and DON'T combine it with a local useState:
import React from "react";
import { useSelector } from "react-redux";
const Invoice = () => {
const userAuth = useSelector(state => state.login);
console.log(userAuth);
}
If you want to log current state (from redux), I think you should use subscription (store.subscribe(...)) and put it into useEffect hook.
Something like this:
useEffect(() => {
const subscription = store.subscribe(() => {
console.log(store.getState().login);
});
return () => { subscription() } // clear out subscription when component unmounts
},[])
You are seeing difference between redux dev tools and your conslole.log because with store.getState() you are getting state at it was when your component was mounted. With subscribe you are getting notified any time some action get dispatched an state get changed.
Related
I know there are other articles and posts on this topic and almost all of them say to use the ! operator for a Boolean state value. I have used this method before but for the life of me I can not toggle this Boolean value.
import { useState } from 'react';
const [playerTurn, setPlayerTurn] = useState(true);
const changePlayerTurn = () => {
console.log(playerTurn); // returns true
setPlayerTurn(!playerTurn);
console.log(playerTurn); // also returns true
};
changePlayerTurn();
I have also tried setPlayerTurn(current => !current), commenting out the rest of my code to avoid interference, and restarted my computer in case that would help but I am still stuck with this issue.
Can anyone point out why this is not working?
The setPlayerTurn method queues your state change (async) so reading the state directly after will provide inconsistent results.
If you use your code correctly in a react component you will see that playerTurn has changed on the next render
You creating a async function, to solve this you can create a button in your component, which will run the function and you can use the "useEffect" hook to log every time the boolean changes... so you can see the changes taking place over time, like this:
import React, { useEffect } from "react";
import { useState } from "react";
const Player = () => {
const [playerTurn, setPlayerTurn] = useState(true);
useEffect(() => {
console.log(playerTurn);
}, [playerTurn]);
return <button onClick={() => setPlayerTurn(!playerTurn)}>change player turn</button>;
};
export default Player;
This is happening because setPlayerTurn is async function.
You can use another hook useEffect() that runs anytime some dependencies update, in this case your playerTurn state.
export default YourComponent = () => {
const [playerTurn, setPlayerTurn] = useState(true);
useEffect(() => {
console.log('playerTurn: ', playerTurn);
}, [playerTurn]);
const changePlayerTurn = () => {
setPlayerTurn(!playerTurn);
}
return (
<button onClick={changePlayerTurn}>Click to change player turn</button>
);
}
Basically whenever you use setState React keeps a record that it needs to update the state. And it will do some time in the future (usually it takes milliseconds). If you console.log() right after updating your state, your state has yet to be updated by React.
So you need to "listen" to changes on your state using useEffect().
useEffect() will run when your component is first mounted, and any time the state in the dependencies array is updated.
The value of the state only changes after the render. You can test this like:
// Get a hook function
const Example = ({title}) => {
const [playerTurn, setPlayerTurn] = React.useState(true);
React.useEffect(() => {
console.log("PlayerTurn changed to", playerTurn);
}, [playerTurn]);
console.log("Rendering...")
return (<div>
<p>Player turn: {playerTurn.toString()}</p>
<button onClick={() => setPlayerTurn(!playerTurn)}>Toggle PlayerTurn</button>
</div>);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
The callback inside the useEffect runs during the component mount and when one of the values inside the second argument, the dependecy array, changes. The depency here is playerTurn. When it changes the console will log.
As you will see, before this happens, the "Rendering..." log will appear.
I have this github repo: https://github.com/salmanfazal01/next-firebase-starter
Basically I am creating a basic boilerplate with NextJS, Firebase and Zustand as a global state manager
I am unable to persist a few states like theme, user, etc in Zustand. After refreshing the window, it defaults back to the default theme.
I did try the persist middleware provided by zustand and even though that works, it causes content mismatch error from the server and client
Error: Hydration failed because the initial UI does not match what was rendered on the server.
How would I go about persisting the theme mode in the state without using other libraries like next-themes?
Desire:
You want to persist state when moving between different pages while using server side rendering (SSR) with next.js. For example, you want to show a user's profile picture as you move between pages.
Initial implementation:
You save the data into the user's browser's local storage. You are using Zustand to do this.
Problem:
You see an error saying that the client-side content does not match the server rendered html. Example from next.js:
# Unhandled Runtime Error
Error: Text content does not match server-rendered HTML. See more info here: [https://nextjs.org/docs/messages/react-hydration-error](https://nextjs.org/docs/messages/react-hydration-error)
The reason for this is that you are rendering html on the server with a particular variables (For example, user_name). When you then load your page on the client-side and load the user_name variable from your client-side local storage, this variable contains a different value compared to the server-side. This causes a mismatch, which the error message highlights (https://nextjs.org/docs/messages/react-hydration-error).
Solution:
When the client-side html differs, load the variable from the local storage after the component/page has first rendered the html. To solve this with next.js, load your data from local storage only in the useEffect hook (equivalent to onMounted in Nuxt/Vue).
Example with Jotai:
// store.js
import { atomWithStorage } from 'jotai/utils'
export const countAtom = atomWithStorage('countAtom', 0);
// pages/login.js
import {useAtom} from "jotai";
import {countAtom} from "../stores/store";
import { useEffect, useState} from "react";
export default function Login(props) {
const [count, setCount] = useAtom(countAtom); // This gets the data from the store.
const add1 = () => {
setCount((count) => count + 1);
};
const [displayedCount, setDisplayedCount] = useState(); // Set a local variable.
useEffect(() => {
setDisplayedCount(count); // Set the local variable from our store data.
}, [count])
return (
<div >
<Head>
<title>Login</title>
</Head>
<main>
<p>
{ displayedCount }
Hello from the login
<button onClick={add1}>Add 1</button>
</p>
</main>
</div>
)
}
// pages/index.js
import {useAtom} from "jotai";
import {countAtom} from "../stores/store";
import { useEffect, useState} from "react";
export default function Home(props) {
const [count, setCount] = useAtom(countAtom); // This gets the data from the store.
const add1 = () => {
setCount((count) => count + 1);
};
const [displayedCount, setDisplayedCount] = useState(); // Set a local variable.
useEffect(() => {
setDisplayedCount(count); // Set the local variable from our store data.
}, [count])
return (
<div >
<Head>
<title>Home</title>
</Head>
<main>
<p>
{ displayedCount }
Hello from the home page
<button onClick={add1}>Add 1</button>
</p>
</main>
</div>
)
}
I suppose you need to use useEffect() to transfer your Zustand states to useState() to prevent Hydration error.
Your Zustand State Data that you imported in any page / component needs to be updated inside useEffect() to new useState().
https://nextjs.org/docs/messages/react-hydration-error
This error is caused by zustand fetching data from persist middleware storage and updating its store before NextJS hydration complete in the beginning.
This can be simply avoided if you render any zustand store variable after first useEffect hook fired. But if you are using multiple variables inside multiple components then this could be tedious and bad solution.
To avoid it you can create a NextJS Layout and wait till first useEffect hook fire to render child elements.
import { useEffect, useState } from "react"
const CustomLayout = ({ children }) => {
const [isHydrated, setIsHydrated] = useState(false);
//Wait till NextJS rehydration completes
useEffect(() => {
setIsHydrated(true);
}, []);
return (
<>
{isHydrated ? ( <div>{children}</div> ) : <div>Loading...</div>}
</>
);
};
export default CustomLayout;
I ran into similar problem when storing opened state in Zustand for an Advent Calendar site. Each day's state is stored on the client and persisted with Zustand persist. To fix, I just followed the guidance from NextJS to use a Dynamic Import and disable SSR for that component.
import dynamic from 'next/dynamic'
const DynamicHeader = dynamic(() => import('../components/header'), {
ssr: false,
})
Source: NextJS Dynamic Imports
This error is caused by Nextjs SSR so all we have to do is set isSSR to true meaning we meaning we assume on the start our page is being server-side rendered and when its true return null.... so we use useEffect to set isSSR to false on client-side immediately the page gets mounted.
I hope this solves your problem...
import { useState, useEffect } from 'react'
const App = ({ Component, pageProps }: AppProps) => {
const [isSSR, setIsSSR] = useState(true);
useEffect(() => {
setIsSSR(false)
}, [])
if (isSSR) return null;
return (
<Component {...pageProps} />
)
}
I currently store user name in redux store and show it in topbar after user logged in. (Shown in screenshots). However, it do not re-render automatically when redux state gets updated. I still need to do some actions in topbar then username does appear. Otherwise, it does not appear. So the question, how to force to re-render topbar component from my login component.
Another question: user name will disapprear when refresh page. So is there any option to persist data with redux state? Or it's impossible.
const Login = () => {
...
const handleLogin = async googleData => {
...
//store user name value als application state into redux store
store.dispatch(nameActionCreator(googleData.profileObj.name));
...
}
}
const TopBar = () => {
...
return(
...
<h5>store.getState().userNameRecuder.name</h5>
...
)
}
Before login
After login
The first question
Short answer:
you must call the selector function with useSelector hook. you are calling it directly in your <h5> element. so it's not working.
const TopBar = () => {
const username = useSelector(() => store.getState().userNameRecuder.name)
return(
<h5>{username}</h5>
)
}
Long answer with best practices:
You need to create a selector and use it in your component to re-render your component after the state changes.
In the selector.js
const selectUsername = (state) => state.userNameRecuder.name
export selectUsername;
Now, in your Topbar component, you need to implement the useSelector hook:
import {useSelector} from 'react-redux';
import {selectUsername} from 'store/selectors';
function TopBar () {
const username = useSelector(selectUsername)
return (
<p>username</p>
)
}
Updating the state with proper action (to update username) will cause a re-render in the Topbar component to get the new username from the store.
The second question
There are many great ways to persist data for example storing them on cookies, localStorage or using third-party libraries like redux-persist, and so on...
take a look at this post.
It works based on #novonimo's answer. Even without apart selector.js. Just connect the redux state with the component's state by using useSelector hooks, so the component will automatically re-rendered when the redux state changes.
const TopBar = () => {
const userName = useSelector(() => store.getState().userNameRecuder.name);
return(
...
<h5>userName</h5>
...
)
}
I'm learning react and redux. I'm creating a todo app but I'm facing a issue.
Let's say we have a list like
List.js
import React from 'react'
function List() {
return (
<div>
<ListItem>Text</ListItem>
<ListItem>Text</ListItem>
<ListItem>Text</ListItem>
<ListItem>Text</ListItem>
</div>
)
}
export default List
Then we have
ListItem.js
import React, { useState, useEffect } from "react";
import { useSelector } from "react-redux";
function ListItem() {
//This is some local state
const [isLoading, setLoading] = useState(false);
const loading = useSelector(state => state.loading);
// This is the loading flag which is coming from the reducer.. Which will be either true or false
useEffect(() => {
setLoading(true);
}, [loading]);
//Whenever the loading flag from the reducer changes, it changes the local state
return <div>{isLoading ? "loading" : "Hey, Im a list item"}</div>;
}
export default ListItem;
And let's say all listItem component have a click handler which dispatch an action to the change the loading flag in the reducer.
Now the issue I'm facing is as all the listItem are listening/subscribed to the same reducer, whenever the isLoading flag in the reducer changes, all the listItem start showing loading which is conditionally rendered from the return statement as shown above.
Expected Behaviour (or intended behaviour I must say)
I want to show loading on only that element that was clicked and whose local state was changed. But unfortunately my logic has a flaw that it all elements local states changes when the isLoading flag in the reducer changes
What you can do is provide key to each list item and assign loading to it in your reducer e.g
loading:{
List1: isLoading,
List2: notLoading
}
I'm trying to understand React Hooks. What I want is to fetch data inside functional component by call redux action with useEffect hooks.
I know that I can pass props to state like
const [todoList] = useState(props.todoList)
But what is the best practice to fetch data by existing redux actions?
In React class component i call this method to fetch data in componentDidMount() and everythink works.
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { ITodo } from './types'
import { getTodos } from '../actions/todoActions'
interface IProps {
todoList: Array<ITodo>
getTodos: typeof getTodos
}
const Todos = (props: IProps) => {
useEffect(() => {
props.getTodos()
}, [props.todoList])
return (
<div>
{props.todoList.map((_) => (<div key={_.Id}>{_.Name}</div>))}
</div>
)
}
const mapStateToProps = (state) => ({
todoList: state.todo.todoList
})
const mapDispatchToProps = {
getTodos
}
export default connect(mapStateToProps, mapDispatchToProps)(ProdRoute)
I expected to get list of todos with props and props.getTodos() should call once like in componentDidMount() method. But actualy I get data and getTodos() are called over and over again but should be called once on component mount
Take care that if you pass [props.todoList] to the useEffect you are erroneously forcing a constant refresh because:
useEffect does an instance comparison (===) to know if props.todoList is changed
after the the very first render the props.getTodos() dispatcher is called
when the props.todoList will be updated the component is re-rendered
the useEffect call will receive [props.todoList] as a value to check if it needs to re-run or not
props.todoList is changed (it was empty and now it's valorized) and props.getTodos() is so re-called
redux updates the todoList with the same values but mutating the array reference
the component is re-rendered and the useEffect will check if the [props.todoList] param is been updated... but IT IS BEEN UPDATED because the previous props.todoList is different from the actual props.todoList, even if the content is the same
So, if you need to call the props.getTodos() just once can
use [props.todoList.length] instead of [props.todoList] as the second parameter for the useEffect call
use an empty array [] as the second parameter for the useEffect call (see the docs)