5

I have an objectList and a size variable.

const [objectList, setObjectList] = useState([]); //will be filled elsewhere
const [size, setSize] = useState([props.width, props.height]); //might change on user input

useEffect(() => {
    //When size changes iterate over objectList
    objectList.forEach(object => {
        object.set({
            width: size.width,
            height: size.height
        );
    });
}, [size]);

Here React complains, that objectList is not in dependency, because it might be, that I want to depend the useEffect on objectList changes. However I don't. Objects can be added to the list occasionally but in this case I am setting size where the object will be added to the list. I do not want to iterate over every object when I add a new object to the list (objectList changed), only when the size changed.

How can I iterate over the objectList on each size change but not on objectList change?

2 Answers 2

1

There seems to be two actions affecting the objectList: on size change and when new object is added. You need to separate these two.

You can achieve this via useReducer to manage the different actions.

const initialState = {
  objectList: []
}

function reducer(state, action) {
  switch (action.type) {
    case 'ADD_OBJECT': /* TODO */ return;
    case 'UPDATE_SIZE': {
       const { size } = action.payload;
       return state.objectList.map((o) => ({
         ...o,
         width: size.width,
         height: size.height
       }))
    }
    default: /* TODO */;
  }
}

function App(props) {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    dispatch({
      type: 'UPDATE_SIZE',
      payload: {
        size: {
          width: props.width,
          height: props.height
        }
      }
    });
  }, [props.size]);
}
Sign up to request clarification or add additional context in comments.

5 Comments

Hm, if I had an onSizeChanged function, I could iterate over the objectList anyhow even without using a reducer, not? But that wouldn't work when I wanted it in a useEffect function.
I just saw your recent update about props.width, etc. Updated. Anyway, it's an anti-pattern where you use props.width, etc as initial state if the parent and child component updates these props too.
Ok this would be possible however is there an alternative? I am not using useReducer in my app. It would require quite a refactoring effort to change the whole app to useReducer. I extracted the example more abstract because I can't post the whole app code here.
Well, the only difference is how to update objectList which is dispatching an action rather than via setObjectList and accessing the objectList is just adding state like state.objectList. I don't think it's too much refactor given only two actions affecting objectList. If it is, I smell something else.
I realize that useReducer is probably best practice here and I always wanted to refactor to useReducer, however I wanted to add this feature quickly, the app needs quite some rework as I am writing it while learning. It works like it is now and refactor is needed but the time is limited now. That's why I wanted to see if I can just use useEffect here. I have a workaround now, that I get the objectList locally into a const objects but will come back to your answer when I have time to cleanup and refactor everything. Hence I check the answer as accepted correct.
1

I ended up doing this and it worked.

useEffect(() => {
    setObjectList((objectList)=>{
        objectList.forEach(object => {
            object.set({
                width: size.width,
                height: size.height
            );
        });
    
        return objectList;
    });
}, [size]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.