Creating A TimeFrameSelector React Component With Preserving User State

Nitish Kumar Singh
May 28, 2025Need a time-frame picker for your trading charts that remembers the user's choice? Let’s build a simple React component for that.
Hello developers, in this blog post we are going to create a TimeFrameSelector React component that can be used in a Trading Charts component while preserving user state between multiple sessions. We can see different states of the component and user interaction results in the image below.
What are the features of this component is as below:
- We will show time-frame in groups like Seconds, Minutes, and Hours.
- All these groups are expandable, and we preserve the expanded and collapsed state of each group.
- We will show short form of the selected time frame in a button, and this button is used to open and close the time-frame dropdown. This is also closed when clicked outside of its UI.
- When selected time frame changes, we notify this change to the required component that is subscribed to listen for these changes.
Okay, now let's start coding.
I have used a global object for loading component state, caching it, managing selected time-frame, and notifying components that want to listen for changes as shown in the code below.
export const timeFrames = {
groups:()=>{
if(!timeFrames.tfGroups) {
let tfData = localStorage.getItem("tfData");
if(tfData){
tfData = JSON.parse(tfData);
timeFrames.active = tfData.active;
timeFrames.tfGroups = tfData.groups;
}else {
tfData = {
active:{s:'1m',n:"1 minute",v:60},
groups:{
SECONDS:{frames:[
{s:"1s",n:"1 second",v:1},
{s:"2s",n:"2 seconds",v:2},
{s:"3s",n:"3 seconds",v:3},
{s:"4s",n:"4 seconds",v:4},
{s:"5s",n:"5 seconds",v:5},
{s:"10s",n:"10 seconds",v:10},
{s:"15s",n:"15 seconds",v:15},
{s:"30s",n:"30 seconds",v:30},
{s:"75s",n:"75 seconds",v:75},
],expanded:false},
MINUTES:{frames:[
{s:"1m",n:"1 minute",v:60},
{s:"2m",n:"2 minutes",v:120},
{s:"3m",n:"3 minutes",v:180},
{s:"4m",n:"4 minutes",v:240},
{s:"5m",n:"5 minutes",v:300},
{s:"10m",n:"10 minutes",v:600},
{s:"15m",n:"15 minutes",v:900},
{s:"30m",n:"30 minutes",v:1800},
{s:"75m",n:"75 minutes",v:4500},
],expanded:true},
HOURS:{frames:[
{s:"1h",n:"1 hour",v:3600},
{s:"2h",n:"2 hours",v:7200},
{s:"3h",n:"3 hours",v:10800},
{s:"4h",n:"4 hours",v:14400},
],expanded:false}
}
}
localStorage.setItem("tfData",JSON.stringify(tfData));
timeFrames.active = tfData.active;
timeFrames.tfGroups = tfData.groups;
}
}
return timeFrames.tfGroups;
},
tfGroups:undefined,
active:undefined,
onSelect:(second)=> console.log(`Now new time frame of chart is ${second}`)
};
We are exporting it to use the selected time-frame in candle formation and to listen for changes by keeping a function on the onSelect
property of this object.
We can keep this object as state in the parent of the Chart component and directly pass the listening function as props or use a state management library.
Below is the code of our main component TimeFrameSelector
, where we have written the logic of opening, closing, outside click, and rendering of the component.
export default function TimeFrameSelector() {
const [open, setOpen] = useState(false);
const containerRef = useRef();
const toggleOpen = () => setOpen(prev => !prev);
const handleSelect = (frame) => {
timeFrames.active = frame;
setOpen(false);
timeFrames.onSelect?.(frame.v);
};
useEffect(() => {
const handleClickOutside = (e) => {
if (open && containerRef.current && !containerRef.current.contains(e.target)) {
setOpen(false);
}
};
document.addEventListener("click", handleClickOutside);
return () => document.removeEventListener("click", handleClickOutside);
}, [open]);
const groups = timeFrames.groups();
return (
<div style={{ position: 'relative' }} ref={containerRef}>
<button onClick={toggleOpen} className="tfBtn">{timeFrames.active.s}</button>
{open && (
<div className="tfContainer">
<div className='tfContent'>
{Object.keys(groups).map((group)=> <TfGroup key={group} title={group} group={groups[group]} onSelect={handleSelect}/>)}
</div>
</div>
)}
</div>
);
}
We have used React.memo
to prevent unnecessary rendering by creating two React components: TfGroup
and Timeframe
as shown in the code below.
const TfGroup = React.memo(({title, group, onSelect})=>{
const [expanded,setExpanded] = useState(group.expanded);
const onClick = useCallback(()=>{
group.expanded = !group.expanded;
setExpanded(group.expanded);
const tfData = {active:timeFrames.active,groups:timeFrames.groups()};
localStorage.setItem("tfData",JSON.stringify(tfData));
});
return (
<div style={{display:"flex",flexDirection:"column"}}>
<label onClick={onClick}>{title}<span className="material-symbols-outlined" style={{fontSize:"22px",rotate:expanded?"180deg":"0deg"}}>keyboard_arrow_down</span></label>
{expanded ? group.frames.map(frame => <Timeframe key={frame.s} frame={frame} click={onSelect}/>):undefined}
</div>
)
});
const Timeframe = React.memo(({frame, click})=> {
const onClick = useCallback(()=>click(frame));
return <span onClick={onClick} className='frame'>{frame.n}</span>;
});
The CSS we have used to design this component is given below.
.tfContainer{
width: 130px;
z-index: 3;
padding: 5px 0;
position: absolute;
background: white;
box-shadow: 0 0 5px 5px #0000001f;
border-radius: 5px;
border: 1px solid #d0d0d0;
max-height: calc(100vh - 180px);
overflow: auto;
font-family: math;
}
.tfContent label{
font-size: 13px;
color: gray;
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px 0 2px 10px;
gap: 5px;
cursor: default;
}
.tfContent .frame{
font-size: 14px;
padding: 5px 10px;
cursor: default;
}
.tfContent .frame:hover, .tfContent label:hover{
background: rgb(233, 233, 233);
}
.tfBtn{
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border: none;
cursor: pointer;
border-radius: 3px;
}
Now we have successfully created a React component that handles time-frame selection of a Chart component. I hope we have enjoyed creating it and learned some meaningful things.
Thanks for reading this post. Happy Coding!