How to implement a theme switcher on an existing React site - css

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.

Related

nextjs localStorage getItem

after searching half a day I still not able to getItem from local storage.
the idea is to save some data to local storage and based on that I want to route a user in the Layout component. I am able to save to local storage and delete but not able to get data from it. I get error 'local storage not defined' or 'destroy is not a function'
I have 3 components save, delete and get. save and delete I execute after a client side api call, the get function I need to be working in the Layout as it is the top level for all routes.
I Need a bit help to the right direction please.
---Upadte
I found something that works
export const IsAuth = ()=>{
const [auth, setAuth] = useState();
useEffect(()=>{
if(typeof windows === undefined) return;
const item = localStorage.getItem('ltu');
setAuth(!!item);
},[]);
return auth;
}
now my problem is I have not much understanding of nextjs. I used the Layout to create a theme template, I basically have only 3 pages that can be visited if not logged in and the rest one needs to be logged in. I get so many examples but it seems like I need to verify auth on every single page instead of being able to do this on root/layout level.
all examples I get are without the use of Layout and I am totally stuck.
I want a simple login system just with jwt and check if thats there to show pages.
I could not get the localStorage.getItem() to work in the layout template.
My solution while maybe not perfect is.
in the _app.js I create useState() and pass those along to the menu trough the Layout, in in the menu useEffect() with 'use client' in the useEffect I set the state I need global.
_app.js
export default function App({ Component, pageProps }){
const [isAuth, setAuth] = useState()
const [user, setUser] = useState()
return (
<Layout setAuth={setAuth} isAuth={isAuth} user={user} setUser={setUser}>
<Component user={user} setUser={setUser} isAuth={isAuth} {...pageProps} />
</Layout>
)
}
Layout.js
export default function Layout({ children, setAuth, isAuth, user, setUser }) {
return (
<>
<Headd />
<SideMenu setAuth={setAuth} isAuth={isAuth} user={user} setUser={setUser}/>
<main>
<div className="menu-spacer"></div>
<content>
{children}
</content>
</main>
</>
)
}
menu.js
'use client';
const SideMenu = ({setAuth, isAuth, user, setUser}) => {
useEffect(()=>{
if(typeof windows === undefined) return;
const item = localStorage.getItem('ltu');
setAuth(!!item);
if(item) setUser(JSON.parse(localStorage.getItem('Ud')))
}, [router, router.isReady])
}
Now I can use the {isAuth, user,} on any page and component.
I am pretty sure this is not the right solution, but I could not find any other working solution and no one here yet posted a answer.

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);
}

Error: Minified React error #321 on Wordpress custom block plugin

Whenever I write a useEffect() inside a component function of my block plugin, the edit page goes blank and the console logs the message:
react_devtools_backend.js:4026 Error: Minified React error #321; visit https://reactjs.org/docs/error-decoder.html?invariant=321 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at Object.it (react-dom.min.js?ver=17.0.1:9:43163)
at e.useState (react.min.js?ver=17.0.1:9:10899)
at Prompt (Prompt.js:5:35)
at N (element.min.js?ver=3dfdc75a0abf30f057df44e9a39abe5b:2:9552)
at U (element.min.js?ver=3dfdc75a0abf30f057df44e9a39abe5b:2:10502)
at N (element.min.js?ver=3dfdc75a0abf30f057df44e9a39abe5b:2:9284)
at lr (blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:111294)
at blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:137935
at xn (blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:138073)
at blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:139086
The component:
import React, { useState, useEffect } from "react";
import axios from "axios";
function Prompt(props) {
const [data, setData] = useState({ hits: [] });
useEffect(() => {
const fetchData = async () => {
const result = await axios(
"http://my-site-test.local/wp-json/wp/v2/posts?_fields[]=title"
);
setData(result.data);
};
fetchData();
}, []);
console.log(data);
return (
<>
JSX...
</>
);
}
export default Prompt;
I tried to delete node_modules and reinstall to no avail…
I believe the problem is in my-plugin/src/index.js — wp.blocks.registerBlockType's 'save' property only allows static HTML to be returned (so it can be stored in the database within the content) and I was trying to insert a React component into it.
Since I want a dynamic block on the front-end, I have to load a register_block_type in my-plugin/index.php to render my component.
EDIT You actually can add React directly in the save attribute if you have specified script when registering your block in the PHP main file (or in your block.json file.

useWrappedStore missing in next-redux-wrapper

I was trying to implement the next-redux-wrapper with redux-toolkit.
As the example suggests in https://github.com/kirill-konshin/next-redux-wrapper/blob/master/packages/demo-redux-toolkit/pages/_app.tsx.
As the example suggests, the store should be wrapper with the useWrappedStore method. Unfortunately, it's not found anywhere in the library.
const MyApp: FC<AppProps> = ({Component, ...rest}) => {
const {store, props} = wrapper.useWrappedStore(rest);
return (
<Provider store={store}>
<Component {...props.pageProps} />
</Provider>
);
};
export default MyApp;
Anyone have any idea how to solve this?

Why I don't see div in html (view page source in browser) when I use getStaticProps in next.js

I try to make test SSR app based on Next.js + React + Apollo.
But I cannot understand how SSR works.
I read the docs and I found that to get some important data while first SSR render we need use getStaticProps and the result of function call will be passed to the props of component which I try to render.
Depends on this props I can render whatever I want, for example I have next code:
import React from "react";
import {GetServerSideProps, GetStaticProps} from "next";
const Home = (props) => {
return (
<div>{props.ValFromGetStaticProps}</div>
)
};
export const getStaticProps: GetStaticProps = async (context) => {
return { props: {ValFromGetStaticProps: 'ValFromGetStaticProps!'} }
};
export default Home;
I expect to see rendered this code on the server side, and if I open sources of the HTML I should see this div. But instead I see just some props object... And there is no div ((
Remark: in the DOM this div present, but for SEO purpose this div should exists in the page source.
Ohh... I didn't see it because I blocked first render early in the parent component:
_app.tsx
export default function ({ Component, pageProps }) {
<BackendDataProvider>
<Component {...pageProps}>
</BackendDataProvider>
}
in the BackendDataProvider was condition
const {data, error, loading} = useQuery(BACKEND_QUERY)
if (error || loading) {
return null;
}
so that was a reason why first render was not rendered correctly

Resources