How should I test if className was added to child component in Jest with react-testing-library when props comes from context? I'm using CSS modules
Here's an example i wrote for this issue:
const Context = createContext()
const ContextProvider = ({ children }) => {
const [state, setState] = useState(false);
return (
<ContextProvider value={state}>
{children}
</ContextProvider>
);
};
const Display = () => {
const state = useContext(Context)
return <div className={`${state && styles[my-class]}`}></div>
}
const App = () => {
return (
<ContextProvider>
<Display />
</ContextProvider>
)
}
I tried something like this
it('should add classname', () => {
const { baseElement } = render(
<Context.Provider value={value}>
<Display />
</Context.Provider>
);
expect(baseElement).toHaveAttribute(
'class',
'my-class'
);
});
Related
I try to pass State and SetState by using useContext.
But there someting strange: only one can pass to children compoenents.
I want to pass {isLogin, setIsLogin, name}, only name can show correctly like this:
also can run in codesandbox
codesandbox
import React, { useState, createContext, useContext } from "react";
import { Text, View, Button } from "react-native";
const AppContext = createContext();
const AppProvider = (props) => {
const [isLogin, setIsLogin] = useState(false);
const [name, setName] = useState("molly");
return (
<AppContext.Provider value={(isLogin, setIsLogin, name)}>
{props.children}
</AppContext.Provider>
);
};
const App = () => {
return (
<AppProvider>
<Home />
</AppProvider>
);
};
const Home = () => {
const storedEvents = useContext(AppContext);
console.log("storedEvents", storedEvents);
const login = () => {
storedEvents.setIsLogin(true);
};
if (!storedEvents.isLogin) {
return (
<View>
<View>
<Text>{storedEvents}</Text>
</View>
<Button title="login" onPress={() => login()} />
</View>
);
}
return (
<View>
<Text>I am Login.</Text>
</View>
);
};
export default App;
You can pass multiple values as the JSON object, so your AppProvider should look something like this.
const AppProvider = (props) => {
const [isLogin, setIsLogin] = useState(false);
const [name, setName] = useState("molly");
return (
<AppContext.Provider value={{isLogin, name, setIsLogin}}>
{props.children}
</AppContext.Provider>
);
};
Notice the change value={{isLogin, name, setIsLogin}} instead of value={(isLogin, name, setIsLogin)}
There's another issue in the Home component which will give render error. You are trying to display storedEvents object which is not allowed. So change it to text string like Not logged in or something.
I want to keep the same state of the sidebar when i navigate between pages , how do i achive that ?
code in _app.js
function MyApp({ Component, pageProps }) {
const open = useState(false);
const [jsonData, setJsonData] = useState({});
const headerVisibility = useState("");
return (
<>
<HeaderContext.Provider value={headerVisibility}>
<NavigationBar open={open} />
<Content open={open}>
<Component {...pageProps} />
</Content>
</HeaderContext.Provider>
</>
);
}
// console.clear();
export default MyApp;
this is how i am fetching data in navigation/sidebar
useEffect(async () => {
const rawData = await fetch("http://localhost:8000/data");
const jsonData = await abc.json();
setData((prevState) => jsonData);
setnavData((prevState) => jsonData);
}, []);
I want to create a custom hook useComponent which returns a JSX.Element that will be rendered elsewhere.
I have tried this:
import { useState} from 'react';
const useComponent = () => {
const [value, setValue] = useState('');
const c = () => {
return <>
<p>Component</p>
<input value={value} onChane={(e) => setValue(e.target.value)} />
</>
}
return {
c,
value,
}
}
export default function App() {
const {c: C} = useComponent();
return (
<div className="App">
<C />
</div>
);
}
but it does not work. Once I try typing on input, nothing happens.
How can I achieve this ?
I know it might be a bad practice to do such a thing, but the reason I want this is to be able to open a global dialog and pass the c component as children to the <Dialog /> component so I can both render c inside the dialog's body and also have access to the [value, setValue] state. So my use case would be something like:
[EDIT]
I also add the whole logic with dialog:
import { createContext, useContext, useState } from "react";
const Test = ({ value, setValue }) => {
return (
<>
<p>Component</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
};
const useComponent = () => {
const [value, setValue] = useState("");
return {
element: <Test value={value} setValue={setValue} />,
value
};
};
const DialogCTX = createContext({});
export function DialogProvider(props) {
const [component, setComponent] = useState(null);
const ctx = {
component,
setComponent
};
return (
<DialogCTX.Provider value={ ctx }>
{props.children}
</DialogCTX.Provider>
);
}
export const useDialog = () => {
const {
component,
setComponent,
} = useContext(DialogCTX);
return {
component,
setComponent,
}
};
const Dialog = () => {
const { component } = useDialog();
return <div>
<p>Dialog</p>
{component}
</div>
}
const Setter = () => {
const {element, value} = useComponent();
const {setComponent} = useDialog();
return <div>
<p>Setter component</p>
<p>{value}</p>
<button onClick={() => setComponent(element)}>Set</button>
</div>
}
export default function App() {
return <div className="App">
<DialogProvider>
<Setter />
<Dialog />
</DialogProvider>
</div>;
}
As you said you want to return a JSX.Element but you actually returning a new component (a new function) every time your hook runs. So you could achieve your goal if you actually declare your component outside your hook and return the rendered one. Here is a working example:
import { useState } from "react";
const Test = ({ value, setValue }) => {
return (
<>
<p>Component</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
};
const useComponent = () => {
const [value, setValue] = useState("");
return {
element: <Test value={value} setValue={setValue} />,
value
};
};
export default function App() {
const { element } = useComponent();
return <div className="App">{element}</div>;
}
I followed this tutorial for creating themes for night/day modes with styled-components.
I created a hook useDarkMode and for some reason, while it's detecting changes locally to the theme state within the hook, it's not sending these updates to my component (_app.tsx) where it needs to be read.
Am I missing something obvious here, why isn't theme changing on _app.tsx?
useDarkMode hook
import { useEffect, useState } from 'react';
export const useDarkMode = () => {
const [theme, setTheme] = useState('light');
const setMode = (mode) => {
window.localStorage.setItem('theme', mode);
setTheme(mode);
};
const themeToggler = () => {
theme === 'light' ? setMode('dark') : setMode('light');
};
useEffect(() => {
console.log('theme:', theme); <=== triggers and shows theme has been updated
}, [theme]);
useEffect(() => {
const localTheme = window.localStorage.getItem('theme');
console.log('localTheme', window.localStorage.getItem('theme'));
localTheme && setTheme(localTheme);
}, []);
return [theme, themeToggler];
};
_app.tsx
function App({ Component, pageProps }: AppProps) {
const store = useStore(pageProps.initialReduxState);
const [theme] = useDarkMode();
useEffect(() => {
console.log('t', theme); <=== only triggers on component mount, theme is not updating
}, [theme]);
const themeMode = theme === 'light' ? LIGHT_THEME : DARK_THEME;
return (
<Provider store={store}>
<ThemeProvider theme={themeMode}>
<RootPage Component={Component} pageProps={pageProps} />
</ThemeProvider>
</Provider>
);
}
where it's being invoked
const TopNav = () => {
const [theme, themeToggler] = useDarkMode();
return (
<Content className="full-bleed">
<NavInner>
<AuthLinks>
<>
<button onClick={themeToggler}>Switch Theme</button>
<Link href="/user/login" passHref>
<div>
<Typography format="body1">Login</Typography>
</div>
</Link>
<Link href="/user/register" passHref>
<div>
<Typography format="body1">Register</Typography>
</div>
</Link>
</>
...
</AuthLinks>
</NavInner>
</Content>
);
};
Issue
Each react hook is its own instance, they don't share state.
Suggested Solution
Use a single dark mode theme state in the provider and expose the themeToggler in a context so all components can update the same context value.
Theme toggle context
const ThemeToggleContext = React.createContext({
themeToggler: () => {},
});
App
import { ThemeToggleContext } from 'themeToggleContext';
function App({ Component, pageProps }: AppProps) {
const store = useStore(pageProps.initialReduxState);
const [theme, themeToggler] = useDarkMode();
useEffect(() => {
console.log('t', theme); <=== only triggers on component mount, theme is not updating
}, [theme]);
const themeMode = theme === 'light' ? LIGHT_THEME : DARK_THEME;
return (
<Provider store={store}>
<ThemeProvider theme={themeMode}>
<ThemeToggleContext.Provider value={themeToggler} > // <-- pass themeToggler to context provider
<RootPage Component={Component} pageProps={pageProps} />
</ThemeToggleContext>
</ThemeProvider>
</Provider>
);
}
Component
import { ThemeToggleContext } from 'themeToggleContext';
const TopNav = () => {
const themeToggler = useContext(ThemeToggleContext); // <-- get the context value
return (
<Content className="full-bleed">
<NavInner>
<AuthLinks>
<>
<button onClick={themeToggler}>Switch Theme</button>
<Link href="/user/login" passHref>
<div>
<Typography format="body1">Login</Typography>
</div>
</Link>
<Link href="/user/register" passHref>
<div>
<Typography format="body1">Register</Typography>
</div>
</Link>
</>
...
</AuthLinks>
</NavInner>
</Content>
);
};
I'm developing an app with Next.js.
I want to get query information via useRouter in Child component.
Which way is faster and more proper to get query information?
Pass props to each ChildComponent from ParentComponent
const ParentComponent = () => {
const router = useRouter()
const { query } = router
return (
<>
<ChildComponent query={query} />
<ChildComponent query={query} />
</>
)
}
const ChildComponent = ({ query }) => {
console.log(query)
return (
<></>
)
}
Get query in each ChildComponent
const ParentComponent = () => {
return (
<>
<ChildComponent />
<ChildComponent />
</>
)
}
const ChildComponent = () => {
const router = useRouter()
const { query } = router
console.log(query)
return (
<></>
)
}