Will the component re-render using useStore in react-redux? - redux

If I use the useStore hook in a component, will the component re-render every time the state tree changes in the store? I'd like to use this to access an updated version of the store inside of a Promise.

Yes, If you change the data using the set Method of created hook. It changes the state variables.
example-
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Explanation:
When you click the button it will call the setCount() method which will change the count (state variable) value and our component is using that value(subscribed to state change). It will trigger the render() method.
More details can be followed here - https://reactjs.org/docs/hooks-state.html

Related

How can I avoid prop drilling with a headless CMS? The context API I think hurts SEO

I want to use a headless CMO in my NextJs app (e.g. Sanity.io).
The content is especially important for SEO.
If I see it correctly, I can only receive the data on page-level via getStaticProps server-side to pre-render it that way (important for SEO).
If I now want to send the data from the page component to a deeply nested child, it's awkward via prop drilling.
My first thought was to use React's Context API (see code).
However, I suspect that during the build the state of the Context API does not take over the values (The SEO text for example).
So the pre-rendered page does not have the SEO content of the headless CMO.
Is there a way to send the values of the headless CMO to deeply nested children via getStaticProps without prop drilling? Or is the context API ok for this in terms of SEO / pre-render?
//pages/index.js
export default function Home({textFromGetStaticProps}) {
const value = useAppContext();
let {seotext, setSeotext} = value.content;
console.log("The State is currently: " + seotext);
console.log("The value of getStaticProps is currently: " + textFromGetStaticProps);
//Can not set this in useEffect since only runs on ClientSide!
setSeotext(() =>{
console.log("---> setCount läuft");
return textFromGetStaticProps;
})
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>
The SEO Text is {seotext}
</h1>
</main>
</div>
)
}
//Fetch headless CMO Date via getStaticProps
export async function getStaticProps(context) {
console.log("I am running Static Props");
//API Fetch of headless CMO
return {
props: {textFromGetStaticProps: "SEO Text aus StaticProps"}, // will be passed to the page component as props
}
}
//appContext.js
const AppContext = createContext();
export function AppWrapper({ children }) {
const [seotext, setSeotext] = useState("SEO Text");
const test = {seotext, setSeotext}
console.log("I am Running AppContext: " + test);
return (
<AppContext.Provider value={{
content: test,
}}>
{children}
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}
```
Future versions of Next.js will make this much easier but in the meantime you could try using SWR or React Query to fetch data for pre-rendering and then query inside nested components.
There are a few ways to achieve this. The first that comes to mind would be to use something like the React Context API. However, you could also use something like Redux.
Here is an example using the Context API:
import React, { createContext, useContext, useState } from 'react';
const AppContext = createContext();
export function AppWrapper({ children }) {
const [seotext, setSeotext] = useState("SEO Text");
const test = {seotext, setSeotext}
console.log("I am Running AppContext: " + test);
return (
<AppContext.Provider value={{
content: test,
}}>
{children}
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}

Next.js: Passing data to nested routes

Issue:
Right now, I have a dynamic route that fetches data using getServerSideProps(). Within this page, there are multiple tabs that renders different data depending on state (which tab is selected).
I wish to transition from using multiple tabs on this page, to instead using nested routes. However, I am having difficulty obtaining the data originally fetched in these nested routes. Is there an efficient way of doing so, without having to call getServerSideProps() again?
My intended setup looks like this, where [page] calls getServerSideProps():
[page].jsx
|_tab1.jsx
|_tab2.jsx
|_tab3.jsx
My current [page].jsx, where I would like to use separate, nested pages that have access to these props (instead of rendering each tab based on state):
export default function Page(props) {
const [currentTab, setCurrentTab] = useState("home");
return (
<div>
<div id="tab1" onClick={() => setCurrentTab("home")}>
home
</div>
<div id="tab2" onClick={() => setCurrentTab("posts")}>
posts
</div>
<div id="tab3" onClick={() => setCurrentTab("info")}>
info
</div>
{currentTab === "home" ? (
<HomeTab props={props}/>
) : currentTab === "posts" ? (
<PostsTab props={props}/>
) : (
<InfoTab props={props}/>
)}
</div>
);
}
Attempts
I've attempted using the context API to utilize data globally, which my other pages can use. However, this requires the user to visit the original dynamic route first.
Call getServerSideProps() on each nested route. Although this works, I wish to find a better solution, since I'm fetching data on each nested route while the route they're nested under has all of this data available already.
You can use shallow routing in next/route or next/link
Note that, in the below example, I'm using next/link for the demonstration. Without your tab data, I'd assume you have an array of tabs in data
import { useEffect } from 'react'
import Link from 'next/link'
//the path is `/tab/:tabId`
function Tab({ data }) {
const [tabData, setTabData] = useState(data[0]) //first tab data as default for example
useEffect(() => {
setTabData(data[tabId])
}, [router.query.tabId])
return <>
<Link href="/tab/0" shallow />
<Link href="/tab/1" shallow />
<div>
{tabData}
</div>
</>
}
export async function getServerSideProps(context) {
return {
props: {
data: [], //tabs' data you fetched from the API
},
}
}
export default Tab

How to implement a theme switcher on an existing React site

I currently have two theme files, theme.js and theme-dark.js. I also have a complex React-based site that has already been set up, and I cannot find a way to implement a way for a user to switch between the two theme files via some switcher on the site.
This is what my index.js render function looks like:
const rootElement = document.getElementById('root')
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
And this is what the relevant code in the App.tsx file looks like:
<ThemeProvider theme={theme}>
<CssBaseline />
<SnackbarProvider
.....
</SnackbarProvider>
</ThemeProvider>
The above code from the App.tsx file is nested in some custom Context provider components and contains some data for loading the initial components of the site.
I'm having some trouble implementing a theme switcher with this existing code to switch between theme.js and theme-dark.js. If anyone could give me a push in the right direction I'd greatly appreciate it. Unfortunately this is about all the actual code I can paste due to security reasons for my company, but I believe the main issue here is that the <Provider> element in index.js breaks when a custom theme provider is provided.
A simple state should suffice the job, but you might need to toggle the state deep down your app, where the switch/button is.
You can pass through with Context, but since you're using redux already, why reinvent the wheel.
Create a reducer for your theme type,
// isDarkModeReducer.js
export default function isDarkModeReducer(state = false, action) {
switch (action.type) {
case 'toggleDarkMode': {
return !state;
}
default:
return state;
}
}
Add it on your rootReducer
// rootReducer.js
...
import isDarkModeReducer from '<location of your isDarkModeReducer reducer>';
...
const rootReducer = combineReducers({
...
isDarkMode: isDarkModeReducer
})
...
On your App.tsx access the isDarkMode value from the created store, and use it to conditionally pass theme.js or theme-dark.js file.
// App.tsx
...
import theme from 'theme.js';
import theme-dark from 'theme-dark.js';
import { useSelector } from 'react-redux'
const isDarkMode = useSelector(state => state.isDarkMode);
...
return (
<ThemeProvider theme={isDarkMode ? theme-dark : theme}>
<CssBaseline />
<SnackbarProvider
.....
</SnackbarProvider>
</ThemeProvider>
);
...
For toggling, all you have to do is dispatch the action toggleDarkMode from wherever your switch button is.
// SwitchButton
import { useDispatch } from 'react-redux'
const dispatch = useDispatch();
const toggleTheme = () => {
dispatch({ type: 'toggleDarkMode' });
};
return (
<button onClick={toggleTheme}>Switch Theme</button>
);
You might also want to save the value to localStorage for persisting, which you can easily do as stated in the docs.

Can we use redux states in a react component without using {connect}?

I usually import {connect} from react-redux to connect my React component to a Redux store.
Is there any other way to access the redux store from react components without using {connect}?
There is useSelector from React Redux Hooks
The selector is approximately equivalent to the mapStateToProps argument to connect conceptually. The selector will be called with the entire Redux store state as its only argument. The selector will be run whenever the function component renders (unless its reference hasn't changed since a previous render of the component so that a cached result can be returned by the hook without re-running the selector). useSelector() will also subscribe to the Redux store, and run your selector whenever an action is dispatched.
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}

Redux Connect: mapDispatchToProps pass in the result of an action creator

I'm learning redux and and wanted to know how dispatch passes in result of an action creator as stated in the screenshot below taken from redux doc.
The learning app code is available here: https://github.com/ZhangMYihua/lesson-12/tree/master/src
As per the course instructor the below mapDispatchToProps code updates the header component state using user.action.js. I'm not able to understand how all this works even after reading the redux documentation.https://react-redux.js.org/using-react-redux/connect-mapdispatch
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
The initial state of the currentUser in the header component is set using the user.reducer.js file in the redux folder. This part is clear.
When we sign in using Google SignIn, the createUserProfileDocument(userAuth) function in App.js will check if the google login is available in the firestore users collections. If not it will create a copy of the account with the required fields. The user login details are fetched from the firestore users collections which are passed in the userRef in App.js.
What i do not understand is how data from userRef in componentDidMount() gets passed to header component using dispatch when we login using google signin?
componentDidMount() {
const {setCurrentUser} = this.props;
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
});
}
Below is the example from the react-redux documentation which is similar to the above.
This part in your componentDidMount is passing userRef data to the redux store:
const {setCurrentUser} = this.props;
// ...
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
mapDispatchToProps
The code calls this.props.setCurrentUser and that is function which you define in your mapDispatchToProps.
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
So this.props.setCurrentUser function definition is simple like this (where dispatch is defined in upper scope):
this.props.setCurrentUser = user => dispatch(setCurrentUser(user))
The function takes a single parameter user and calls dispatch(setCurrentUser(user)). Here the setCurrentUser(user) is your action creator that produce some action (something like this {action: 'SET_CURRENT_USER', payload: {user}}). This action is dispatched to redux store with dispatch function (the react-redux passes correct dispatch function to your mapDispatchToProps through react-redux connect, see bellow).
connect
The mapDispatchToProps is useless if you are not use it as an argument of the react-redux connect function. You usually wrap your component with connect and export the wrapped component not just the component itself. So whenever you import your App.js module and use the default export from it as component in some JSX tree, it is not just your component, but it is your component wrapped with react-redux magic. This magic ensures, that the mapDispatchToProps is called with correct dispatch argument (store.dispatch from redux library) and enhances your component props object with another properties, the ones form mapStateToProps and the ones from mapDispatchToProps.
// here you are wrapping your App component with `connect` from `react-redux`
export default connect(
null,
mapDispatchToProps
)(App);
In next file you will import and use the wrapped component like this:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import App from './App'; // here you are importing the wrapped component
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
It is necessary to have your components which uses react-redux have wrapped with Provider in the app component tree. Otherwise connect will not have access to redux store so connecting component to redux store won't work.
EDIT
mapStateToProps
this.props.setCurrentUser is used to dispatching action to your redux store which just MODIFIES it (how the store is modified is up to your implementation of reducer - see user.reducer.js in your example).
If you want to access some part of your redux store, you need to pass the mapStateToProps by the first argument (see above that in example of your App component the first argument of connect was null).
You can imagine the redux store as a single javascript object. The first definition of this object you can see in your root-reducer.js:
export default combineReducers({
user: userReducer
});
This defines that your redux store (object) has the property user on the top level. The value of property user handles user.reducer.js. That reducer defines initial value of the user part of the store and also defines how it can be modified (by redux actions). So according to your example your initial store is this:
{
user: {
currentUser: null
}
}
If you want access it in your component (props) you need to use mapStateToProps as I stated above, like this (see the file header.component.jsx in your example):
// header.component.jsx
const mapStateToProps = state => ({ // react-redux passes here the whole redux store to the argument state
/*
you want access currentUser of user reducer,
so you just access it the same way as you would do it
in javascript object, with use of dots.
*/
currentUser: state.user.currentUser //
});
export default connect(mapStateToProps)(Header);
And then you access the connected store value in your component by props:
// whenever in your component code
console.log(this.props.currentUser);

Resources