jpribyl / react-hook-resize-panel

MIT License
11 stars 3 forks source link

feature: set width dynamically #1

Open angelmtztrc opened 2 years ago

angelmtztrc commented 2 years ago

Hi,

I'm using this package on a little just-fun project, and I realised about one detail or problem.

I was building a layout, and one panel needs to have a dynamic width because it depends on the existence of another element (the values of the last are needed to calculate the final width of the panel).

For this, I'm using useState to store the initial and final result of the width, I also have a useEffect that sets the calculated data. In this example, I send the values (my state) to the <ResizePanel /> component via props, but the component does not take the state update and It does not trigger a rerender for that, so my new width is not set in the DOM but the contexts that are used inside of the package have the new values.

Ex.

const MyComponent = () => {
  const element = useRef(null)

  const [initialWidth, setInitialWidth] = useState(100)

  useEffect(() =. {
    if(element.current){
      // calculation...
      setInitialWidth(400)
    }
  }, [element])

  return (
    <ResizePanel
      // it would render 100 instead of 400
      initialWidth={initialWidth}    
    >
      {/* ... */}
    </ ResizePanel>
  )
}

Btw, would be amazing if support for computed values be added to the project :) (%, vw, rem, etc)

jpribyl commented 2 years ago

Hey! Thanks for the issue- this is definitely by design for initialWidth you may see it in the code here: https://github.com/jpribyl/react-hook-resize-panel/blob/main/src/resize-panel.tsx#L35

There are several issues with the code you provided

  1. a change to `ref`` in react will not trigger a state update
  2. the dependency array of useEffect only performs shallow comparison (so if it DID trigger a state update you would likely windup with an infinite render cycle)
  3. initialWidth provides the initial state for the render and should not be used to attempt a state update

I can suggest a couple approaches offhand. Based on your description, this may be an appropriate time to use flex. Keep in mind that <ResizePane l/> is just a context container and does not insert anything into the DOM. This means that any styles applied to <ResizeContent /> should behave themselves.

Here are some possible approaches:

  1. Use css rules + styles to control relative width. Depending upon your exact use case this is likely the cleanest solution and would also allow you to build a responsive layout (which I believe is what you're getting at with your second question)
  2. Use setWidth from the panel context. This will require a slight restructuring of your components but is generally a clean solution
  3. Set maxWidth and / or minWidth conditionally

For example something more like this:

export default function MyComponent() {
  const initialWidth = 100;
  const isBig = // do your computation here, do not memoize unless you have a good reason to do so

  return (
    <ResizePanel initialWidth={initialWidth}>
      <ResizeContent 
        style={{
          ...(isBig && { flexGrow: 2 }),
          ...(!isBig && { flexGrow: 1 }),
        }}
      {/* ... */}
      </ ResizeContent>
    </ ResizePanel>
  )
}
jpribyl commented 2 years ago

As far as allowing computed widths- feel free to open a PR! I would be happy to review it.

You will need to add logic into the ResizeHandle components which translates into a number of pixels to allow dragging. I didn't add it initially because it's non-trivial (though likely not too hard) and it's normally possible to build responsive components with flex