I'm kind of new w/ react and nextjs. How can insert a script in a component to add class in when the page reload? It seems like the code below don't work because the page is not yet rendered when I add the class for the body tag.
const ModeToggler = (props: Props) => {
// ** Props
const { settings, saveSettings } = props
const handleModeChange = (mode: PaletteMode) => {
saveSettings({ ...settings, mode })
}
const handleModeToggle = () => {
if (settings.mode === 'light') {
handleModeChange('dark');
document.body.classList.add('mode-dark');
} else {
handleModeChange('light');
document.body.classList.remove('mode-dark');
}
}
// This will not work because the page is not rendered yet right?
if (settings.mode === 'light') {
document.body.classList.add('mode-dark');
} else {
document.body.classList.remove('mode-dark');
}
return (
<IconButton color='inherit' aria-haspopup='true' onClick={handleModeToggle}>
{settings.mode === 'dark' ? <WeatherSunny /> : <WeatherNight />}
</IconButton>
)
}
export default ModeToggler
In your case, since the class is dependent on the prop settings
I would suggest you use a useEffect with a dependency of that prop. So not only will it retrieve the value and apply the style on render, but also re-render/apply style each time the prop changes.
const ModeToggler = (props: Props) => {
// ** Props
const { settings, saveSettings } = props
const handleModeChange = (mode: PaletteMode) => {
saveSettings({ ...settings, mode })
}
const handleModeToggle = () => {
if (settings.mode === 'light') {
handleModeChange('dark');
document.body.classList.add('mode-dark');
} else {
handleModeChange('light');
document.body.classList.remove('mode-dark');
}
}
useEffect(() => {
if (settings.mode === 'light') {
document.body.classList.add('mode-dark');
} else {
document.body.classList.remove('mode-dark');
}
}, [settings.mode])
return (
<IconButton color='inherit' aria-haspopup='true' onClick={handleModeToggle}>
{settings.mode === 'dark' ? <WeatherSunny /> : <WeatherNight />}
</IconButton>
)
}
export default ModeToggler
you can detect if your page is reload or not using window.performance
for more info https://developer.mozilla.org/en-US/docs/Web/API/Performance/getEntriesByType
useEffect(()=>{
let entries = window.performance.getEntriesByType("navigation");
if(entries[0]=="reload"){
// add class to an element;
}
},[])
Related
I just integrated a light/dark mode toggle into my Gatsby site here. I based it off of Josh Comeau's article, and it works just fine in Chrome. However on the homepage when using Safari, when I click the toggle button the background color doesn't change unless I resize the window. Here is my gatsby-ssr.js:
import React from 'react';
import { THEME_COLORS } from 'utils/theme-colors';
import { LOCAL_STORAGE_THEME_KEY } from './src/contexts/ThemeContext';
const SetTheme = () => {
let SetThemeScript = `
(function() {
function getInitialTheme() {
const persistedColorPreference = window.localStorage.getItem('${LOCAL_STORAGE_THEME_KEY}');
const hasPersistedPreference = typeof persistedColorPreference === 'string';
if (hasPersistedPreference) {
return persistedColorPreference;
}
const mql = window.matchMedia('(prefers-color-scheme: dark)');
const hasMediaQueryPreference = typeof mql.matches === 'boolean';
if (hasMediaQueryPreference) {
return mql.matches ? 'dark' : 'light';
}
return 'light';
}
const colorMode = getInitialTheme();
const root = document.documentElement;
root.style.setProperty(
'--color-primary',
colorMode === 'dark'
? '${THEME_COLORS.dark}'
: '${THEME_COLORS.light}'
);
root.style.setProperty(
'--color-secondary',
colorMode === 'dark'
? '${THEME_COLORS.light}'
: '${THEME_COLORS.dark}'
);
root.style.setProperty(
'--color-accent',
colorMode === 'dark'
? '${THEME_COLORS.accentLight}'
: '${THEME_COLORS.accentDark}'
);
root.style.setProperty('--initial-color-mode', colorMode);
})()`;
return <script id="theme-hydration" dangerouslySetInnerHTML={{ __html: SetThemeScript }} />;
};
export const onRenderBody = ({ setPreBodyComponents }) => {
setPreBodyComponents(<SetTheme />);
};
and my ThemeToggle component:
import React, { useContext } from 'react';
import { ThemeContext } from 'contexts/ThemeContext';
import { DarkModeSwitch } from 'react-toggle-dark-mode';
import { THEME_COLORS } from 'utils/theme-colors';
import s from './ThemeToggle.scss';
export const ThemeToggle = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
const toggleDarkMode = (checked: boolean) => {
toggleTheme(checked ? 'dark' : 'light');
};
return (
<div className={s.toggler}>
<DarkModeSwitch
checked={theme === 'dark'}
onChange={toggleDarkMode}
size={20}
sunColor={THEME_COLORS.dark}
moonColor={THEME_COLORS.light}
/>
</div>
);
};
Any ideas on how to fix this?
It doesn't appear that the toggleTheme property being destructured from the ThemeContext value triggers a re-render, but resizing your browser window does. Josh handles this by providing a setter function with a side effect (it manipulates the root styles directly):
const contextValue = React.useMemo(() => {
function setColorMode(newValue) {
const root = window.document.documentElement;
localStorage.setItem(COLOR_MODE_KEY, newValue);
Object.entries(COLORS).forEach(([name, colorByTheme]) => {
const cssVarName = `--color-${name}`;
root.style.setProperty(cssVarName, colorByTheme[newValue]);
});
rawSetColorMode(newValue);
}
return {
colorMode,
setColorMode,
};
}, [colorMode, rawSetColorMode]);
I'm using react 16.13.1 and react-dom 16.13.1. I create a ref using React.createRef() and attach to a component I defined by myself.And then I want to use a method that I defined in that component, but it does not work because .current is null.Here's my code.
class SomeComponent {
//ref
picturesRef = React.createRef();
richTextRef = React.createRef();
componentDidMount() {
console.log("this.picturesRef", this.picturesRef);
this.setState({ operation: "update" });
const product = this.props.products.find(
(item) => item._id === this.props.match.params.id,
);
const {
name,
price,
categoryId,
imgs,
desc,
detail,
} = product;
this.setState({
name,
price,
categoryId,
imgs,
desc,
detail,
});
this.picturesRef.current.setFileList(imgs);
}
render() {
const {
categories,
isLoading,
name,
price,
categoryId,
desc,
detail,
} = this.state;
return (
<Card title={<div>Add Product</div>} loading={isLoading}>
<Form
{...layout}
onFinish={this.onFinish}
onFinishFailed={this.onFinishFailed}
initialValues={{
name,
price,
categoryId,
desc,
detail,
}}
>
<Item label="Product Pictures" name="imgs">
{/**Here I attach picturesRef to this component */}
<PicturesWall ref={this.picturesRef} />
</Item>
<Item {...tailLayout}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Item>
</Form>
</Card>
);
}
}
(P.S. When I use this.picturesRef.current in onFinish(), it works fine.)
Below is the code in PicturesWall
import React, { Component } from "react";
import { Upload, Modal, message } from "antd";
import { PlusOutlined } from "#ant-design/icons";
import { BASE_URL } from "../../config";
import { reqPictureDelete } from "../../api";
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
}
class PicturesWall extends Component {
state = {
previewVisible: false,
previewImage: "",
previewTitle: "",
fileList: [],
};
handleCancel = () => this.setState({ previewVisible: false });
handlePreview = async (file) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
this.setState({
previewImage: file.url || file.preview,
previewVisible: true,
previewTitle:
file.name ||
file.url.substring(file.url.lastIndexOf("/") + 1),
});
};
handleChange = ({ file, fileList }) => {
console.log("file=", file);
const { response, status } = file;
if (status === "done") {
if (response.status === 0) {
fileList[fileList.length - 1].url = response.data.url;
fileList[fileList.length - 1].name = response.data.name;
} else {
message.error(response.msg, 1);
}
}
if (status === "removed") {
this.deleteImg(file.name);
}
this.setState({ fileList });
};
deleteImg = async (name) => {
const response = await reqPictureDelete(name);
if (response.status === 0) {
message.success("Successfully Delete", 1);
} else {
message.error("Failed", 1);
}
};
getImgNames() {
let imgs = [];
this.state.fileList.forEach((item) => {
imgs.push(item.name);
});
return imgs;
}
setFileList = (imgNames) => {
let fileList = [];
imgNames.forEach((item, index) => {
fileList.push({
uid: index,
name: item,
url: `${BASE_URL}/upload/${item}`,
});
});
this.setState(fileList);
};
render() {
const {
previewVisible,
previewImage,
fileList,
previewTitle,
} = this.state;
const uploadButton = (
<div>
<PlusOutlined />
<div className="ant-upload-text">Upload</div>
</div>
);
return (
<div className="clearfix">
<Upload
action={`${BASE_URL}/manage/img/upload`}
method="post"
listType="picture-card"
fileList={fileList}
onPreview={this.handlePreview}
onChange={this.handleChange}
name="image"
>
{fileList.length >= 4 ? null : uploadButton}
</Upload>
<Modal
visible={previewVisible}
title={previewTitle}
footer={null}
onCancel={this.handleCancel}
>
<img
alt="example"
style={{ width: "100%" }}
src={previewImage}
/>
</Modal>
</div>
);
}
}
export default PicturesWall;
In the first line of componentDidMount, I print out this.picturesRef, and something weird happens:
in the first line, it shows that current is null, but when I open it, it seems that it has content. However, when I print .current, it is still null.
As I indicated in the comments section of the OP's question, I noticed that the Card component has a prop loading
<Card title={<div>Add Product</div>} loading={isLoading}>
<Form>
<Item>
<PicturesWall ref={this.picturesRef} />
...
This led me to believe that the Card component has conditions which prevented its children from rendering until it is finished loading, an example of this is instead of rendering its children while it's loading - it renders a "is-loading" type of component.
In this scenario, this.picturesRef.current will will return null on the componentDidMount lifecycle because the ref will not be referring to anything because it is not yet in the DOM by that time.
My original comment:
This post might shed some light. You have props such as loading on
Card which makes me think that perhaps you are initially rendering
some "is-loading" type of component on the DOM rather than the
children of Card such as the PicturesWall component. This could be why
PicturesWall ref is not accessible on the componentDidMount lifecycle
This doesn't directly answer your question, but I think you may be Reacting it wrong.
Your componentDidMount function seems to be basically only deriving state from props (and then calling a function on the reffed component). You can derive the state in a class component's constructor, e.g. something like
constructor(props) {
const product = props.products.find((item)=>item._id === props.match.params.id);
const {name,price,categoryId,imgs,desc,detail} = product;
this.state = {name,price,categoryId,imgs,desc,detail};
}
Then, instead of having a setFileList function, you would similarly pass the fileList down to PictureWall as a prop, e.g.
<PicturesWall fileList={this.state.imgs} />
child component
export class child extends Component {
buttonclick() {
const { pin } = this.props
if (pin === null) {
add().then(result => {
updatePin(result.data)
})
} else {
remove(pin.id).then(result => {
updatePin(result)
})
}
}
render() {
const { pin } = this.props
const label =
pin === null
? 'yes'
: 'no'
const icon =pin === null ? 'yes' : 'no'
return (
<div>
<Button
icon={icon}
label={label}
onClick={() => this.buttonclick()}
/>
</div>
)
}
}
parent component(classless component)
const parent= props =>{
const { pins = []} = props
const { pin } = Data
}
const updatePin = result => {
// here iam updating the pin
}
const renderchildComponent=()=>{
return(
<div>
<ChildComponent
pin={pin}
updatePin={result => updatePin(result)}
/>
</div>
)
}
here in the above code the pin is updating in the parent component but how to pass it to child component every time when the button click happens without refreshing the page. please help me out with this
I want to change routes on button press in React Native. In my SplashContainer component this is the method I'm running:
handleToSignUp = () => {
console.log("Running handleToSignUp")
this.props.navigator.push({
signUpForm: true
});
}
My Navigator component looks like this
export default class NimbusNavigator extends Component {
static propTypes = {
isAuthed: PropTypes.bool.isRequired
}
renderScene = (route, navigator) => {
console.log(route);
console.log(navigator);
// Keeps track of whether user is Authed or not.
if (this.props.isAuthed === false && route !== 'signUpForm') {
return <SplashContainer navigator={navigator}/>
} else if (route === 'signUpForm') {
return <SignUpForm navigator={navigator} />
}
return <FooterTabsContainer navigator={navigator} />
}
configureScene = (route) => {
return Navigator.SceneConfigs.FloatFromRight
}
render () {
return (
<Navigator
configureScene={this.configureScene}
renderScene={this.renderScene}
/>
)
}
}
If route is not equal to 'signUpForm' shouldn't code skip to the else if statement and render <SignUpForm/> component?
Thanks!
There are a few ways to get this working, but the main thing to remember is that everything passed to navigator.push({ // properties of the route object }) become properties of the route object.
For instance, if you keep your handleSignUp method the way it is, you would need to rewrite your renderScene method like so:
renderScene = (route, navigator) => {
if (this.props.isAuthed === false && !route.signUpForm) {
return <SplashContainer navigator={navigator}/>
} else if (route.signUpForm) {
return <SignUpForm navigator={navigator} />
}
return <FooterTabsContainer navigator={navigator} />
}
You could also rewrite your handleSignUp method like this:
handleToSignUp = () => {
this.props.navigator.push({
title: 'handleSignUpForm'
});
}
And renderScene like this:
renderScene = (route, navigator) => {
if (this.props.isAuthed === false && route.title !== 'signUpForm') {
return <SplashContainer navigator={navigator}/>
} else if (route.title === 'signUpForm') {
return <SignUpForm navigator={navigator} />
}
return <FooterTabsContainer navigator={navigator} />
}
I'm working on a HTMl5 video player for a French company. We use React and Redux to build the UI, and it works very well, it's very pleasant to code ! We currently use eslint-plugin-react to check React code style. Since last versions, the linter recommands to use pure functions instead of React Components (view the rule) but it raises some debates in my team.
We are already using pure function for very small components that render always the same things (for given props). No problems with that. But for bigger components, in my opinion, pure functions seem to make the code less elegant (and less uniform compared to other components).
This is an example of one of our components that should be changed :
const ControlBar = ({ actions, core, root }) => {
const onFullscreenScreen = (isFullscreen) => {
const playerRoot = root;
if (isFullscreen && !screenfull.isFullscreen) {
screenfull.request(playerRoot);
} else {
screenfull.exit();
}
};
const renderIconButton = (glyph, action, label = false) => {
let content = (
<Button modifier="icon" clickCallback={ action }>
<Icon glyph={ glyph } />
</Button>
);
if (label) {
content = <TooltipOrigin content={label}>{content}</TooltipOrigin>;
}
return content;
};
const renderPlayButton = () => {
const { play, pause } = actions;
const { playerState } = core;
if (playerState === CoreStates.PAUSED) {
return renderIconButton(playGlyph, play, 'lecture');
}
return renderIconButton(pauseGlyph, pause, 'pause');
};
const renderMuteButton = () => {
const { mute, unmute } = actions;
const { currentVolume } = core;
if (currentVolume === 0) {
return renderIconButton(muteGlyph, unmute);
}
return renderIconButton(volumeGlyph, mute);
};
const renderFullscreenButton = () => {
const { isFullscreen } = core;
if (!isFullscreen) {
return renderIconButton(fullscreenGlyph, () => { onFullscreenScreen(true); });
}
return renderIconButton(fullscreenExitGlyph, () => { onFullscreenScreen(false); });
};
const { setCurrentVolume } = actions;
const { currentVolume } = core;
return (
<div className={ style.ControlBar }>
<div className={ style.audio }>
{ renderMuteButton() }
<SoundBar setCurrentVolume={ setCurrentVolume } volume={ currentVolume } />
</div>
<div className={ style.controls }>
{ renderPlayButton() }
</div>
<div className={ style.settings }>
{ renderFullscreenButton() }
</div>
</div>
);
};
ControlBar.propTypes = {
actions: PropTypes.object.isRequired,
core: PropTypes.object.isRequired,
root: PropTypes.object.isRequired,
};
export default ControlBar;
versus :
export default class ControlBar extends Component {
static propTypes = {
actions: PropTypes.object.isRequired,
core: PropTypes.object.isRequired,
root: PropTypes.object.isRequired,
};
onFullscreenScreen(isFullscreen) {
const playerRoot = this.props.root;
if (isFullscreen && !screenfull.isFullscreen) {
screenfull.request(playerRoot);
} else {
screenfull.exit();
}
}
renderIconButton(glyph, action, label = false) {
let content = (
<Button modifier="icon" clickCallback={ action }>
<Icon glyph={ glyph } />
</Button>
);
if (label) {
content = <TooltipOrigin content={label}>{content}</TooltipOrigin>;
}
return content;
}
renderPlayButton() {
const { play, pause } = this.props.actions;
const { playerState } = this.props.core;
if (playerState === CoreStates.PAUSED) {
return this.renderIconButton(playGlyph, play, 'lecture');
}
return this.renderIconButton(pauseGlyph, pause, 'pause');
}
renderMuteButton() {
const { mute, unmute } = this.props.actions;
const { currentVolume } = this.props.core;
if (currentVolume === 0) {
return this.renderIconButton(muteGlyph, unmute);
}
return this.renderIconButton(volumeGlyph, mute);
}
renderFullscreenButton() {
const { isFullscreen } = this.props.core;
if (!isFullscreen) {
return this.renderIconButton(fullscreenGlyph, () => { this.onFullscreenScreen(true); });
}
return this.renderIconButton(fullscreenExitGlyph, () => { this.onFullscreenScreen(false); });
}
render() {
const { setCurrentVolume } = this.props.actions;
const { currentVolume } = this.props.core;
return (
<div className={ style.ControlBar }>
<div className={ style.audio }>
{ this.renderMuteButton() }
<SoundBar setCurrentVolume={ setCurrentVolume } volume={ currentVolume } />
</div>
<div className={ style.controls }>
{ this.renderPlayButton() }
</div>
<div className={ style.settings }>
{ this.renderFullscreenButton() }
</div>
</div>
);
}
}
We like the structure of a React Component. The PropTypes and default props can be inside the class thanks to ES7, it's not possible with pure functions. And, in this example particularly, we have many function to render sub-components.
We could simply disable this rule if we don't like, but we really want to understand that and we care about performance and React good practices. So, maybe you can help us.
How can you help me ? I would get other opinions about this interesting problematic. What are the arguments in favor of pure functions ?
Maybe the solution is not to change ControlBar Component in pure function but just to improve it. In this case, what would be your advices to do that ?
Thanks a lot for your help !