Sole
Sole

Reputation: 3340

Set default value in State object in React Typescript

At the moment in my component I am setting the default value to 'null', but I am trying to default the active states value to the first object in the objects which is part of the state, a bit confused.

code so far:

import React from 'react';

interface ActiveObject {
    id: number;
    title: string;
    function(index: number): void;
};

interface ButtonGroupState {
    activeObject: ActiveObject | null;
    objects: ActiveObject[];
};

const ButtonGroup: React.FunctionComponent = () => {

    const [active, setActive] = React.useState<ButtonGroupState>({
        activeObject: null,
        objects: [{
            id: 1,
            title: '1 Week',
            function(index: number) {
                setActive({ ...active, activeObject: active.objects[index] })
            }

        }, {
            id: 2,
            title: '1 Month',
            function(index: number) {
                setActive({ ...active, activeObject: active.objects[index] })

            }
        }, {
            id: 3,
            title: '3 Months',
            function(index: number) {
                setActive({ ...active, activeObject: active.objects[index] })

            }
        }]
    })

    const toggleActiveStyles = (index: number) => {
        if (active.objects[index] === active.activeObject) {
            return "btn-active"
        } else {
            return " btn-plain"
        }
    }

    return (
        <> <div className="wrapper">
            {active.objects.map((inner, index) => 
                <button type="button" className={toggleActiveStyles(index)} onClick={() => inner.function(index)} key={inner.id}>{inner.title}</button>
            )}
        </div>
        </>

    )
}

export default ButtonGroup;

Upvotes: 0

Views: 3248

Answers (2)

Drew Reese
Drew Reese

Reputation: 202781

You can either duplicate the first element data as the initial state. Note: factoring the array definition out would be an optimization.

const ButtonGroup: React.FunctionComponent = () => {
  const [active, setActive] = React.useState<ButtonGroupState>({
    activeObject: {
      id: 1,
      title: '1 Week',
      function(index: number) {
        setActive({ ...active, activeObject: active.objects[index] })
      }
    },
    objects: [{
      id: 1,
      title: '1 Week',
      function(index: number) {
        setActive({ ...active, activeObject: active.objects[index] })
      }
    }, {
      id: 2,
      title: '1 Month',
      function(index: number) {
        setActive({ ...active, activeObject: active.objects[index] })

      }
    }, {
      id: 3,
      title: '3 Months',
      function(index: number) {
        setActive({ ...active, activeObject: active.objects[index] })
      }
    }]
  });

or use an useEffect hook to set it on the initial render.

React.useEffect(() => {
  setActive(active[0]);
}, []);

Note

You appear to have a logical bug with the functions where they will have stale enclosures the active and activeObject state. This may be ok for the active.object if it's never updated. Since it's the same function for each element you should factor this out so your code is more DRY, and use a functional update.

You likely also don't what to duplicate state. Just store the active id (or index) and reference this in your code when accessing/passing props.

const setActive = (index: number) => {
  setActive(active => ({
    ...active,
    activeObject: index,
  }));
}

Then use/pass setActive as a prop where you need to.

...
activeObject={active.objects[active.activeObject]}
setActive={setActive}
...

Upvotes: 0

NascentSoul
NascentSoul

Reputation: 81

I suggest you move objects into a variable and set it's first element to activeObject

import React from 'react';

interface ActiveObject {
    id: number;
    title: string;
    function(index: number): void;
};

interface ButtonGroupState {
    activeObject: ActiveObject | null;
    objects: ActiveObject[];
};

const ButtonGroup: React.FunctionComponent = () => {
    const objectsArray = [{
            id: 1,
            title: '1 Week',
            function(index: number) {
                setActive({ ...active, activeObject: active.objects[index] })
            }

        }, {
            id: 2,
            title: '1 Month',
            function(index: number) {
                setActive({ ...active, activeObject: active.objects[index] })

            }
        }, {
            id: 3,
            title: '3 Months',
            function(index: number) {
                setActive({ ...active, activeObject: active.objects[index] })

            }
        }];

    const [active, setActive] = React.useState<ButtonGroupState>({
        activeObject: objectsArray[0],
        objects: objectsArray
    })

    const toggleActiveStyles = (index: number) => {
        if (active.objects[index] === active.activeObject) {
            return "bp-active"
        } else {
            return " btn-plain"
        }
    }

    return (
        <> <div className="wrapper">
            {active.objects.map((inner, index) => 
                <button type="button" className={toggleActiveStyles(index)} onClick={() => inner.function(index)} key={inner.id}>{inner.title}</button>
            )}
        </div>
        </>

    )
}

export default ButtonGroup;

Upvotes: 1

Related Questions