Passing ref into styled components in react - reactjs

I want to get height and width of styled component in react. I am using this https://opensourcelibs.com/lib/use-resize-observer and my code looks like:
const Test = ({className, ref}) => {
return (
<div className={className} ref={ref}/>
)
}
const StyledTest = styled(Test)`
height: 100px;
width: 100px;
background-color: greenyellow;
`
const TestClass = () => {
const testRef = useRef(null)
const testSize = useResizeObserver({ref: testRef});
return (
<React.Fragment>
<ThemeProvider theme={testTheme}>
<h1>Height test: {leftContainerSize.height}</h1>
<StyledTest ref={leftContainerRef}/>
</ThemeProvider>
</React.Fragment>
)
}
Unfortunately it doesn't work. If I try to do the same for example with image it works so I think there are problem with passing ref into styled components. I read this article Using 'ref' on React Styled Components is not working, but I don't know how to use innerRef in my case. I also tried to use forwardRef but I failed too. Does someone know to make it work?

Try this.
Use forwardRef in functional component to get ref. Don't try to get ref from props.
Your example is missing variables: leftContainerRef, leftContainerSize.
Although you are trying to use them.
const Test = forwardRef(({ className }, ref) => {
return (
<div className={className} ref={ref} />
)
})
const StyledTest = styled(Test)`
height: 100px;
width: 100px;
background-color: greenyellow;
`
const TestClass = () => {
const { ref, height } = useResizeObserver();
return (
<React.Fragment>
<ThemeProvider theme={testTheme}>
<h1>Height test: {height}</h1>
<StyledTest ref={ref} />
</ThemeProvider>
</React.Fragment>
)
}
If you want to work with the ref. You can create your ref and pass it to the hook.
const Test = forwardRef(({ className }, ref) => {
return (
<div className={className} ref={ref} />
)
})
const StyledTest = styled(Test)`
height: 100px;
width: 100px;
background-color: greenyellow;
`
const TestClass = () => {
const ownRef = useRef(null)
const { height } = useResizeObserver({ ref: ownRef });
return (
<React.Fragment>
<ThemeProvider theme={testTheme}>
<h1>Height test: {height}</h1>
<StyledTest ref={ownRef} />
</ThemeProvider>
</React.Fragment>
)
}

Related

How can I use useRef when using ScrollTigger in React?

I'm using Gsap's ScrollTigger to develop horizontal scrolling.
If a ref is passed when using Gsap's toArray, only the ref of the last element that uses the ref will be referenced. How can I pass all used refs to toArray?
Is only className used as an argument to toArray? Or is there another way to implement horizontal scrolling differently?
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { useLayoutEffect, useRef } from 'react';
import styled from 'styled-components';
gsap.registerPlugin(ScrollTrigger);
const Home = () => {
const panelRef = useRef(null);
const containerRef = useRef(null);
useLayoutEffect(() => {
const sections = gsap.utils.toArray(panelRef); // If you pass a ref, only the last ref will be referenced
gsap.to(sections, {
xPercent: -100 * (sections.length - 1),
scrollTrigger: {
trigger: containerRef.current,
pin: true,
scrub: 1,
end: '+=3500',
},
});
}, []);
return (
<Container ref={containerRef}>
<Panel className="panel" ref={panelRef}>
ONE
</Panel>
<Panel className="panel" ref={panelRef}>
TWO
</Panel>
<Panel className="panel" ref={panelRef}>
THREE
</Panel>
</Container>
);
};
const Container = styled.div`
position: relative;
overscroll-behavior: none;
height: 100%;
width: max-content;
display: flex;
flex-direction: row;
`;
const Panel = styled.div`
height: 100%;
width: 100vw;
background-color: #000;
`;
export default Home;
import { useRef, useEffect } from 'react';
import { ScrollTrigger } from 'react-scroll-trigger';
function MyComponent() {
const triggerRef = useRef(null);
useEffect(() => {
const current = triggerRef.current;
current.addEventListener("enter", () => {
// do something
});
current.addEventListener("leave", () => {
// do something
});
return () => {
current.removeEventListener("enter", () => {});
current.removeEventListener("leave", () => {});
};
}, []);
return (
<div>
<ScrollTrigger ref={triggerRef}>
<MyContent />
</ScrollTrigger>
</div>
);
}

How to change the color of thumb switch react js

I have a switch that I would like to change the color of the circle to a dark gray. I've looked on the internet but I can't quite understand how to use the css rules that exist in the component documentation. Can anybody help me!? Here is my code:
const ThemeToggle = () => {
const { theme, setTheme } = useContext(ThemeContext);
const handleThemeToggle = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
if (theme === 'light') {
document.body.classList.add('darkBackground');
} else {
document.body.classList.remove('darkBackground');
}
};
return <div>
<Switch
uncheckedIcon={false}
checkedIcon={false}
onColor={'#eee'}
onChange={handleThemeToggle}
checked={theme === 'light'}
/>
</div>
}
export default ThemeToggle;
Component Documentation: https://mui.com/material-ui/api/switch/
I use this switch to change the mode. So while in light mode the thumb would be grayed out. In dark mode, the thumbnail would be white
**ThemeContext:
export const ThemeContext = React.createContext({} as IThemeContext);
const App: React.FC = () => {
const storedTheme = localStorage.getItem("darkTheme");
const [theme, setTheme] = useState(storedTheme);
useEffect(() => {
localStorage.setItem("darkTheme", theme);
})
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<CssBaseline />
<BrowserRouter>
<GlobalStyle />
<AppRouter />
</BrowserRouter>
</ThemeContext.Provider>
);
};
Try to overwrite this class:
.MuiSwitch-colorSecondary.Mui-checked {
color: green; // any color you want
}
Example:
https://codesandbox.io/s/material-ui-switch-forked-2plbik?file=/src/components/SwitchContainer.jsx
I created the following codesandbox where you can see how I change the colour of the Switch component with CSS global class this website: https://mui.com/material-ui/api/switch/#css provides + your case that you just want it when its darkBackground:
.darkBackground {
background-color: #404042;
color: gray;
}
.light {
background-color: #fff;
color: #000;
}
.MuiSwitch-switchBase .MuiSwitch-thumb {
color: purple;
}
.MuiSwitch-switchBase + .MuiSwitch-track {
background-color: purple;
}
.darkBackground .MuiSwitch-switchBase.Mui-checked .MuiSwitch-thumb {
color: white;
}
.darkBackground .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track {
background-color: white;
}
I wasn't able to change the context that's why i asked you if you could share it but if you make the context work in this demo it should work as you expect:
DEMO:
https://codesandbox.io/s/charming-shannon-36bl1m?file=/src/styles.css

How to add custom CSS to existing styled component?

I have an issue related to styled component.
I have a prewritten Select component and I want to write my own select component by using that.
Here are some codes that I wrote.
in the Comopnent.tsx
...
import Select from './Select'
const StyledSelect = styled(Select)`
// CUSTOMIZED STYLES
height: 25px;
border-radius: 10px;
cursor: default;
`
...
const Component1:React.FC = () => {
...
return (
<StyledSelect
options={[
{
label: t('Hot'),
value: 'hot',
},
{
label: t('APR'),
value: 'apr',
},
{
label: t('Multiplier'),
value: 'multiplier',
},
{
label: t('Earned'),
value: 'earned',
},
{
label: t('Liquidity'),
value: 'liquidity',
},
]}
onChange={handleSortOptionChange}
/>
)
}
...
in the Select.tsx
...
const DropDownContainer = styled.div<{ isOpen: boolean; width: number; height: number }>`
cursor: pointer;
width: ${({ width }) => width}px;
position: relative;
...
`
const Select:React.FunctionComponent<SelectProps> = ({options, onChange}) => {
...
return (
<DropDownContainer isOpen={isOpen} ref={containerRef} {...containerSize}>
{containerSize.width !== 0 && (
<DropDownHeader onClick={toggling}>
<Text>{options[selectedOptionIndex].label}</Text>
</DropDownHeader>
)}
<ArrowDropDownIcon color="text" onClick={toggling} />
<DropDownListContainer>
<DropDownList ref={dropdownRef}>
{options.map((option, index) =>
index !== selectedOptionIndex ? (
<ListItem onClick={onOptionClicked(index)} key={option.label}>
<Text>{option.label}</Text>
</ListItem>
) : null,
)}
</DropDownList>
</DropDownListContainer>
</DropDownContainer>
)
}
...
But CUSTOMIZED STYLES don't work.
I have some similar experience with adding custom CSS to the existing styled component like the above but I don't know why that doesn't work.
What is my fault?
You forgot to accept a property className and set it on needed element.
Below is an example. Pay attention to the className in the props and set it to the container.
To style the component, you need to pass the className props. And in the component where you get that className prop, you need to assign it to the element that needs to be styled, like so:
Notice how Link takes a className and assigns it to Element "a".
const Link = ({ className, children }) => (
<a className={className}>
{children}
</a>
);
const StyledLink = styled(Link)`
color: palevioletred;
font-weight: bold;
`;
For more detail, check official docs.
https://styled-components.com/docs/basics#styling-any-component
const Select:React.FunctionComponent<SelectProps> = ({options, onChange, className}) => {
...
return (
<DropDownContainer className={className} isOpen={isOpen} ref={containerRef} {...containerSize}>
{containerSize.width !== 0 && (
<DropDownHeader onClick={toggling}>
<Text>{options[selectedOptionIndex].label}</Text>
</DropDownHeader>
)}
<ArrowDropDownIcon color="text" onClick={toggling} />
<DropDownListContainer>
<DropDownList ref={dropdownRef}>
{options.map((option, index) =>
index !== selectedOptionIndex ? (
<ListItem onClick={onOptionClicked(index)} key={option.label}>
<Text>{option.label}</Text>
</ListItem>
) : null,
)}
</DropDownList>
</DropDownListContainer>
</DropDownContainer>
)
}
...

How to prevent child re-render on every parent state update done though input field in React?

I am building a basic chat application. I have a parent component (Console.js) that contains three child components
QuestionCard.js
AnswerCard.js
InputCard.js
This is how the basic layout looks like
const Console = () => {
// initial state for input field
const [userInput, setUserInput] = useState("");
// fetch previous conversation history
const conversationHistory = useSelector(state=> state.conversationHistory.data)
conversationHistory.map((info, idx) => (
info.type == "statement" ?
<span key={idx}>
<QuestionCard
data={info}
/>
</span>
: info.type == "answer" ?
<span key={idx}>
<AnswerCard
userInput={userInput}
setUserInput={setUserInput}
data={info}
/>
</span>
:
<span></span>
))
<InputCard
userInput={userInput}
setUserInput={setUserInput}
/>
}
Specifically in the InputCard.js child component, there resides the input field where the user types
const InputCard = ({userInput, setUserInput}) => {
const handleTextBoxInput = e => {
setUserInput(e.target.value)
}
return (
<input
type="text"
value={userInput || ""}
onChange={handleInput}
id="userQuery"
/>
)
}
The problem here is that every time I press a key, all the child components (QuestionCard.js, AnswerCard.js, InputCard.js) re-renders.
I read about memo and it is one way to ensure components don't re-render but needs something to compare against. So I understand I need to compare the userInput state before and after and check if indeed something changed. But I just don't know where do I do this comparison or whether to even use memo
Can anybody help me with this?
Note: I understand I can put the setState inside the InputCard component and re-rendering will stop but as you can see, I need the setState variables inside the AnswerCard too for some processing.
That's how React works AFAIK. A change in props, triggers a render of the component.
That said,
Here are some suggestions for your problem:
Debounce
One of the common patterns around handling user input is to debounce it. This prevents the component re-render on every key press.
You can tailor the debounce timer to suit your use-case:
const Border = {
RED: {
border: '1px solid red',
margin: '12px 0',
padding: '4px'
},
GREEN: {
border: '1px solid green',
margin: '12px 0',
padding: '4px'
},
BLUE: {
border: '1px solid blue',
margin: '12px 0',
padding: '4px'
},
MAGENTA: {
border: '1px solid magenta',
margin: '12px 0',
padding: '4px'
}
};
const MARGIN = { margin: '12px' };
function useDebounce(value, delay) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = React.useState(value);
React.useEffect(
() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
);
return debouncedValue;
}
const useRenderCounter = (thing) => {
const renderCount = React.useRef(1);
React.useEffect(() => {
renderCount.current += 1;
});
return `Render count for ${thing} ${renderCount.current}`;
};
const InputCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('InputCard');
const [input, setInput] = React.useState(userInput);
const debouncedValue = useDebounce(input, 750);
React.useEffect(() => {
setUserInput(debouncedValue);
}, [debouncedValue]);
return (
<div style={Border.MAGENTA}>
<span>{renderCount}</span>
<div style={MARGIN}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
id="userQuery"
/>
</div>
</div>
);
};
const QuestionCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('QuestionCard');
return (
<div style={Border.RED}>
<span>{renderCount}</span>
<div style={MARGIN}>User Input: {userInput}</div>
</div>
);
};
const AnswerCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('AnswerCard');
return (
<div style={Border.GREEN}>
<span>{renderCount}</span>
<div style={MARGIN}>User Input: {userInput}</div>
</div>
);
};
function App() {
const renderCount = useRenderCounter('App');
const [userInput, setUserInput] = React.useState('');
return (
<div style={Border.BLUE}>
<span>{renderCount}</span>
<QuestionCard userInput={userInput} setUserInput={setUserInput} />
<AnswerCard userInput={userInput} setUserInput={setUserInput} />
<InputCard userInput={userInput} setUserInput={setUserInput} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("react"));
span {
padding: 4px;
font-style: italic;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
useRef
However, if you need the other components to not re-render on entering text, you can leverage useRef hook and keep track of the user input without having to re-render the component tree.
But bear in mind, with this approach, in order for other components to show the updated value on the DOM,
there has to be a trigger to cause a re-render. In the example, the state is updated on button click
which then triggers the re-render. Since you mentioned you are building a chat app, maybe you'd find this pattern useful.
const Border = {
RED: {
border: '1px solid red',
margin: '12px 0',
padding: '4px'
},
GREEN: {
border: '1px solid green',
margin: '12px 0',
padding: '4px'
},
BLUE: {
border: '1px solid blue',
margin: '12px 0',
padding: '4px'
},
MAGENTA: {
border: '1px solid magenta',
margin: '12px 0',
padding: '4px'
}
};
const MARGIN = { margin: '12px' };
const useRenderCounter = (thing) => {
const renderCount = React.useRef(1);
React.useEffect(() => {
renderCount.current += 1;
});
return `Render count for ${thing} ${renderCount.current}`;
};
const InputCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('InputCard');
const [input, setInput] = React.useState(userInput);
React.useEffect(() => {
setUserInput(input);
}, [input]);
return (
<div style={Border.MAGENTA}>
<span>{renderCount}</span>
<div style={MARGIN}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
id="userQuery"
/>
</div>
</div>
);
};
const QuestionCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('QuestionCard');
return (
<div style={Border.RED}>
<span>{renderCount}</span>
<div style={MARGIN}>User Input: {userInput}</div>
</div>
);
};
const AnswerCard = ({ userInput, setUserInput }) => {
const renderCount = useRenderCounter('AnswerCard');
return (
<div style={Border.GREEN}>
<span>{renderCount}</span>
<div style={MARGIN}>User Input: {userInput}</div>
</div>
);
};
function App() {
const renderCount = useRenderCounter('App');
const inputRef = React.useRef('');
const setUserInput = (input) => {
inputRef.current = input;
};
const [submit, onSubmit] = React.useState('');
return (
<div style={Border.BLUE}>
<span>{renderCount}</span>
<QuestionCard userInput={inputRef.current} setUserInput={setUserInput} />
<AnswerCard userInput={inputRef.current} setUserInput={setUserInput} />
<InputCard userInput={inputRef.current} setUserInput={setUserInput} />
<button type="submit" onClick={() => onSubmit(inputRef.current)}>
Trigger Render
</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("react"));
span {
padding: 4px;
font-style: italic;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
React.memo()
React.memo() is used to memoize a component, based on the dependencies passed to it. It takes a component as a prop and returns a component that prevents a component from re-rendering if the props (or values within it) have not changed.
It is important to keep in mind that your code must not depend on React.memo() just to avoid re-renders. You should be able to replace React.memo() with direct component calls and it must not affect anything else, except performance.
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs. - Docs
I honestly do not see any benefits by using a memo for your case. Anyways, this is ideally how you can memoize a component:
const QuestionCardMemoized = React.memo(
() => QuestionCard({ userInput, setUserInput }),
[userInput, setUserInput]
);
const AnswerCardMemoized = React.memo(
() => AnswerCard({ userInput, setUserInput }),
[userInput, setUserInput]
);
return (
<div>
<QuestionCardMemoized />
<AnswerCardMemoized />
<InputCard userInput={userInput} setUserInput={setUserInput} />
</div>
);
Do keep in mind that React is VERY fast. Unless you see any performance issues by measuring using profiler tools, I'd say spend your time on something more useful than unnecessary/pre-mature optimization.

react styled component created dynamically

I am new to styled Components from react and I am having trouble creating dynamically a clock with has an initial degree for minutes(minute degrees) and an initial degree for hours (hourDegrees).
This is so far what I have achieved, but I get the following message:
Keyframes.js:20 The component styled.div with the id of "..." has been created dynamically.
You may see this warning because you've called styled inside another component.
To resolve this only create new StyledComponents outside of any render method and function component.
APP CODE
function App() {
return (
<div className="App">
<main>
<div className="clock-wrap">
<section className="firstMinute">
{numbers[0].map((coord, index) => {
return (
<Clock
key={index}
hourDegrees={coord[0]}
minuteDegrees={coord[1]}
/>
);
})}
</section>
<section className="secondMinute">
{numbers[1].map((coord, index) => {
return (
<Clock
key={index}
hourDegrees={coord[0]}
minuteDegrees={coord[1]}
/>
);
})}
</section>
</div>
</main>
</div>
);
}
But I can't solve this issue as, from what I understand, I have created separated components with the info passed as props.
import styled from 'styled-components'
export default function Clock ({ minuteDegrees, hourDegrees, index}) {
const finalHourDegrees = Number(hourDegrees + 360)
const finalMinuteDegrees = Number(minuteDegrees + 360)
const StyledClock = styled.div`
width: 10vh;
`
//hour
const animationHour = keyframes`
from {
transform: rotate(${props => props.hourDegrees}deg);
}
to {
transform: rotate(${finalHourDegrees}deg);
}
`
const HourStyled = styled.div`
animation: ${animationHour} 4s ease-out infinite;
`
//minutes
const animationMinute = keyframes`
from {
transform: rotate(${props => props.minuteDegrees}deg);
}
to {
transform: rotate(${finalMinuteDegrees}deg);
}
`
const MinuteStyled = styled.div`
animation: ${animationMinute} 4s ease-out infinite;
`
return(
<StyledClock className={index}>
<HourStyled className={hourDegrees} key={index} hourDegrees={hourDegrees}/>
<MinuteStyled className={minuteDegrees} key={index} minuteDegrees={minuteDegrees}/>
</StyledClock>
)
}
Thanks a lot beforehand!
You can create StyledClock or MinuteStyled styled-components outside the target component. Also, you can send props to the styled components if needed.
UPDATED I forked your code below and updated by using a callback function for the keyframes to pass the dynamic degrees codesandbox
const StyledComponent = styled.div`
background: ${props => props.background};
`;
Clock
const StyledClock = styled.div`
width: 6vw;
`;
export default function Clock({ minuteDegrees, hourDegrees, index }) {
return (
<StyledClock className={index}>
<Hours index={index} hourDegrees={hourDegrees} />
<Minutes index={index} minuteDegrees={minuteDegrees} />
</StyledClock>
);
}
Minutes
const animationMinute = keyframes`
from {
transform: rotate(${(props) => props.minuteDegrees}deg);
}
to {
transform: rotate(${(props) => props.finalMinuteDegrees}deg);
}
`;
const MinuteStyled = styled.div`
animation: ${animationMinute} 4s ease-out infinite;
`;
export default function Minutes({ minuteDegrees, index }) {
const finalMinuteDegrees = Number(minuteDegrees + 360);
return (
<MinuteStyled
className={minuteDegrees}
key={index}
minuteDegrees={minuteDegrees}
finalMinuteDegrees={finalMinuteDegrees}
/>
);
}

Resources